一、对象
JavaScript 的设计是一个简单的基于对象的范式。在 JavaScript 中,一个对象可以是一个单独的拥有属性和类型的实体。一个对象就是一系列属性的集合,一个属性包含一个名和一个值。一个属性的值可以是函数,这种情况下属性也被称为方法。
JavaScript 拥有一系列预定义的对象。另外,你可以创建你自己的对象。
1、使用对象初始化器
可以通过对象初始化器创建对象,即通过字面值创建对象。
var obj = { property_1: value_1, // property_# 可以是一个标识符...
2: value_2, // 或一个数字...
["property" +3]: value_3, // 或一个可计算的key名...
// ...,
"property n": value_n }; // 或一个字符串
- obj:新对象的名称;
- property_i:一个标识符(可以是一个名称、数字或字符串字面量);
- value_i:一个其值将被赋予 property_i 的表达式。
示例代码:
var obj = {id:"001", say:function(){alert("Hello!");}};
obj.say(); // Hello!
2、使用构造函数
作为另一种方式,你可以通过两步来创建对象:
- 通过创建一个构造函数来定义对象的类型(一般首字母大写);
- 通过 new 创建对象实例。
function Obj(arg1,...){
this.arg1 = arg1;
...
this.otherArg = value;
}
var obj = new Obj(arg1,...);
- obj:对象的类型名称;
- arg1,…:创建实例时传入的参数;
- otherArg,value:未传入的参数,及赋的值。
示例代码:
var obj = {id:"001", say:function(){alert("Hello!");}};
function Person(name, age){
this.name = name;
this.age = age;
this.say = function(){alert("I'm " + this.name +", my age is " + age + ".");};
}
var per = new Person("Tom", 20);
per.say(); // I'm Tom, my age is 20.
3、使用 Object.create 方法
对象也可以用 Object.create() 方法创建。该方法非常有用,因为它允许你为创建的对象选择其原型对象,而不用定义一个构造函数。
var Obj = { property_i:value_i,
...};
var obj= Object.create(Obj);
- obj:原型对象的名称;
- property_i:一个标识符(可以是一个名称、数字或字符串字面量);
- value_i:一个其值将被赋予 property_i 的表达式。
示例代码:
var Animal = {
type: "Invertebrates", // Default value of properties
displayType : function() { // Method which will display type of Animal
console.log(this.type);
}
}
var fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Output:Fishes
二、原型
所有的 JavaScript 对象继承于至少一个对象。被继承的对象被称作原型,并且继承的属性可通过构造函数的 prototype 对象找到。
1、构造函数、原型对象、实例对象
- 在构造函数中,有一个属性——prototype,指向它的原型对象;
- 在原型对象中,有一个属性——constructor,指向它所在的构造函数;
- 在实例对象( object )中,有一个私有属性(__proto__),指向它的构造函数的原型对象(prototype )。
- 示例代码1:
利用原型,让不同实例对象共享属性、方法。当然,我们也可以通过原型为系统内置对象添加属性和方法。
<script>
function Person(name, age){
this.name = name;
this.age = age;
this.play = function(){console.log("Playing")};
}
// 利用原型共享的属性和方法
Person.prototype.country = "China";
Person.prototype.say = function(){console.log("hello")};
var p1 = new Person("Tom", 20);
var p2 = new Person("Alice", 21);
console.log(p1.play == p2.play); // false
console.log(p1.say == p2.say); // true
</script>
- 示例代码2:
简单的原型语法,直接赋值给 prototype 对象。注意,采用这种方式的话,会覆盖掉 constructor 属性,因此需要手动指定构造器的引用。
<script>
function Person(name, age){
this.name = name;
this.age = age;
this.play = function(){console.log("Playing")};
}
// 简单的原型语法
Person.prototype = {
// 需要手动指定构造器的引用
constructor : Person,
country : "China",
say : function(){console.log("hello")}
};
var p1 = new Person("Tom", 20);
console.log(p1.country); // China
p1.say(); // hello
console.dir(Person);
</script>
2、原型链
对于实例对象中的原型对象,该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
- 示例代码1:
当对象与对象的原型存在同名属性时,访问到的是对象中的属性;当访问只存在于原型对象中的属性时,可以直接访问到原型对象中的属性。
<script>
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.age = "18";
Person.prototype.country = "China";
var p1 = new Person("Tom", 20);
console.log(p1.age); // 20
console.log(p1.country); // China
</script>
3、通过原型实现继承
(1)改变原型引用
将子类级别的原型引用指向父类级别实例对象,然后隐式地通过父类级别实例对象中的“__proto__”属性共享父类级别的方法和属性。这种方式虽然继承了父级类别的属性和方法,但是子类级别继承属性的值被固定为父类级别实例对象中的值。
- 示例代码:
<script>
// 父类
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){console.log("hello, my name is " + this.name);};
// 子类
function Student(id){
this.id = id;
}
Student.prototype = new Person("Tom", 20);
Student.prototype.study = function(){console.log(this.id + " are studying.")};
var stu = new Student("001");
stu.say(); // hello, my name is Tom
stu.study(); // 001 are studying.
</script>
(2)借用构造函数
在子类级别的构造函数中,通过call()来调用父类级别的构造函数,进行属性的继承和初始化。这种方式虽然解决了属性继承时的值固定问题,但是未继承父级类别中的方法。
- Function.prototype.call():
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
// thisArg:在 fun 函数运行时指定的 this 值。
// arg1, arg2, ...:指定的参数列表。
fun.call(thisArg, arg1, arg2, ...)
- 示例代码:
<script>
// 父类
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){console.log("hello, my name is " + this.name);};
// 子类
function Student(name, age, id){
this.id = id;
Person.call(this, name, age);
}
Student.prototype.study = function(){console.log(this.name + " are studying.")};
var stu = new Student("Bob", 18, "002");
stu.study(); // Bob are studying.
stu.say(); // 报错
</script>
(3)组合继承
通过改变原型引用和借用构造函数这两种组合的方式,来继承父级类别。
通过这种方式,虽然在子类的构造函数中继承了父类的属性,但显然存在了冗余。由于子类的原型对象指向父类的实例对象,所以子类的原型对象中也会有父类的属性(属性值为 undefined ),只是在子类实例对象访问父类属性时,优先访问了构造函数中继承的属性,所以可以说是一种假继承。
- 示例代码:
<script>
// 父类
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){console.log("hello, my name is " + this.name);};
// 子类
function Student(name, age, id){
this.id = id;
Person.call(this, name, age);
}
// 在改变原型引用时,不初始化实例对象的属性值
Student.prototype = new Person();
Student.prototype.study = function(){console.log(this.name + " are studying.")};
var stu = new Student("Bob", 18, "002");
stu.study(); // Bob are studying.
stu.say(); // hello, my name is Bob
console.dir(stu);
</script>
(4)拷贝继承
把父级对象中需要共享(原型对象中)的属性或者方法,复制到子级对象中。
- 示例代码:
<script>
// 父类
function Person(){}
Person.prototype.name = "Alice";
Person.prototype.age = 20;
Person.prototype.say = function(){console.log("hello, my name is " + this.name);};
// 子类
var obj = {};
// 循环遍历父类属性并复制到子类对象
for(var key in Person.prototype){
obj[key] = Person.prototype[key];
}
obj.say(); // hello, my name is Alice
</script>
三、综合案例——贪吃蛇
- 网页——1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.map{
width: 800px;
height: 600px;
background-color: lightblue;
position: relative;
}
</style>
</head>
<body>
<!-- 地图 -->
<div class="map"></div>
<script src="food.js"></script>
<script src="snake.js"></script>
<script src="Game.js"></script>
<script>
var gm = new Game(document.querySelector(".map"));
gm.init();
</script>
</body>
</html>
- 食物对象——food.js
// 食物对象
(function(){
// 保存食物的引用
var elements = [];
function Food(x,y,width,height,color){
this.x = x || 0;
this.y = y || 0;
this.width = width || 20;
this.height = height || 20;
this.color = color || "green";
}
// 在map上初始化食物
Food.prototype.init = function(map){
// 每次初始化之前,先删除以前的食物
remove();
// 创建div元素作为食物
var div = document.createElement("div");
map.appendChild(div);
div.style.width = this.width + "px";
div.style.height = this.height + "px";
div.style.backgroundColor = this.color;
div.style.position = "absolute";
// 随机产生x,y坐标
this.x = parseInt(Math.random() * (map.offsetWidth / this.width));
this.y = parseInt(Math.random() * (map.offsetHeight / this.height));
div.style.left = this.x * this.width + "px";
div.style.top = this.y * this.height + "px";
// 保存引用
elements.push(div);
}
// 删除食物
function remove(){
for(var i = elements.length - 1; i >= 0; i--){
var e = elements[i];
e.parentNode.removeChild(e);
elements.splice(i, 1);
}
}
// 暴露引用接口给window
window.Food = Food;
})();
- 小蛇对象——snake.js
// 蛇对象
(function(){
// 元素数组
var elements = [];
function Snake(width,height,direction){
this.width = width || 20;
this.height = height || 20;
this.body = [
{"x":3, "y":1, "color":"red"}, // 头
{"x":2, "y":1, "color":"yellow"},
{"x":1, "y":1, "color":"yellow"},
]
this.direction = direction || "right";
}
Snake.prototype.init = function(map){
// 初始化前,先删除
remove();
// 循环,每节身体创建一个div
for(var i = 0; i < this.body.length; i++){
var obj = this.body[i];
var div = document.createElement("div");
map.appendChild(div);
div.style.position = "absolute";
div.style.width = this.width + "px";
div.style.height =this.height + "px";
div.style.left = obj.x * this.width + "px";
div.style.top = obj.y * this.height + "px";
div.style.backgroundColor = obj.color;
if(i == 0){
div.style.zIndex = 1;
}
elements.push(div);
}
}
// 小蛇移动函数
Snake.prototype.move = function(food,map,timeId){
// 前一时刻身体的前一节位置,就是这节这时刻的位置
for(var i = this.body.length - 1; i > 0; i--){
this.body[i].x = this.body[i-1].x;
this.body[i].y = this.body[i-1].y;
}
// 头部的下一个位置
switch(this.direction){
case "up":
this.body[0].y--;
break;
case "down":
this.body[0].y++;
break;
case "left":
this.body[0].x--;
break;
case "right":
this.body[0].x++;
break;
}
// 如果吃到了食物
var headX = this.body[0].x;
var headY = this.body[0].y;
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);
}
// 判断是否撞到了身体
for(var i = 1; i < this.body.length; i++){
if(headX == this.body[i].x && headY == this.body[i].y){
clearInterval(timeId);
alert("You died, game over!");
}
}
}
// 删除函数
function remove(){
for(var i = elements.length - 1; i >= 0; i--){
elements[i].parentNode.removeChild(elements[i]);
elements.splice(i, 1);
}
}
// 暴露接口给外部
window.Snake = Snake;
})();
- 游戏对象——Game.js
// 游戏对象
(function (){
function Game(map){
this.snake = new Snake();
this.food = new Food();
this.map = map;
}
// 初始化游戏
Game.prototype.init = function(){
this.food.init(this.map);
this.snake.init(this.map);
this.runSnake();
this.bindKey();
}
// 小蛇自动前进
Game.prototype.runSnake = function(){
var that = this;
var timeId = setInterval(function(){
this.snake.move(this.food, this.map, timeId);
this.snake.init(this.map);
// 边界判断
var maxX = this.map.offsetWidth / this.snake.width;
var maxY = this.map.offsetHeight / this.snake.height;
var headX = this.snake.body[0].x;
var headY = this.snake.body[0].y;
if(headX < 0 || headX >= maxX || headY < 0 || headY >= maxY){
// 停止定时器
clearInterval(timeId);
alert("You died, game over!");
}
}.bind(that), 150);
}
// 小蛇方向控制(不能反向)
Game.prototype.bindKey = function(){
var that = this;
document.addEventListener("keydown", function(e){
var direction = this.snake.direction;
switch(e.keyCode){
case 37:
this.snake.direction = direction == "right"? direction:"left";
break;
case 38:
this.snake.direction = direction == "down"? direction:"up";
break;
case 39:
this.snake.direction = direction == "left"? direction:"right";
break;
case 40:
this.snake.direction = direction == "up"? direction:"down";
break;
}
}.bind(that), false)
}
window.Game = Game;
})();
参考链接: