深刻JavaScript(一)this & Prototype

注意:本文章是我的《You Don’t Know JS》的读书笔记。
在看backbone源码的时候看到这么一小段,看上去很小,其实忽略了也没有太大理解的问题。可是不知道为何,我以为内心很难受。因此我以为必定要真正解决这个问题。这个问题就是原型。
就是下面这段代码:git

var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;

看懂了这一段简单的代码了吗?github

其实就着backbone的注释,理解彻底没有问题。可是以后我不当心深究了一下,忽然发现本身对于这一个问题的理解还很不透彻。很惭愧,写了好一阵子的JavaScript,可是一直都是以实用主义的角度来写的。不少时候真的把JavaScript当成传统有class的语言来写了。可是JavaScript是一门很“狡猾”的语言,由于它的不少关键字仿佛都在透露本身是传统面向对象语言,instanceconstructornew等等。然而事实上并非。解决这个问题的方法就是读书,我选了《You Don’t Know JS》。我快速过了相关的那一本,被这本书深深震撼到。本文是我的的学习笔记,也但愿能帮助到你。chrome

this

this是函数执行的时候,内部产生的一个内部对象。在状况复杂的时候,this的指向会比较难把握。
this的指向经常不明,在书中主要列举了两种状况的混淆和四条规则,掌握了这二者,this就会比较明晰了。(还有不少很复杂的状况须要细致的分析)闭包

两种混淆

1,this不是函数对象自己。函数自己也是一个对象,可是给这个对象添加属性并不能影响this。除非foo.call(foo, i),这句代码的含义是:在foo对象上调用foo函数,并传递参数i。这是一个特殊状况。
2,this不是函数内部的做用域。所以在函数内部申明的变量若是不加其余操做,和this根本不会有任何关系。app

四条规则

这四条规则只有一个中心,就是call-site。本质上就是指this决定于调用的对象而不是在哪里声明。
1,直接调用函数。函数内部this会指向全局。this此时为window闭包调用亦是如此。
2,内部绑定调用。如obj.foo()。此时foo中的this指的就是obj对象。注意obj.foo(),无论前面是否是还有其余调用,可是都不会改变this此时为obj
3,直接绑定调用。主要是用了callapply来调用。除此以外还有先bind在调用的方式,这种方式很是强。直接绑定调用的this就是指定的那个参数的对象。
4,new方式调用var bar = new foo() 这一句代码的做用之一就是把foo函数中的this所有赋到bar上做为新的bar对象的属性。ide

优先级

1,通常来讲直接绑定调用会比内部绑定调用有更高优先级。
2,new在必定状况下能“覆盖”bind调用。用这个特性能够实现函数currying。(函数传入多个参数,在bind的时候传入固定的变量)函数

function foo(val) {
    this.val = val;
}
var obj = {};
var bar = foo.bind(obj);
bar("p1");
var baz = new bar("p2")
console.log(baz.val); // p2

new & Object.create

JavaScript没有类,也不能建立实例。可是语法上确实很是很是面向对象,让人误解。new就是总被误解。学习

var bar = new foo();

下面详述这一句代码执行的效果。这很是重要。
1,执行foo()函数。是“构造方式调用”。注意,JavaScript里面并无真正的构造函数。
2,链接foo.prototype和新对象bar[[prototype]]。(至关于把foo.prototype总体搬到(引用)bar.__proto__里面)
3,把foo中的内部对象this的属性给bar
4,foo若是有返回对象就返回那个对象,没有就自动返回一个新的对象。对象特征如2,3所述。
事实上,new作得事太多,不少时候使人讨厌。
注意:barfoo函数对象自己的属性没有关系。this

在经典的教材上,JavaScript的“继承”经常使用下面的代码:spa

Bar.prototype = new Foo();

这句代码很经典,可是并很差。它当然可以完成原型链的链接,可是也产生了不少没必要要的麻烦。Bar.prototype上会带上Foothis,反作用大。所以书上建议是这样作来完成继承的:

Bar.prototype = Object.create(Foo.prototype);

A = Object.create(B)的做用是把B对象的属性添加到A__proto__上面,而后A[[prototype]]B的链接。有个比较“直观”的说法,就是把B的“属性”,添加到A.__proto__上面,而后把B.__proto__也添加进去(原型链延长的不严谨说法)。试着在chrome里面console一下,就会发现此时的A只有一个__proto__属性,里面是B的属性和B.__proto__。这样就很好地完成了“继承”,并且也没有反作用了。下面提供一个降级写法(原理很简单。按照以前的几条规则能够很轻易地读懂):

// Object.create
if(!Object.create) {
    Object.create = function(o) {
        function F(){};
        F.prototype = o;
        return new F();
    }
}

还须要注意的是constructor属性。若是对象是函数,这个属性会自动出如今函数的prototype里面,但有时候会被覆盖。


原型

prototype是函数对象的一个属性。其实能够理解成比较特殊的一个属性,一个对象。这个属性在用构造器方式调用(new)的时候就用来链接新对象。链接到最后就是Object.prototype,这是每个对象(不包括null)中__proto__的尽头,原型链的尽头。能够想象,对象的声明是new Object(),那么新的对象原型链天然就会有Object.prototype了。若是想摆脱这个Object.prototype,能够用Object.create(null)
常常有这种状况,调用一个对象的属性的时候,若是在自己找不到就会沿着原型链找。直观而不严谨地说,就是从__proto__一直找下去。这也揭露了a instanceof foo的本质。就是沿着原型链找,看看在a的原型链上有没有foo.prototype的存在,有就返回trueinstanceof这个英文很误导,由于JavaScript里面根本上就算不上有实例这样的东西。


OLOO

做者对于JavaScript的面向对象有一个很是惊艳的解决方案,在那以前先看看之前的方案是怎么作的。

function Foo(who) {
    this.me = who;
}
Foo.prototype.identify = function() {
    return "I am " + this.me;
};

function Bar(who) {
    Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};

var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );

b1.speak();
b2.speak();

一图胜千言:
图片描述


做者给出的解决方案OLOOobjects-linked-to-other-objects),没有麻烦的new,没有虚伪的constructor,没有混淆视线的call, apply, bind,原型链链接再也不赤裸裸。感受真心很棒(注意一点,JavaScript对象的方法实际上是不是“属于”一个对象值得商榷,由于所谓方法其实和这个对象关系并无想象中大):

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create( Foo );

Bar.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};

var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );

b1.speak();
b2.speak();

仍然是一图胜千言:
图片描述


最后来说讲这个:

var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;

很好懂了吧。构造形式调用Surrogate函数,把this上的属性交给child.prototype,这里是constructor;把Surrogate.prototype的内容搬到child.prototype.__proto__上面。而Surrogate.prototype引用自parent.prototype。因此child是这样的一个函数:以child为构造函数,parent.prototype为原型。

总结:

这个问题是老生常谈的问题了,可是我以为对于每个写JavaScript的人来讲,这个问题是永远避不开的。最后再次推荐《You Don’t Know JS》这一套开源书。能够绝不夸张地说,这是自红宝书和犀牛书以后讲JavaScript的最好书了。深度深得恰到好处,语言和例子清晰简明,我还要继续学习。

有关 this & Object Prototypes书的内容远远比我这篇文章丰富精彩,去读吧。(我也要复反复读)

若是有任何错误请轻喷,相互学习~