一談到this,很多讓人暈暈乎乎的抽象概念就跑出來了,這裡我就只說最核心的一點——函數中的this總指向調用它的對象,接下來的故事都將圍繞著這一點。
【故事】有一個年輕人叫"迪斯"(this),有一天,迪斯不小心穿越到一個叫「伽瓦斯克利」(javascript)的異世界,此時此刻迪斯身無分文, 他首先要做的事情就是-找到他的住宿的地方-呼叫函數的物件
this的預設綁定
#【故事-線路1】如果迪斯(this)直到天黑前都沒有找到能收留自己的住所,他眼看就要過上非洲難民的生活, 這時候,一位樂善好施的魔法師村長——window救世主一般地出現了:先住在我家吧!
【正文】
當一個函數沒有明確的呼叫物件的時候,也就是單純作為獨立函數呼叫的時候,將對函數的this使用預設綁定:綁定到全域的window物件
function fire () {
console.log(this === window)
}
fire(); // 输出true
登入後複製
上面的例子我相信對大多數人都很簡單,但有的時候我們把例子變一下就會具有迷惑性:
function fire () {
// 我是被定义在函数内部的函数哦! function innerFire() {
console.log(this === window)
}
innerFire(); // 独立函数调用}
fire(); // 输出true
登入後複製
函數innerFire在一個外部函數fire裡面宣告且調用,那麼它的this是指向誰呢? 仍然是window
許多人可能會顧慮於fire函數的作用域對innerFire的影響,但我們只要抓住我們的理論武器——沒有明確的調用對象的時候,將對函數的this使用預設綁定:綁定到全域的window對象,便可得正確的答案了
下面這個加強版的例子也是同樣的輸出true
var obj = {
fire: function () {
function innerFire() {
console.log(this === window)
}
innerFire(); // 独立函数调用 }
}
obj.fire(); //输出 true
登入後複製
#
【注意】在這個例子中,obj .fire()的呼叫其實使用到了this的隱式綁定,這就是下面我要講的內容,這個例子我接下來還會繼續講解
【總結】 凡事函數作為獨立函數調用,無論它的位置在哪裡,它的行為表現,都和直接在全局環境中調用無異
this的隱式綁定
【故事-線路2】 迪斯(this)穿越來異世界「伽瓦斯克利」(javascript)的時候,剛好身上帶了一些錢,於是他找到一個旅館住宿了下來
當函數被一個物件「包含」的時候,我們稱函數的this被隱式綁定到這個物件裡面了,這時候,透過this可以直接存取所綁定的物件裡面的其他屬性,例如下面的a屬性
var obj = {
a: 1,
fire: function () {
console.log(this.a)
}
}
obj.fire(); // 输出1
登入後複製
現在我們需要對平常司空見慣的的程式碼操作做一些更深的思考,首先,下面的這兩段程式碼達到的效果是相同的:
#
// 我是第一段代码function fire () {
console.log(this.a)
}
var obj = {
a: 1,
fire: fire
}
obj.fire(); // 输出1
// 我是第二段代码var obj = {
a: 1,
fire: function () {
console.log(this.a)
}
}
obj.fire(); // 输出1
登入後複製
#
var obj = {
a: 1, // a是定义在对象obj中的属性 1 fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定义在全局环境中的变量 2var fireInGrobal = obj.fire;
fireInGrobal(); // 输出 2
登入後複製
登入後複製
## #### ###### ###fire函數並不會因為它被定義在obj物件的內部和外部而有任何區別,也就是說在上述隱式綁定的兩種形式下,fire透過this還是可以存取obj內的a屬性,這告訴我們:### ###1. this是動態綁定的,或者說是在程式碼運行期綁定而不是在書寫期###### #2. 函數於物件的獨立性,this 的傳遞遺失問題###
(下面的描述可能带有个人的情感倾向而显得不太严谨,但这是因为我希望阅读者尽可能地理解我想表达的意思)
隐式绑定下,作为对象属性的函数,对于对象来说是独立的
基于this动态绑定的特点,写在对象内部,作为对象属性的函数,对于这个对象来说是独立的。(函数并不被这个外部对象所“完全拥有”)
我想表达的意思是:在上文中,函数虽然被定义在对象的内部中,但它和“在对象外部声明函数,然后在对象内部通过属性名称的方式取得函数的引用”,这两种方式在性质上是等价的(而不仅仅是效果上)
定义在对象内部的函数只是“恰好可以被这个对象调用”而已,而不是“生来就是为这个对象所调用的”
借用下面的隐式绑定中的this传递丢失问题来说明:
var obj = {
a: 1, // a是定义在对象obj中的属性 1 fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定义在全局环境中的变量 2var fireInGrobal = obj.fire;
fireInGrobal(); // 输出 2
登入後複製
登入後複製
上面这段简单代码的有趣之处在于: 这个于obj中的fire函数的引用( fireInGrobal)在调用的时候,行为表现(输出)完全看不出来它就是在obj内部定义的,其原因在于:我们隐式绑定的this丢失了!! 从而 fireInGrobal调用的时候取得的this不是obj,而是window
上面的例子稍微变个形式就会变成一个可能困扰我们的bug:
var a = 2;var obj = {
a: 1, // a是定义在对象obj中的属性 fire: function () {
console.log(this.a)
}
}
function otherFire (fn) {
fn();
}
otherFire(obj.fire); // 输出2
登入後複製
在上面,我们的关键角色是otherFire函数,它接受一个函数引用作为参数,然后在内部直接调用,但它做的假设是参数fn仍然能够通过this去取得obj内部的a属性,但实际上, this对obj的绑定早已经丢失了,所以输出的是全局的a的值(2),而不是obj内部的a的值(1)
在一串对象属性链中,this绑定的是最内层的对象
在隐式绑定中,如果函数调用位置是在一串对象属性链中,this绑定的是最内层的对象。如下所示:
var obj = {
a: 1,
obj2: {
a: 2,
obj3: {
a:3,
getA: function () {
console.log(this.a)
}
}
}
}
obj.obj2.obj3.getA(); // 输出3
登入後複製
this的显式绑定:(call和bind方法)
【故事——线路3】 迪斯(this)穿越来异世界“伽瓦斯克利”(javascript),经过努力的打拼,积累了一定的财富,于是他买下了自己的房子
上面我们提到了this的隐式绑定所存在的this绑定丢失的问题,也就是对于 “ fireInGrobal = obj.fire”
fireInGrobal调用和obj.fire调用的结果是不同的,因为这个函数赋值的过程无法把fire所绑定的this也传递过去。这个时候,call函数就派上用场了
call的基本使用方式: fn.call(object)
fn是你调用的函数,object参数是你希望函数的this所绑定的对象。
fn.call(object)的作用:
1.即刻调用这个函数(fn)
2.调用这个函数的时候函数的this指向object对象
例子:
var obj = {
a: 1, // a是定义在对象obj中的属性 fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定义在全局环境中的变量 var fireInGrobal = obj.fire;
fireInGrobal(); // 输出2fireInGrobal.call(obj); // 输出1
登入後複製
原本丢失了与obj绑定的this参数的fireInGrobal再次重新把this绑回到了obj
但是,我们其实不太喜欢这种每次调用都要依赖call的方式,我们更希望:能够一次性 返回一个this被永久绑定到obj的fireInGrobal函数,这样我们就不必每次调用fireInGrobal都要在尾巴上加上call那么麻烦了。
怎么办呢? 聪明的你一定能想到,在fireInGrobal.call(obj)外面包装一个函数不就可以了嘛!
var obj = {
a: 1, // a是定义在对象obj中的属性 fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定义在全局环境中的变量 var fn = obj.fire;var fireInGrobal = function () {
fn.call(obj) //硬绑定}
fireInGrobal(); // 输出1
登入後複製
如果使用bind的话会更加简单
var fireInGrobal = function () {
fn.call(obj) //硬绑定}
登入後複製
可以简化为:
var fireInGrobal = fn.bind(obj);
登入後複製
call和bind的区别是:在绑定this到对象参数的同时:
1.call将立即执行该函数
2.bind不执行函数,只返回一个可供执行的函数
【其他】:至于apply,因为除了使用方法,它和call并没有太大差别,这里不加赘述
在这里,我把显式绑定和隐式绑定下,函数和“包含”函数的对象间的关系比作买房和租房的区别。
因为this的缘故
在隐式绑定下:函数和只是暂时住在“包含对象“的旅馆里面,可能过几天就又到另一家旅馆住了
在显式绑定下:函数将取得在“包含对象“里的永久居住权,一直都会”住在这里“
new绑定
【故事】 迪斯(this)组建了自己的家庭,并生下多个孩子(通过构造函数new了许多个对象)
执行new操作的时候,将创建一个新的对象,并且将构造函数的this指向所创建的新对象