0%

原型模式

假设我们在编写一个飞机大战的网页游戏。某种飞机拥有分身技能,当它使用分身技能的时 候,要在页面中创建一些跟它一模一样的飞机。如果不使用原型模式,那么在创建分身之前,无 疑必须先保存该飞机的当前血量、炮弹等级、防御等级等信息,随后将这些信息设置到新创建的 飞机上面,这样才能得到一架一模一样的新飞机。如果使用原型模式,我们只需要调用负责克隆的方法,便能完成同样的功能。

js中调用 Object.create()即可克隆对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name){
this.name=name
}
Person.prototype.say = function(){
console.log(this.name);
}
var me = new Person('ltinyho')
var myClone = Object.create(me)
myClone.say();
var myClone2 = {}
myClone2.__proto__ = me;
myClone2.say();

原型模式一定有一个根对象,其他的对象一定是从某个已经存在的对象生成的,然后在添加它自己的属性和方法。在这些对象上就会形成一条原型链,当调用某个对象的方法时,如果在它本身没有这个方法,那么就会从原型链一直向上找,直到根对象为止。
js中的根对象为Object.prototype,它是这个空对象。

1
2
var a = {}
Object.getPrototypeOf(a)===Object.prototype // true

构造函数

  • 创建一个对象
  • 将构造函数的作用域赋给新对象。(因此this指向了新对象)
  • 执行构造函数中的代码。(为这个新对象添加属性)
  • 如果不调用return ,默认返回这个对象,显式的return一个对象会影响结果,返回原始值不会。

对象的原型链是如何查找的呢?
要完成查找,每个对象首先至少得记住自己的原型,知道自己从哪里来的。js 给对象提供了一个名为__proto__的隐藏属性,某个对象的 __proto__属性默认指向它的构造器原型对象,即 {Constructor}.prototype ,当在自己找不到属性时,就会到这个对象上查找。但是一个对象的 Constructor 可能会被修改。原因是 js中每个对象都是从 Object.prototype 对象克隆而来的,如果这样的话,只能得到单一的继承关系,即每个对象都是继承于 Object.protoype。但是对象构造器的原型并不仅限于 Object.prototype 上,而是可以动态的指向其他对象。看一下以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function A(){
}
A.prototype.name= 'A'
A.prototype.say = function(){
console.log(this.name);
}
function B(){
}
var a = new A()
B.prototype = a;
var b= new B()
console.log(b.name);
b.__proto__===B.prototype // true
  • 首先遍历 b 上的所有属性,没有找到 name 。
  • 查找 name 属性的请求被委托给 b 的 b.__proto__b.__proto__记录了构造器原型 B.prototype,而B.prototype指向一个通过 new A() 创建的对象。
  • 然后就到 a 的 属性上遍历,还是没有。然后到a.__proto__记录的 A.prototype 上找的构造器原型上找,找到了。

注意:当在本身查找不到时,是先到 __proto__ 上去找, 如果一个对象已经创建,__proto__ 确定了,这时改变 这个对象的 constructor 并不会改变原型链。

this, call, apply, bind

JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
this的指向大致分为四种

  • 对象的方法调用
  • 普通函数调用
  • 构造函数调用
  • Function.prototype.call和Function.prototype.apply调用

call和apply

call和apply可以修改函数内部的this指向,它们的区别只在于参数的不同。第一个参数都为this指向的对象,第二个参数call接收一个参数,apply则以数组的形式接收。

call和apply的用途

  • 改变this的指向
  • 实现bind方法
  • 借用其他对象的方法

bind的实现

1
2
3
4
5
6
Function.prototype.bind = function(){ 
var self = this, context = [].shift.call( arguments ),args = [].slice.call( arguments );
return function(){
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
}
};

从上面可以看出,bind 会返回一个新的函数,并且可以传入参数,作为新生成函数的预置的参数。