JS本身不存在类的概念,它对于继承的判定有时非常混乱,当然这并不影响我们以面向对象的思维去编程。
例如我们有两个类A类B类,我们想让B类继承A类,在python中可以轻而易举的实现,在JS中需要一种折中法
function A() { } A.prototype.hello = function() { console.log(1) } function B() { } B.prototype = new A(); (new B()).hello()
B的原型对象指向了A的实例
// 在子类需要继承父类属性的情况下需要用这种方法 function A(a) { this.a = a; } A.prototype.hello = function() { console.log(this.a) } function B(a, b) { this.constructor.apply(this, arguments) this.b = b; } B.prototype = new A(); (new B(1, 2)).hello()
1 var obj = { 2 a : 1, 3 b : 2, 4 c : false 5 } 6 7 console.log("a" in obj); //true 8 console.log("b" in obj); //true 9 console.log("c" in obj); //true console.log("d" in obj); //false
in不仅仅检测是对象自己有没有这个属性,如果原型链上有这个属性,那么也会返回true。整个原型链如果没有这个属性,就返回false。也就是说,in操作符会进行原型链查找。
for in 这个循环,会把原型链上所有的可枚举的属性列出来
而hasOwnProperty方法则是只检测自己是否有这个属性
值得说的是instanceof这个运算符的机理,它会查找原型链所有原型,只有要构造函数指向了原型链上的任意原型它就会返回true
function A() { } function B() { } B.prototype = new A(); console.log(new B() instanceof B) // 此时B的原型已经是A的实例,在原型链层面上没有任何逻辑可以证明B的实例的构造函数是B但是结果是true // 再举个例子 function C() {} C.prototype = Array.prototype; var arr = []; console.log(arr instanceof C) // 返回的也是true,如果说上一个例子B的实例和构造函数的关系是真实的,那么构造函数C和arr并不存在任何关系 // 只是C的原型指向了Array的原型,但instanceof的意义是证明arr 是 C 的实例,实际并不是
面向对象对于代码的组织结构更加清晰例如,贪吃蛇小游戏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <style> table, tr, td { border: 1px solid #000; border-collapse: collapse; } td { 18px; height: 18px; } .red { background-color: red; } .blue { background-color: blue; } </style> <body> <table id="snake"> </table> <script> // 面向对象的思维做游戏,首先确认需要的类 // 地图类 // 蛇类 // 食物类 // 创建地图类 function Map() { this.data = []; this.timer = null; this.dom = document.getElementById('snake'); this.init(); } // 地图类有一个初始化的方法 Map.prototype.init = function() { var rowArr; var tr; var td; for (var row = 0; row < 40; row++) { rowArr = []; tr = document.createElement('tr'); this.dom.appendChild(tr); for (var col = 0; col < 40; col++) { td = document.createElement('td'); tr.appendChild(td); rowArr.push(td); } this.data.push(rowArr) } } // Map有一个清空画布的方法 Map.prototype.clear = function() { for (var i = 0; i < 40; i++) { for (var j = 0; j < 40; j++) { this.data[i][j].className = ""; } } }; // 创建蛇类 function Snake() { this.location = [{ 'x': 5, 'y': 10 }, { 'x': 5, 'y': 9 }, { 'x': 5, 'y': 8 }, { 'x': 5, 'y': 7 }, { 'x': 5, 'y': 6 }, { 'x': 5, 'y': 5 }, ]; this.direction = 'right'; this.isEnd = false; this.render(); this.bindEvenet(); } // 蛇有一个渲染方法 Snake.prototype.render = function() { var tempDom; for (var i = 0; i < this.location.length; i++) { if (this.isEnd) { break; } tempDom = map.data[this.location[i].x][this.location[i].y]; tempDom.className = 'blue' } }; // 蛇有一个更新自己的函数 Snake.prototype.update = function() { // 蛇头向用户输入方向加1蛇尾减1 this.location.pop(); // 随时都要监听用户输入了什么方向 switch (this.direction) { case 'left': this.location.unshift({ 'x': this.location[0].x, 'y': this.location[0].y - 1, }); break; case 'top': this.location.unshift({ 'x': this.location[0].x - 1, 'y': this.location[0].y, }); break; case 'right': this.location.unshift({ 'x': this.location[0].x, 'y': this.location[0].y + 1, }); break; case 'bottom': this.location.unshift({ 'x': this.location[0].x + 1, 'y': this.location[0].y, }); break; } this.check(); }; // 给蛇类绑定一个方向事件 Snake.prototype.bindEvenet = function() { var self = this; document.onkeydown = function(event) { event = event || window.event; switch (event.keyCode) { case 37: // 蛇头只有一个方向,假设当前方向为左 // 那么我们蛇的X值是-1的,如果这时候用户输入右 // 那么X值变为 + 1,此时就会出现bug也就是短暂的重合 if (self.direction == 'right') { return; } self.direction = 'left' break; case 38: if (self.direction == 'bottom') { return; } self.direction = 'top' break; case 39: if (self.direction == 'left') { return; } self.direction = 'right' break; case 40: if (self.direction == 'top') { return; } self.direction = 'bottom' break; } }; }; // 蛇有一个吃食物的方法 Snake.prototype.eatFood = function() { // 蛇头碰到食物 if (this.location[0].x == food.x && this.location[0].y == food.y) { // 当我们碰到食物时重新计算位置 food.change(); // 蛇头加1 switch (this.direction) { case 'left': this.location.unshift({ 'x': this.location[0].x, 'y': this.location[0].y - 1, }); break; case 'top': this.location.unshift({ 'x': this.location[0].x - 1, 'y': this.location[0].y, }); break; case 'right': this.location.unshift({ 'x': this.location[0].x, 'y': this.location[0].y + 1, }); break; case 'bottom': this.location.unshift({ 'x': this.location[0].x + 1, 'y': this.location[0].y, }); break; } } }; // 蛇有要检测是否撞到自己或边缘 Snake.prototype.check = function() { if (snake.location[0].x < 0 || snake.location[0].x > 39 || snake.location[0].y < 0 || snake.location[0].y > 39) { this.isEnd = true; } for (var i = 0; i < this.location.length - 1; i++) { if (this.location[0].x == this.location[i + 1].x && this.location[0].y == this.location[i + 1].y) { this.isEnd = true } } }; // 食物类 function Food() { this.x = NaN; this.y = NaN; this.change(); }; // 食物类有个方法,计算食物的位置 Food.prototype.change = function() { // 食物不能和蛇重合 this.x = parseInt(Math.random() * 40) this.y = parseInt(Math.random() * 40) for (var i = 0; i < snake.location.length; i++) { if (this.x == snake.location[i].x && this.y == snake.location[i].y) { food.create(); } } }; // 食物类有一个渲染方法 Food.prototype.render = function() { map.data[this.x][this.y].className = 'red'; }; var map = new Map(); var snake = new Snake(); var food = new Food(); var timer = setInterval(function() { // 清空画布 map.clear(); // 我们将其想象成canvas现在我们自己建的画布每秒都在重新绘制 // 清空画布后我们需要渲染蛇与食物 snake.update(); snake.eatFood(); snake.render(); food.render(); if (snake.isEnd) { clearInterval(timer) alert('GAME OVER') } }, 300); // 当然这里只是最简单的实现,如果你想同时产生多个不同大小的食物,根据蛇身体大小加速等等,都可以自由实现 // 比如要产生多个食物,可以给食物类添加食物地图... // 面向对象的思维就是可以根据人类的思维无限延伸,理论上是这样,创造性和协调性更强 </script> </body> </html>