JavaScript原型与函数调用

JavaScript原型对象及函数四种调用模式。

《JavaScript语言精粹》一书问世,业界似乎才明白如何正确运用JavaScript。

原型链

JavaScript对象都有一个特殊属性prototype,prototype属性指向一个对象,称为原型对象。

var fun = function(){};
fun.prototype = 原型对象

原型对象上的属性,对持有这个原型的对象都可见。

var fun = function(){};
fun.prototype.name = 'Lee';
new fun().name; //Lee

示例中原型对象添加一个name属性,使用new关键字创建对象。

fun.prototype = {};

原型对象特性

更新操作不会影响到原型连接上的属性。如:

f.name='Rain'

原型的name值不会改变,只会在f对象上新增name属性。

delete操作不会删除原型的属性。

delete f.name //不影响原型属性

可见性

读取一个属性时会从当前对象上找,未找到,沿着对象原型链接查找原型对象上是否存在同名属性。

给所有类型的对象添加一个方法,在Object.prototype原型上添加即可。

Object.prototype.hello=function(){
    console.log('say hi');
}
"".hello(); //say hi

让一个方法对所有函数都可见,在Function.prototype原型上添加即可。

Function.prototype.goodbye=function(){
    console.log('say goodbye');
}
(function(){}).goodbye();//say goodbye

动态特性

原型关系是一种动态关系。对原型添加新的属性,基于该原型的对象立即可见。

排除原型属性

hasOwnProperty可以判断属性是当前对象,还是来自原型。

f.hasOwnProperty('name')

函数的原型对象

函数的原型连接到Function.prototype,Function.prototype的原型连接到Object.prototype。

函数对象创建时附带两个隐藏属性。函数对象在创建时附带一个prototype属性,属性值是一个对象,对象上有一个constructor属性,constructor值为当前函数对象。类似如下代码:

this.prototype = {constructor: this};

原型实现继承

函数对象创建时会被赋予一个prototype属性,prototype属性指向一个包含constructor属性的对象。prototype属性是继承的关键所在,constructor属性意义不大。

new关键字调用函数时,函数执行方式有所变化。大致执行逻辑如下:

function(){
    var that = Object.create(this.prototype);
    this.apply(that,arguments);
    return typeof (other === 'object' && other) || that;
}

先会创建一个对象that,并将函数对象的原型,作为新对象that的原型。

接着调用函数(),将新对象that作为函数的this指针传入。

若函数返回值不是一个对象,则将新创建的that对象作为返回值。验证代码如下:

var con = function(){
    this.a = 2;
    return {a:1};
}
new con().a; //1

var con2 = function(){
    this.a = 2;
    {a:1};
}
new con2().a; //2

原型继承实现

如下示例中给Animal定义一个特性says,Panda利用原型继承,让says行为具体化。

var Animal = function(name){
    this.name = name;
}

Animal.prototype.says=function(){
    return (this.name ? this.name + ' say: ' : '') + (this.saying|| '...');
}

var Panda = function(){    this.name = 'Panda';
    this.saying = 'ha ha ~'
}

Panda.prototype = new Animal();

new Panda().says();
//"Panda say: ha ha ~"

原型继承优化

JavaScript伪类型模式本意向面向对象靠拢,真实实现并不优雅。jQuery等前端框架,都有自己的继承实现。两步优化js原型继承。

Function.prototype.addMethod=function(name,func){
    this.prototype[name]=func;
    return this;
}

函数原型对象增加一个方法,方便向原型中添加方法。

Function.addMethod('ext',function(Parent){
    this.prototype=new Parent();
    return this;
});

利用新添加的方法,向原型对象添加一个简化版的继承实现:

var Animal = function(name){
    this.name = name;
}

Animal.addMethod('says',function(){
      return (this.name ? this.name + ' say: ' : '') + (this.saying|| '...');
});

var Panda = function(){
    this.name = 'Panda';
    this.saying = 'ha ha ~'
}

Panda.ext(Animal);

new Panda().says();

//"Panda say: ha ha ~"

示例中向Animal添加一个says方法,使用继承完成Panda对Animal行为继承。

方法调用模式

函数作为一个对象属性值出现时,被称为方法。方法调用时,this被绑定到该对象上。

var obj = {
      name:'Lee',
      say:function(){
           alert(this.name);
       }
}
obj.say();

函数调用模式

函数不做为对象属性时,会当作函数被调用:

var name = 'Lee';
(function(){
    alert(this.name);
})();

this被绑定到全局对象上,两个不同函数虽然没什么关系,但他们的this是相同的,这就有可能引起变量的全局污染。

构造器调用模式

JavaScript对外宣称是基于原型的继承,在实际使用中却不简单,有一个间接的构造层完成继承关系。

简单声明原型继承关系还不够,需要执行一个构建动作完成继承事实,这就是构造器调用模式存在的原因。

var Clazz = function(n){
     this.name = n;
}

Clazz.prototype.getName = function(){
     return this.name;
}

var obj = new Clazz('Lee');

alert(obj.getName());//Lee

Apply调用模式

JavaScript函数式面向对象的概念,致使函数可以拥有方法。函数的apply方法,允许选择一个对象作为函数的this。

apply也接受多个参数,填充函数的形参列表(示例中没有):

var a = {name:'Lee'};
var b = {name:'Rain'};

var c  = function(){
    return this.name;
}

alert(c.apply(a));//Lee
alert(c.apply(b));//Rain