当面试官问你了不了解defineProperty的时候。。。

在当前的前端环境下,vue这种框架可以算是一项基础技能,可以说不会vue很难找到工作,而且大多数的面试官都很喜欢问的一个问题就是,关于vue的双向数据绑定原理,这个问题可以说是耳熟能详了,那抛开vue的设计思路,单单就是 Object.defineProperty() 这个api的话,说你写过这个就够了。

这是一个非常简单的贪吃蛇的小游戏(请忽略里面非常多的细节bug。。。),这个小游戏就是通过defineProperty这个api实现的。这个api的一些属性就不多介绍了,相信大家都知道。
首先,先要分析一下这个游戏,主体的组成成分就是三个类,背景,食物,和蛇,剩下的就是那个开始按钮,暂且不管。接下来就开始一个一个来看,先说背景,这个背景可以看成是一个类似于棋盘的东西,既然是棋盘就可以把它当成一个平面直角坐标系构成的网格,

然后这个网格就可以看成是一个二维数组,这样就可以对应坐标了,既然要用defineProperty,所以每一项都要是个对象。好,背景这个类大概的功能就是这样了。

class Qipan {
	constructor(w, h, id){
		this.w = w
		this.h = h
		this.box = document.getElementById(id)
	}
	init(){
		var tpl = "<ul class='point clearfix'>"
		var tem = ''
		for (var i = 0;i < this.w;i++) {
			tpl += '<li></li>'
		}
		tpl += '</ul>'
		for(var j = 0;j < this.h;j++){
			tem += tpl
		}
		this.box.innerHTML = tem
		this.box.style.width = 20 * this.w +'px'
	}
	getQi () {
		var arr = []
		for (var i = 0;i < this.h; i++) {
			arr.push(new Array())
			for (var j = 0;j < this.w;j++){
				arr[i].push({flag : false,newFlag: '', site:[i, j]})
			}
		}
		return arr
	}
}
复制代码

接下来就要开始劫持每个格子对应的对象的key。

function observer(arr) {
	arr.forEach(arr1 => {
		arr1.forEach( item => {
			Object.defineProperty(item,'flag',{
				enumerable: true,
				configurable: true,
				get: ()=>{
					return item.newFlag
				},
			  	set:newVal=> {
			  		if (newVal === 'snake') {
			            document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = '#DB7093'
			  			item.newFlag = 'snake'
			  		} else if (newVal === 'food') {
			  			document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = 'red'
			  			item.newFlag = 'food'
			  		} else {
			  			item.newFlag = ''
			  			document.getElementsByClassName('point')[item.site[0]].getElementsByTagName('li')[item.site[1]].style.background = '#F5DEB3'
			  			
			  		}
		        }
			})
		})
	})
}
复制代码

介绍一下里面的参数
configurable
当且仅当该属性的configurable为true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值: value
该属性对应的值。可以是任何有效的JavaScript值(数值,对象,函数等)。默认为 undefined。
writable
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。 存取描述符同时具有以下可选键值:
get
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 默认为 undefined。
set
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
(摘自MDN)

这里我们监听的属性为flag,flag表示的就是当前这个网格是哪个对象所处的位置,如果是食物处于这个位置,那这个flag就是food,当这个字段改变的时候,根据这个字段来判断,这个位置的状态,并作出相应的改变。

再来分析第二个类,蛇,这个类应该具有的功能,初始化,移动,吃食物,变长,死亡。这里初始化和死亡可以看成是一个方法,这里的移动就要根据上下左右和速度前进,吃食物和变长简单理解就是下一个前进的格子如果食物的话,就直接变成蛇的一部分就好了。

class Snake {
	constructor(qipan,h,food){
		this.qipan = qipan
		this.h = h
		this.h2 = JSON.parse(JSON.stringify(h))
		this.direct = 'left'
		this.que = []
		this.food = food
	}
	init () {
		this.qipan.forEach(item=>{
			item.forEach(items=>{
				items.flag = ''
			})
		})
        this.direct = 'left'
		
		if (this.que.length !== 0) {
			this.h = JSON.parse(JSON.stringify(this.h2))
			this.que = []
			clearInterval(time)
			time = null
		} 
			this.qipan[this.h[0]][this.h[1]].flag = 'snake'
			this.qipan[this.h[0]][this.h[1] + 1].flag = 'snake'
			this.que.push(this.qipan[this.h[0]][this.h[1]])
			this.que.push(this.qipan[this.h[0]][this.h[1] + 1])
		
	}
	getUp(){
		this.direct = 'up'
	}
	getLeft() {
		this.direct = 'left'
	}
	getRight(){
		this.direct = 'right'
	}
	getDown(){
		this.direct = 'down'
	}
	getGo (){
		var _this = this
		switch (this.direct) {
			case 'left':
                if (this.qipan[this.h[1] - 1]) {
                    wooDir(this.qipan[this.h[0]][this.h[1] - 1],'left')
                } else {
                    this.init()
                    this.food.init()
                }

				break
			case 'right':
                if (this.qipan[this.h[1] + 1]) {
                    wooDir(this.qipan[this.h[0]][this.h[1] + 1], 'right')
                } else {
                    this.init()
                    this.food.init()
                }
				break
			case 'up':
				if (this.qipan[this.h[0] - 1]) {
					wooDir(this.qipan[this.h[0] - 1][this.h[1]], 'up')
				} else {
					this.init()
                    this.food.init()
				}
				
				break
			case 'down':
				if (this.qipan[this.h[0] + 1]) {
					wooDir(this.qipan[this.h[0] + 1][this.h[1]], 'down')
				} else {
					this.init()
                    this.food.init()
				}
				
				break
		}
		function wooDir(oo, dir) {
            if (oo) {
                if (oo.flag === '') {
                    oo.flag = 'snake'
                    switch (dir) {
						case 'left':
                            _this.h[1] -= 1
							break
                        case 'right':
                            _this.h[1] += 1
                            break
                        case 'up':
                            _this.h[0] -= 1
                            break
                        case 'down':
                            _this.h[0] += 1
                            break

                    }
                    _this.que.unshift(oo)
                    _this.que[_this.que.length - 1].flag = ''
                    _this.que.pop()
                } else if (oo.flag === 'snake') {
                    _this.init()
                    _this.food.init()
                } else if (oo.flag === 'food') {
                    _this.eat(oo)
                }
            } else {
                _this.init()
                _this.food.init()

            }
        }

	}
    eat (oo) {
        oo.flag = 'snake'
        this.que.unshift(oo)
        this.h = JSON.parse(JSON.stringify(oo.site))
        this.food.init()
    }

}
复制代码

最后是食物,这个就比较简单了,只要能初始化就好了。

class Food {
	constructor (qipan) {
		this.qipan = qipan
	}
	init() {
		var _this = this
		var arr = []
		_this.qipan.forEach(item=>{
			item.forEach(items=>{
				if(items.flag !== 'snake') {
					arr.push(items)
				}
			})
		})
		arr[Math.floor(Math.random()*arr.length)].flag = 'food'
	}
}
复制代码

好,三个类都准备好了,剩下的就是实例化对象,然后设置一个蛇往前走的定时器,和改变方向的监听事件,就ok了。

扫描二维码关注公众号,回复: 6564761 查看本文章
var qi = new Qipan(10,10, 'box')
		qi.init()
		var pan = qi.getQi()
		var time = null
		observer(pan)
		var food = new Food(pan)
        food.init()
        var sna = new Snake(pan, [0, 3],food)
        sna.init()
		var btn_s = document.getElementById('start')
		btn_s.onclick = function () {
			if (time === null) {
				time = setInterval(function(){
					sna.getGo()
				}, 200)
			}

		}
		document.onkeydown = function (ev) {
			var e = event || window.event || arguments.callee.caller.arguments[0]
			if(e && e.keyCode === 37 ){
				sna.getLeft()
            }
			if(e && e.keyCode === 38 ){
				sna.getUp()
            }
			if(e && e.keyCode === 39 ){
				sna.getRight()
            }
			if(e && e.keyCode === 40 ){
				sna.getDown()
            }
		}
复制代码

ok,这样基本的功能就实现了。完整版请看 github.com/whyjson/com… 。不过这个代码是好久之前写的了,结构很模糊,耦合性也很高,之后会优化一下代码,希望大家能够看的明白。。
盒盒盒盒。。。

转载于:https://juejin.im/post/5d0aed9af265da1b672113ce

猜你喜欢

转载自blog.csdn.net/weixin_34090562/article/details/93164373