Douglas Crockford大师把this古怪的行为解释为和函数的调用模式有关。
js中有四种调用模式---方法、函数、构造器以及apply调用模式,它们在初始化this时候是不一样的。
1)方法模式 简单的说就是使用点表达式或是下标表达式来调用 这里定然是有对象的 这种情况下 this的绑定发生在调用的时候 绑的自然是调它的那个对象了。
作为方法调用的函数有一个非常重要的属性:在方法体中,用来调用方法的对象成为关键字this的值。
2)函数模式 这个就更简单了,函数名加调用运算符('()')。不过要小心,这个this绑的可是全局对象,不管你写哪了。(可以理解成 你不给我指明了 我就自己给它加个全局对象)
当一个函数作为函数调用而不是方法调用时,这个this关键字引用全局对象。容易混淆的是,当一个嵌套的函数(作为函数)在一个包含的函数中调用,而这个包含的函数是作为方法调用的,这也是成立的:this关键字在包含的函数中有一个值,但是它却(不太直观地)引用嵌套的函数体的内部的全局对象。
看个例子吧
var a = 'global'
var obj = {
a : 'local',
test : function(){
function test1(){
alert(this.a)//global
}
alert(this.a)//local
test1()
}
}
obj.test()
/*****************************解决这个问题的方法*****************************/
var a = 'global'
var obj = {
a : 'local',
test : function(){
var that = this
function test1(){
alert(that.a)//local
}
alert(this.a)//local
test1()
}
}
obj.test()
3)构造器调用模式 一句话就是用new来调用的 new的时候this就绑定到新对象上了 比较好理解
(1)new运算符后面必须跟着一个函数调用。new创建了一个新的没有任何属性的对象,然后调用该构造函数,把新的对象作为this关键字的值传递。
(2)构造函数通常没有返回值。它们初始化作为this的值来传递的对象,并且没有返回值。但一个构造是允许返回一个对象值,并且如果它这么做,返回的对象成为new表达式的值。在此情况下,作为this的值的对象会被抛弃。可参考jQuery构造函数。
4)apply,call调用模式 apply,call是函数对象的方法,你想把谁绑定到this 就直接把它作为第一个参数传给apply或call就好了。
ECMAScript规范给所有函数定义了两个方法call()和apply(),使用这两个方法可以像调用其他对象的方法一样调用函数。call()和apply()的第一个参数都是要调用的函数的对象,在函数体内这一参数是关链字this的值。call()的剩余参数是传递给要调用的函数的值。
现在写一个函数,这个函数的返回值是一个对象,来观察一下这个对象,和函数体内this指向
①,对象a就是一个空对象
②,对象a没有color属性
③,对象a原型指向Object.prototype
④,函数体内this指向window(上图可见,没调用函数之前,window.color属性不存在)
在执行函数时,加一个new试试看有啥效果:
①,没有return,这个函数居然返回了一个对象
②,对象a有个color属性
③,对象a的原型指向了这个构造函数的prototype属性
④,window对象没有color属性
⑤,this指向了对象a
由上得出总结new的作用:
①,new能让一个构造函数返回一个对象(下面称为A)而无需return
②,new会让函数体内的this指向这个A
③,new会让A的原型指向这个构造函数的prototype属性
PS:
①,new只用了三个字符,却实现了很多功能,非常贴心,可以称呼new为语法糖或贴心语法( syntactic sugar )
②,一个函数自带prototype对象,而且里面默认有一个constructor属性,这个属性返回实例对象的构造函数,如果你重新给prototype对象赋值,小心不要覆盖掉它
③,如果使用new,函数内就别 return一个对象 了 ,这样返回的是return后面的对象,this不会指向它
④,就算函数体内空空如也,new的作用不变
js中的函数有两种调用方式,一种是通过普通的声明之后进行的调用。一种是通过new关键字进行构造调用。普通的调用就是依次执行函数内部的函数语句,如果有返回值则返回返回值,如果没有则函数内部的声明周期结束。但是,函数还有另一个调用方式,使用new关键字,将函数当做构造函数。js中没有所谓独立出来的构造函数的概念,所有的函数都用同样的方式声明,所以有了new这个关键字,js(ES6之前)只能通过这种方式实现构造器的构造。那么使用new关键字跟普通的调用有什么区别呢?
使用new关键字,比普通的函数调用,主要分为以下四个步骤:
稍后我们会对上面的四句话逐一解析,首先我们看一看函数调用的两种方式:
这样看来,两者似乎没有区别,但是这里要注意,使用new进行构造调用时,函数是有返回值的。
上面的return2并没有返回一个函数。
其实它会返回一个空的对象。这也就是上面使用new关键词的第一步, 内部创建一个新的空对象 。
那么当函数内部有this时,结果会是怎样的呢?
使用new关键字后,在函数内部如果出现了this,则自动将this指向内部新建的对象上。最后返回时,因为this的缘故,对象上新建了a属性,并且赋值返回。
修正定义的对象Object的[[prototype]]
虽然实例上的[[prototype]]属性__proto__是ES6才作为规范出版的。但是在这之前chrome已经支持__proto__属性,他指向对象的原型。
原型的问题相当复杂,单独拿出来也可以当好几篇文章的量来讲。但是这并不是本文的重点。但是每一个对象从根部来说,继承自Object。而Object.prototype上面定义了一些方法,有类似toString,valueOf等等等方法。对于对象来说,支持通过属性链和方法链向上查询。所以在一个对象实例中,如果没有定义toString方法,但它还可以向上查询,找到原型中的toString方法,进行调用。
同时的,有很多元素通过Object实现继承。比如Function, Array, RegExp等等对象,它们也是对象,但是却是继承来自Object。
在这里,内部定义的对象,让他继承来自构造函数。
最后一步,也是最容易被忽略的一步,那就是当构造函数存在返回值时,并且返回值为对象时,返回对象而不返回之前定义的对象。
当然,上面说的Function,Array,RegExp也算Object的一种,如果返回他们同样也会阻止默认的对象返回。