JavaScript原型继承与函数四种调用模式

原型对象是JavaScript中一个重要概念,函数四种调用模式更是变化多端,JavaScript中继承依赖于原型对象,函数四种调用模式区别在于this。

在各个语言相继倒在了浏览器这块战场上以后,JavaScript似乎迎来了为自己证明的绝佳机会。各种大型应用都相继开始拥抱JavaScript,事实再一次证明,属于JavaScript的时间表还未真正到来。直到《JavaScript语言精粹》一书问世,业界似乎才体会到如何正确运用JavaScript。

JavaScript原型链

每个JavaScript对象都有一个特殊的属性prototype,这个属性的值保存着一个对象。这个对象称为原型对象。之所称之为原型对象,不是对象自身决定,而是它所处的位置,所有保存在prototype属性上的对象都可以称之为原型对象。

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

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

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

给函数fun的原型对象添加一个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行为的继承。

JavaScript方法调用模式

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

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

JavaScript函数调用模式

当一个函数并非对象属性时,就会当作函数被调用:

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

此时的this被绑定到全局对象上,也就是说,两个不同函数之间虽然没什么关系,但他们的this都是相同的,这就有可能导致变量的全局污染。

JavaScript构造器调用模式

在实现继承方面JavaScript对外宣称是基于原型的继承方式,而在实际使用中却没这么简单,它还增加了一个间接的构造层来完成继承关系。简单的原型声明继承关系还不够,还需要一个构建动作来完成继承的事实,这就是构造器调用模式存在的原因。

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

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

var obj = new Clazz('Lee');

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

JavaScript 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