你们好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新......javascript
- github:https://github.com/Daotin/Web
- 微信公众号:Web前端之巅
- 博客园:http://www.cnblogs.com/lvonve/
- CSDN:https://blog.csdn.net/lvonve/
在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。如今就让咱们一块儿进入 Web 前端学习的冒险之旅吧!html
实例对象和构造函数之间的关系:前端
一、实例对象是经过构造函数来建立的,建立的过程叫实例化。java
二、如何判断一个对象是否是某种数据类型?git
实例对象.constructor === 构造函数名字
实例对象 instanceof 构造函数名字
由来:构造函数的问题。若是一个构造函数中有一个匿名方法,那么每实例化一个对象,而后在对象调用这个方法的时候,因为每一个对象的方法都是各自的,因此每次调用这个方法的时候都会在内存中开辟一块空间存储这个方法,这样就形成内存资源的浪费。github
解决方法:定义一个函数代替匿名方法。json
由这个思想,提出原型的概念。数组
原型的做用之一:共享数据,节省内存空间。浏览器
function Person(name, age) { this.name = name; this.age= age; } Person.prototype.eat = function () { console.log("haha"); }; var per1 = new Person("Daotin", 18); var per2 = new Person("lvonve", 18); console.log(per1); console.log(Person); console.log(per1.eat === per2.eat); // true
一、为 Person 构造函数添加 eat 方法,使得 Person 建立的实例对象调用的 eat 方法,共享内存空间。微信
二、实例对象 per 中有个属性
__proto__
也是对象,叫原型,它不是标准的属性(IE8 不支持,谷歌和火狐支持)。三、构造函数中有一个属性
prototype
也是对象,叫原型。它是标准属性,供开发人员使用。四、per1.eat === per2.eat; 因此原型的做用之一:共享数据,节省内存空间。
(使用面向对象思想)
面向对象思想:按钮是一个对象,div 是一个对象,样式时一种属性
<body> <input type="button" value="按钮" id="btn"> <div id="dv"></div> <script src="common.js"></script> <script> /* * 操做 Obj1 设置 Obj2 的属性 * 属性列表在 json 里面 * */ function ChangeStyle(Obj1, handle, Obj2, json) { this.Obj1 = Obj1; this.Obj2 = Obj2; this.handle = handle; this.json = json; } ChangeStyle.prototype.init = function () { var that = this; that.Obj1[that.handle] = function () { for (var key in that.json) { that.Obj2.style[key] = that.json[key]; } }; }; var json = {"width": "200px", "height": "200px", "backgroundColor": "red"}; var cs = new ChangeStyle(my$("btn"), "onclick", my$("dv"), json); cs.init(); </script> </body>
一、实例对象是由构造函数建立的;
二、构造函数中有个属性prototype
,指向原型对象;
三、原型对象中有一个构造器,指向构造函数;
四、实例对象中的下划线原型对象__proto__
指向原型对象 prototype
。
五、原型对象中的方法能够被实例对象访问,虽然实例对象中没有这个方法,可是实例对象中 __proto__
指向 prototype
,因此全部的实例对象共享原型对象中的方法。
什么样的数据须要添加到原型对象呢?
须要数据共享的数据,不管是属性仍是方法。
既然 prototype
是一个对象,那么须要添加的属性和方法就能够以对象的方法添加:
<script> function Person(name, age) { this.name = name; this.age= age; } Person.prototype = { // 手动修改构造器指向 constructor: Person, sex: "man", eat: function () { console.log("eat"); }, study: function () { console.log("study"); } }; var per = new Person("lvovne", 18); console.log(per); </script>
须要注意的是:这种写法须要手动修改构造器指向,原型对象中将无这个构造器。
咱们知道,实例对象中的方法是能够相互访问的,那么原型对象中的方法能够相互访问吗?
也是能够的。
实例对象使用的属性和方法会先在实例中查找,找不到才会到__proto__
指向的构造函数的原型对象 prototype
中找,找到了则使用,找不到则报错。
像正常为自定义构造函数添加原型方法同样。
把函数中的局部变量暴露给浏览器顶级对象 window,那么这个局部变量将变成 window 的一个属性,能够被整个浏览器所访问。
(function () { var num = 10; window.num = num; })(); console.log(num);
<script> // 产生随机数对象 (function () { function Random() {} Random.prototype.getRandom = function (min, max) { // 范围 min ~ max-1 return Math.floor(Math.random()*(max-min) + min); }; window.Random = Random; })(); var rd = new Random(); var num = rd.getRandom(0,5); console.log(num); </script>
这里把自定义的产生随机数的 Random 对象暴露给顶级对象 window,那么 Random 从局部对象变成全局对象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .map { width: 800px; height: 600px; background-color: #ccc; position: relative; } </style> </head> <body> <div class="map"></div> <script src="common.js"></script> <script> // 获取地图对象 var map = document.getElementsByClassName("map")[0]; // 产生随机数对象 (function () { // 产生随机数的构造函数 function Random() { } // 在原型对象中添加方法 Random.prototype.getRandom = function (min, max) { // 范围 min ~ max-1 return Math.floor(Math.random()*(max-min) + min); }; window.Random = Random; })(); // 产生小方块对象 (function () { // 食物的构造函数 function Food(width, height, color) { this.width = width||20; this.height = height||20; this.color = color||"green"; this.x = 0; // left属性 this.y = 0; // top属性 this.element = document.createElement("div"); // 食物实例对象 } // 初始化小方块的显示效果及位置 Food.prototype.init = function () { var div = this.element; div.style.position = "absolute"; div.style.width = this.width + "px"; div.style.height = this.height + "px"; div.style.backgroundColor = this.color; map.appendChild(div); this.product(map); }; // 产生小方块的随机位置 Food.prototype.product = function () { var div = this.element; var x = new Random().getRandom(0, map.offsetWidth/this.width) * this.width; var y = new Random().getRandom(0, map.offsetHeight/this.height) * this.height; this.x = x; this.y = y; div.style.left = this.x + "px"; div.style.top = this.y + "px"; }; window.Food = Food; })(); var food = new Food(20,20,"red"); food.init(map); </script> </body> </html>
一、产生小方块对象也是个自调用函数,这里面有一个构造函数 Food,两个原型函数 init 和 product,其中构造函数中包括小方块的全部属性,好比小方块是个 div,小方块的宽高,颜色,left,top 的值等。
二、init 方法初始化小方块的宽高,颜色以及将 div 加入到地图 map 中。
三、product 方法是产生随机位置,并赋值给小方块的 left,top。
四、最后,在产生小方块对象的最后,将 Food 对象暴露给 window,这样在 Food 自调用函数的外面也能够产生小方块。
这个案例按照面向对象的思想,咱们把它分红四个模块。
第一,地图模块;
第二,食物模块,也就是上面小方块的模块;
第三,小蛇模块;
第四,整个游戏也能够封装成一个模块,这个模块是将上面三个模块再次封装起来。
地图模块最简单了,就是一块 div,设置宽高,背景就能够了。
注意:因为食物和小蛇都要在背景之上移动,因此食物和小蛇是脱标的,因此地图须要设置 position: relative;
<style> .map { width: 800px; height: 600px; background-color: #ccc; position: relative; } </style> <div class="map"></div>
首先,食物的主体是由一个 div 组成,另外还有宽高,背景颜色属性,在食物脱标以后还有left,top属性,因此为了建立一个食物对象,就须要一个食物的构造函数,这个构造函数要设置食物的属性就是上面提到的属性。
function Food(width, height, color) { this.width = width || 20; this.height = height || 20; this.color = color || "green"; this.x = 0; // left属性 this.y = 0; // top属性 this.element = document.createElement("div"); // 食物实例对象 }
别忘了将 Food 暴露给 window
window.Food = Food;
以后须要初始化食物的显示效果和位置。
一、因为常用 init 函数,因此将其写入原型对象。
二、每次在建立食物以前先删除以前的小方块,保证map中只有一个食物
三、咱们须要在自调用函数中定义一个数组,用来保存食物,方便每次建立食物以前的删除和后来小蛇吃掉食物后的删除。
Food.prototype.init = function (map) { // 每次在建立小方块以前先删除以前的小方块,保证map中只有一个小方块 remove(); var div = this.element; map.appendChild(div); div.style.width = this.width + "px"; div.style.height = this.height + "px"; div.style.backgroundColor = this.color; div.style.borderRadius = "50%"; div.style.position = "absolute"; this.x = new Random().getRandom(0, map.offsetWidth / this.width) * this.width; this.y = new Random().getRandom(0, map.offsetHeight / this.height) * this.height; div.style.left = this.x + "px"; div.style.top = this.y + "px"; // 把div加到数组中 elements.push(div); };
食物的删除函数。设置为私有函数,其实就是自调用函数中的一个函数,保证不被自调用函数外面使用。
function remove() { for (var i = 0; i < elements.length; i++) { elements[i].parentElement.removeChild(elements[i]); elements.splice(i, 1); // 清空数组,从i的位置删除1个元素 } }
小蛇模块也得现有构造函数。
一、direction是小蛇移动的方向;
二、beforeDirection 是小蛇在键盘点击上下左右移动以前移动的方法,做用是不让小蛇回头,好比小蛇正往右走,不能点击左按键让其往左走,这时只能上下和右走。
三、小蛇最初的身体是三个 div,因此每一个 div 都是一个对象,有本身的宽高和背景颜色和坐标。,因此这里用一个数组保存小蛇的身体。
function Snack(width, height, direction) { // 小蛇每一块的宽高 this.width = width || 20; this.height = height || 20; this.direction = direction || "right"; this.beforeDirection = this.direction; // 小蛇组成身体的每一个小方块 this.body = [ {x: 3, y: 2, color: "red"}, {x: 2, y: 2, color: "orange"}, {x: 1, y: 2, color: "orange"} ]; }
仍是须要暴露给 window
window.Snack = Snack;
小蛇的初始化函数就就是设置构造函数中的属性。因为有多个 div 组成,因此要循环设置。初始化以前也要先删除小蛇。
Snack.prototype.init = function (map) { // 显示小蛇以前删除小蛇 remove(); // 循环建立小蛇身体div for (var i = 0; i < this.body.length; i++) { var div = document.createElement("div"); map.appendChild(div); div.style.width = this.width + "px"; div.style.height = this.height + "px"; div.style.borderRadius = "50%"; div.style.position = "absolute"; // 移动方向,移动的时候设置 // 坐标位置 var tempObj = this.body[i]; div.style.left = tempObj.x * this.width + "px"; div.style.top = tempObj.y * this.width + "px"; div.style.backgroundColor = tempObj.color; // 将小蛇添加到数组 elements.push(div); } };
接下来是小蛇移动的方法。
一、小蛇移动的方法分两步,第一步是身体的移动;第二步是头部的移动。
二、当小蛇头坐标和食物的坐标相同时,表示吃到食物,这个时候小蛇要增加,怎么增加呢?将小蛇的尾巴赋值一份添加到小蛇的尾部。
三、以后删除食物,并从新生成食物。
Snack.prototype.move = function (food, map) { var index = this.body.length - 1; // 小蛇身体的索引 // 不考虑小蛇头部 for (var i = index; i > 0; i--) { // i>0 而不是 i>=0 this.body[i].x = this.body[i - 1].x; this.body[i].y = this.body[i - 1].y; } // 小蛇头部移动 switch (this.direction) { case "right" : // if(this.beforeDirection !== "left") { this.body[0].x += 1; // } break; case "left" : this.body[0].x -= 1; break; case "up" : this.body[0].y -= 1; break; case "down" : this.body[0].y += 1; break; default: break; } // 小蛇移动的时候,当小蛇偷坐标和食物的坐标相同表示吃到食物 var headX = this.body[0].x * this.width; var headY = this.body[0].y * this.height; // 吃到食物,将尾巴复制一份加到小蛇body最后 if ((headX === food.x) && (headY === food.y)) { var last = this.body[this.body.length - 1]; this.body.push({ x: last.x, y: last.y, color: last.color }); // 删除食物 food.init(map); } };
首先建立游戏对象须要游戏构造函数,这个构造函数应该包含三个部分:食物,小蛇和地图。
这个 that 是为了之后进入定时器后的 this 是 window,而不是 Game 作的准备。
function Game(map) { this.food = new Food(20, 20, "purple"); this.snack = new Snack(20, 20); this.map = map; that = this; }
而后是游戏初始化函数,初始化游戏的目的就是让游戏开始,用户能够开始玩游戏了。
这里面调用了两个函数:autoRun 和 changeDirection。
Game.prototype.init = function () { this.food.init(this.map); this.snack.init(this.map); this.autoRun(); this.changeDirection(); };
autoRun 是小蛇自动跑起来函数。这个函数主要是让小蛇动起来,而且在碰到边界时结束游戏。
注意:这里有一个在函数后面使用 bind 函数:使用bind,那么 setInterval 方法中全部的 this 都将被bind 的参数 that 替换,而这个 that 就是 Game。
Game.prototype.autoRun = function () { var timeId = setInterval(function () { this.snack.move(this.food, this.map); this.snack.init(this.map); // 判断最大X,Y边界 var maxX = this.map.offsetWidth / this.snack.width; var maxY = this.map.offsetHeight / this.snack.height; // X方向边界 if ((this.snack.body[0].x < 0) || (this.snack.body[0].x >= maxX)) { // 撞墙了 clearInterval(timeId); alert("Oops, Game Over!"); } // Y方向边界 if ((this.snack.body[0].y < 0) || (this.snack.body[0].y >= maxY)) { // 撞墙了 clearInterval(timeId); alert("Oops, Game Over!"); } }.bind(that), 150); // 使用bind,那么init方法中全部的this都将被bind的参数that替换 };
changeDirection 是监听按键,改变小蛇的走向。
这里面按键按下的事件是 onkeydown 事件。每一个按键按下都会有一个对应的 keyCode 值,经过这个值就能够判断用户按下的是哪一个键。
Game.prototype.changeDirection = function () { addAnyEventListener(document, "keydown", function (e) { // 每次按键以前保存按键方向 this.snack.beforeDirection = this.snack.direction; switch (e.keyCode) { case 37: // 左 this.snack.beforeDirection !== "right" ? this.snack.direction = "left" : this.snack.direction = "right"; break; case 38: // 上 this.snack.beforeDirection !== "down" ? this.snack.direction = "up" : this.snack.direction = "down"; break; case 39: // 右 this.snack.beforeDirection !== "left" ? this.snack.direction = "right" : this.snack.direction = "left"; break; case 40: // 下 this.snack.beforeDirection !== "up" ? this.snack.direction = "down" : this.snack.direction = "up"; break; default: break; } }.bind(that)); };
最后,咱们只须要两行代码就能够开启游戏。
var game = new Game(document.getElementsByClassName("map")[0]); game.init();