成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

一篇文章帶你完全理解this

Jenny_Tong / 3094人閱讀

摘要:追夢子追夢子通過在方法,給第一個參數(shù)添加要把添加到哪個環(huán)境中,簡單來說,就會指向那個對象。追夢子追夢子還有一點就是雖然也是對象,但是在這里還是指向那個函數(shù)的實例,因為比較特殊。追夢子追夢子在嚴格版中的默認的不再是,而是。

走在前端的大道上

本篇將自己讀過的相關(guān) this指向 的文章中,對自己有啟發(fā)的章節(jié)片段總結(jié)在這(會對原文進行刪改),會不斷豐富提煉總結(jié)更新。

版本一 一句話
this的指向在函數(shù)定義的時候是確定不了的,只有函數(shù)執(zhí)行的時候才能確定this到底指向誰,實際上this的最終指向的是那個調(diào)用它的對象(這句話有些問題,后面會解釋為什么會有問題,雖然網(wǎng)上大部分的文章都是這樣說的,雖然在很多情況下那樣去理解不會出什么問題,但是實際上那樣理解是不準確的,所以在你理解this的時候會有種琢磨不透的感覺)  —— ——徹底理解js中this的指向,不必硬背。
5大規(guī)則 (1)構(gòu)造函數(shù)模式的時候,this指向新生成的實例
function Aaa(name){
  this.name= name;
  this.getName=function(){
    console.log(this.name)
  }
}
var a = new Aaa("kitty");
a.getName()        //  "kitty"
var b = new Aaa("bobo");
b.getName()        //  "bobo"

如果 new 關(guān)鍵詞出現(xiàn)在被調(diào)用函數(shù)的前面,那么JavaScript引擎會創(chuàng)建一個新的對象,被調(diào)用函數(shù)中的this指向的就是這個新創(chuàng)建的函數(shù)。

function ConstructorExample() {
    console.log(this);
    this.value = 10;
    console.log(this);
}

new ConstructorExample();

// -> ConstructorExample {}
// -> ConstructorExample { value: 10 }

構(gòu)造函數(shù)版this:

function Fn(){
    this.user = "追夢子";
}
var a = new Fn();
console.log(a.user); //追夢子

  這里之所以對象a可以點出函數(shù)Fn里面的user是因為new關(guān)鍵字可以改變this的指向,將這個this指向?qū)ο骯,為什么我說a是對象,因為用了new關(guān)鍵字就是創(chuàng)建一個對象實例,我們這里用變量a創(chuàng)建了一個Fn的實例(相當于復制了一份Fn到對象a里面),此時僅僅只是創(chuàng)建,并沒有執(zhí)行,而調(diào)用這個函數(shù)Fn的是對象a,那么this指向的自然是對象a,那么為什么對象a中會有user,因為你已經(jīng)復制了一份Fn函數(shù)到對象a中,用了new關(guān)鍵字就等同于復制了一份。

(2)apply/call調(diào)用模式的時候,this指向apply/call方法中的第一個參數(shù)
var list1 = {name:"andy"}
var list2 = {name:"peter"}

function d(){
  console.log(this.name)
}
d.call(list1)     //  "andy" 
d.call(list2)     //  "peter" 

如果通過apply、call或者bind的方式觸發(fā)函數(shù),那么函數(shù)中的this指向傳入函數(shù)的第一個參數(shù)。

function fn() {
    console.log(this);
}

var obj = {
    value: 5
};

var boundFn = fn.bind(obj);

boundFn(); // -> { value: 5 }
fn.call(obj); // -> { value: 5 }
fn.apply(obj); // -> { value: 5 }

在沒有學之前,通常會有這些問題。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b(); //undefined

我們是想打印對象a里面的user卻打印出來undefined是怎么回事呢?如果我們直接執(zhí)行a.fn()是可以的。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
a.fn(); //追夢子

雖然這種方法可以達到我們的目的,但是有時候我們不得不將這個對象保存到另外的一個變量中,那么就可以通過以下方法。

1、call()

 

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
var b = a.fn;
b.call(a);

通過在call方法,給第一個參數(shù)添加要把b添加到哪個環(huán)境中,簡單來說,this就會指向那個對象。

call方法除了第一個參數(shù)以外還可以添加多個參數(shù),如下:

var a = {
    user:"追夢子",
    fn:function(e,ee){
        console.log(this.user); //追夢子
        console.log(e+ee); //3
    }
}
var b = a.fn;
b.call(a,1,2);
2、apply()

apply方法和call方法有些相似,它也可以改變this的指向

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
var b = a.fn;
b.apply(a);

同樣apply也可以有多個參數(shù),但是不同的是,第二個參數(shù)必須是一個數(shù)組,如下:

var a = {
    user:"追夢子",
    fn:function(e,ee){
        console.log(this.user); //追夢子
        console.log(e+ee); //11
    }
}
var b = a.fn;
b.apply(a,[10,1]);

或者

var a = {
    user:"追夢子",
    fn:function(e,ee){
        console.log(this.user); //追夢子
        console.log(e+ee); //520
    }
}
var b = a.fn;
var arr = [500,20];
b.apply(a,arr);

//注意如果call和apply的第一個參數(shù)寫的是null,那么this指向的是window對象

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this); //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…}
    }
}
var b = a.fn;
b.apply(null);
3、bind()

bind方法和call、apply方法有些不同,但是不管怎么說它們都可以用來改變this的指向。

先來說說它們的不同吧。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b.bind(a);

我們發(fā)現(xiàn)代碼沒有被打印,對,這就是bind和call、apply方法的不同,實際上bind方法返回的是一個修改過后的函數(shù)。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
var c = b.bind(a);
console.log(c); //function() { [native code] }

那么我們現(xiàn)在執(zhí)行一下函數(shù)c看看,能不能打印出對象a里面的user

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
var b = a.fn;
var c = b.bind(a);
c();

ok,同樣bind也可以有多個參數(shù),并且參數(shù)可以執(zhí)行的時候再次添加,但是要注意的是,參數(shù)是按照形參的順序進行的。

var a = {
    user:"追夢子",
    fn:function(e,d,f){
        console.log(this.user); //追夢子
        console.log(e,d,f); //10 1 2
    }
}
var b = a.fn;
var c = b.bind(a,10);
c(1,2);

總結(jié):call和apply都是改變上下文中的this并立即執(zhí)行這個函數(shù),bind方法可以讓對應的函數(shù)想什么時候調(diào)就什么時候調(diào)用,并且可以將參數(shù)在執(zhí)行的時候添加,這是它們的區(qū)別,根據(jù)自己的實際情況來選擇使用。

(3)方法調(diào)用模式的時候,this指向方法所在的對象
var a={};
a.name = "hello";
a.getName = function(){
  console.log(this.name)
}
a.getName()         //"hello"

如果一個函數(shù)是某個對象的方法,并且對象使用句點符號觸發(fā)函數(shù),那么this指向的就是該函數(shù)作為那個對象的屬性的對象,也就是,this指向句點左邊的對象。

var obj = {
    value: 5,
    printThis: function() {
      console.log(this);
    }
};

obj.printThis(); // -> { value: 5, printThis: ? }

由淺入深

例子1:
function a(){
    var user = "追夢子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

按照我們上面說的this最終指向的是調(diào)用它的對象,這里的函數(shù)a實際是被Window對象所點出來的,下面的代碼就可以證明。

function a(){
    var user = "追夢子";
    console.log(this.user); //undefined
    console.log(this);  //Window
}
window.a();

和上面代碼一樣吧,其實alert也是window的一個屬性,也是window點出來的。

例子2:
var o = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);  //追夢子
    }
}
o.fn();

  這里的this指向的是對象o,因為你調(diào)用這個fn是通過o.fn()執(zhí)行的,那自然指向就是對象o,這里再次強調(diào)一點,this的指向在函數(shù)創(chuàng)建的時候是決定不了的,在調(diào)用的時候才能決定,誰調(diào)用的就指向誰,一定要搞清楚這個。

其實例子1和例子2說的并不夠準確,下面這個例子就可以推翻上面的理論。

如果要徹底的搞懂this必須看接下來的幾個例子

例子3:
var o = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
window.o.fn();

  這段代碼和上面的那段代碼幾乎是一樣的,但是這里的this為什么不是指向window,如果按照上面的理論,最終this指向的是調(diào)用它的對象,這里先說個而外話,window是js中的全局對象,我們創(chuàng)建的變量實際上是給window添加屬性,所以這里可以用window點o對象。

  這里先不解釋為什么上面的那段代碼this為什么沒有指向window,我們再來看一段代碼。

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();

  這里同樣也是對象o點出來的,但是同樣this并沒有執(zhí)行它,那你肯定會說我一開始說的那些不就都是錯誤的嗎?其實也不是,只是一開始說的不準確,接下來我將補充一句話,我相信你就可以徹底的理解this的指向的問題。

  - 情況1:如果一個函數(shù)中有this,但是它沒有被上一級的對象所調(diào)用,那么this指向的就是window,這里需要說明的是在js的嚴格版中this指向的不是window,但是我們這里不探討嚴格版的問題,你想了解可以自行上網(wǎng)查找。

  - 情況2:如果一個函數(shù)中有this,這個函數(shù)有被上一級的對象所調(diào)用,那么this指向的就是上一級的對象。

  - 情況3:如果一個函數(shù)中有this,這個函數(shù)中包含多個對象,盡管這個函數(shù)是被最外層的對象所調(diào)用,this指向的也只是它上一級的對象,例子3可以證明,如果不相信,那么接下來我們繼續(xù)看幾個例子。

var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

盡管對象b中沒有屬性a,這個this指向的也是對象b,因為this只會指向它的上一級對象,不管這個對象中有沒有this要的東西。

還有一種比較特殊的情況,例子4:
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

這里this指向的是window,是不是有些蒙了?其實是因為你沒有理解一句話,這句話同樣至關(guān)重要。

  this永遠指向的是最后調(diào)用它的對象,也就是看它執(zhí)行的時候是誰調(diào)用的,例子4中雖然函數(shù)fn是被對象b所引用,但是在將fn賦值給變量j的時候并沒有執(zhí)行所以最終指向的是window,這和例子3是不一樣的,例子3是直接執(zhí)行了fn。

  this講來講去其實就是那么一回事,只不過在不同的情況下指向的會有些不同,上面的總結(jié)每個地方都有些小錯誤,也不能說是錯誤,而是在不同環(huán)境下情況就會有不同,所以我也沒有辦法一次解釋清楚,只能你慢慢地的去體會。

(4)函數(shù)調(diào)用模式的時候,this指向window
function aa(){
  console.log(this)
}
aa()         //window

如果一個函數(shù)作為FFI被調(diào)用,意味著這個函數(shù)不符合以上任意一種調(diào)用方式,this指向全局對象,在瀏覽器中,即是window。

function fn() {
    console.log(this);
}

// If called in browser:
fn(); // -> Window {stop: ?, open: ?, alert: ?, ...}

注意,第4條規(guī)則和第3條很類似,不同的是當函數(shù)沒有作為方法被調(diào)用時,它將自動隱式編程全局對象的屬性——window。也就是當我們調(diào)用 fn(),可以理解為window.fn(),根據(jù)第三條規(guī)則,fn()函數(shù)中的this指向的就是window。

function fn() {
    console.log(this);
}

// In browser:
console.log(fn === window.fn); // -> true
(5) 如果出現(xiàn)上面對條規(guī)則的累加情況,則優(yōu)先級自1至4遞減,this的指向按照優(yōu)先級最高的規(guī)則判斷。

將規(guī)則應用于實踐
看一個代碼示例,并使用上面的規(guī)則判斷this的指向。

var obj = {
    value: "hi",
    printThis: function() {
        console.log(this);
    }
};

var print = obj.printThis;

obj.printThis(); // -> {value: "hi", printThis: ?}
print(); // -> Window {stop: ?, open: ?, alert: ?, ...}

obj.prinThis() ,根據(jù)第三條規(guī)則this指向的就是obj。根據(jù)第四條規(guī)則print()是FFI,因此this指向window。

obj對象中printThis這一方法其實是函數(shù)的地址的一個引用,當我們將obj.printThis賦值給print時,print包含的也是函數(shù)的引用,和obj對象一點關(guān)系也沒有。obj只是碰巧擁有一個指向這個函數(shù)的引用的屬性。

當不適用obj對象觸發(fā)函數(shù)時,這個函數(shù)就是FFI。

應用多項規(guī)則
當出現(xiàn)多個上述規(guī)則時,將優(yōu)先級高的“獲勝”,如果規(guī)則2和規(guī)則3同時存在,則規(guī)則2優(yōu)先:

var obj1 = {
    value: "hi",
    print: function() {
        console.log(this);
    },
};

var obj2 = { value: 17 };

obj1.print.call(obj2); // -> { value: 17 }

如果規(guī)則1和規(guī)則3同時被應用,則規(guī)則1優(yōu)先:

var obj1 = {
    value: "hi",
    print: function() {
        console.log(this);
    },
};

new obj1.print(); // -> print {}
額外的

當this碰到return時

function fn()  
{  
    this.user = "追夢子";  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined

再看一個

function fn()  
{  
    this.user = "追夢子";  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined

再來

function fn()  
{  
    this.user = "追夢子";  
    return 1;
}
var a = new fn;  
console.log(a.user); //追夢子
function fn()  
{  
    this.user = "追夢子";  
    return undefined;
}
var a = new fn;  
console.log(a.user); //追夢子

什么意思呢?

  如果返回值是一個對象,那么this指向的就是那個返回的對象,如果返回值不是一個對象那么this還是指向函數(shù)的實例。

function fn()  
{  
    this.user = "追夢子";  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: "追夢子"}

  還有一點就是雖然null也是對象,但是在這里this還是指向那個函數(shù)的實例,因為null比較特殊。

function fn()  
{  
    this.user = "追夢子";  
    return null;
}
var a = new fn;  
console.log(a.user); //追夢子
在嚴格版中的默認的this不再是window,而是undefined。 代碼中引用了庫?

有些庫會將this的指向綁定更有用的對象上,比如jQuery庫,在事件處理程序中,this的指向不是全局對象而被綁定到了元素對象上。因此,如果你發(fā)現(xiàn)一些不能用上述5項規(guī)則解釋的情況,請閱讀你所使用的庫的官方文檔,找到關(guān)于該庫是如何改變this的指向的,通常通過 bind 方法改變this的指向。

參考文章:
1.徹底理解js中this的指向,不必硬背。
2.javascript中this指向的規(guī)則
3.The Complete Rules to "this"
4.JavaScript中的this

版本二 上下文 vs 作用域

每個函數(shù)調(diào)用都有與之相關(guān)的作用域和上下文。首先需要澄清的問題是上下文和作用域是不同的概念。很多人經(jīng)常將這兩個術(shù)語混淆。

作用域(scope) 是在運行時代碼中的某些特定部分中變量,函數(shù)和對象的可訪問性。換句話說,作用域決定了代碼區(qū)塊中變量和其他資源的可見性。而上下文(context)是用來指定代碼某些特定部分中 this 的值。

從根本上說,作用域是基于函數(shù)(function-based)的,而上下文是基于對象(object-based)的。換句話說,作用域是和每次函數(shù)調(diào)用時變量的訪問有關(guān),并且每次調(diào)用都是獨立的。上下文總是被調(diào)用函數(shù)中關(guān)鍵字 this 的值,是調(diào)用當前可執(zhí)行代碼的對象的引用。說的通俗一點就是:this 取值,是在函數(shù)真正被調(diào)用執(zhí)行的時候確定的,而不是在函數(shù)定義的時候確定的。

全局上下文

無論是否在嚴格模式下,在全局執(zhí)行上下文中(在任何函數(shù)體外部)this 都指向全局對象。當然具體的全局對象和宿主環(huán)境有關(guān)。

在瀏覽器中, window 對象同時也是全局對象:

console.log(this === window); // true

NodeJS 中,則是 global 對象:

console.log(this); // global
函數(shù)上下文

由于其運行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數(shù)的調(diào)用方式。JavaScript 中函數(shù)的調(diào)用有以下幾種方式:作為函數(shù)調(diào)用,作為對象方法調(diào)用,作為構(gòu)造函數(shù)調(diào)用,和使用 apply 或 call 調(diào)用。下面我們將按照調(diào)用方式的不同,分別討論 this 的含義

作為函數(shù)直接調(diào)用

作為函數(shù)直接調(diào)用時,要注意 2 種情況:

非嚴格模式

在非嚴格模式下執(zhí)行函數(shù)調(diào)用,此時 this 默認指向全局對象。

function f1(){
  return this;
}
//在瀏覽器中:
f1() === window;   //在瀏覽器中,全局對象是window
 
//在Node中:
f1() === global;

嚴格模式 ‘use strict’

在嚴格模式下,this 將保持他進入執(zhí)行上下文時的值,所以下面的 this 并不會指向全局對象,而是默認為 undefined 。

"use strict"; // 這里是嚴格模式
function test() {
  return this;
};
 
test() === undefined; // true
作為對象的方法調(diào)用

在 JavaScript 中,函數(shù)也是對象,因此函數(shù)可以作為一個對象的屬性,此時該函數(shù)被稱為該對象的方法,在使用這種調(diào)用方式時,內(nèi)部的 this 指向該對象。

var Obj = {
  prop: 37,
  getProp: function() {
    return this.prop;
  }
};
 
console.log(Obj.getProp()); // 37

上面的例子中,當 Obj.getProp() 被調(diào)用時,方法內(nèi)的 this 將指向 Obj 對象。值得注意的是,這種行為根本不受函數(shù)定義方式或定義位置的影響。在前面的例子中,我們在定義對象 Obj 的同時,將成員 getProp 定義了一個匿名函數(shù)。但是,我們也可以首先定義函數(shù),然后再將其附加到 Obj.getProp 。所以,下面的代碼和上面的例子是等價的:

var Obj = {
  prop: 37
};
 
function independent() {
  return this.prop;
}
 
Obj.getProp = independent;
 
console.log(Obj.getProp()); // logs 37

JavaScript 非常靈活,現(xiàn)在我們把對象的方法賦值給一個變量,然后直接調(diào)用這個函數(shù)變量又會發(fā)生什么呢?

var Obj = {
  prop: 37,
  getProp: function() {
    return this.prop;
  }
};
 
var test = Obj.getProp
console.log(test()); // undefined

可以看到,這時候 this 指向全局對象,這個例子 test 只是引用了 Obj.getProp 函數(shù),也就是說這個函數(shù)并不作為 Obj 對象的方法調(diào)用,所以,它是被當作一個普通函數(shù)來直接調(diào)用。因此,this 指向全局對象。

一些坑

我們來看看下面這個例子:

var prop = 0;
var Obj = {
  prop: 37,
  getProp: function() {
    setTimeout(function() {
        console.log(this.prop) // 結(jié)果是 0 ,不是37!
    },1000)
  }
};
 
Obj.getProp();

正如你所見, setTimeout 中的 this 向了全局對象,這里不是把它當作函數(shù)的方法使用嗎?這一點經(jīng)常讓很多初學者疑惑;這種問題是很多異步回調(diào)函數(shù)中也會普遍會碰到,通常有個土辦法解決這個問題,比如,我們可以利用 閉包 的特性來處理:

var Obj = {
  prop: 37,
  getProp: function() {
    var self = this; 
    setTimeout(function() {
        console.log(self.prop) // 37
    },1000)
  }
};
 
Obj.getProp();

其實,setTimeoutsetInterval 都只是在全局上下文中執(zhí)行一個函數(shù)而已,即使是在嚴格模式下:

"use strict";
 
function foo() {
  console.log(this); // Window
}
 
setTimeout(foo, 1);

記住 setTimeoutsetInterval 都只是在全局上下文中執(zhí)行一個函數(shù)而已,因此 this 指向全局對象。 除非你實用箭頭函數(shù),Function.prototype.bind 方法等辦法修復。至于解決方案會在后續(xù)的文章中繼續(xù)討論。

作為構(gòu)造函數(shù)調(diào)用

JavaScript 支持面向?qū)ο笫骄幊?,與主流的面向?qū)ο笫骄幊陶Z言不同,JavaScript 并沒有類(class)的概念,而是使用基于原型(prototype)的繼承方式。作為又一項約定通用的準則,構(gòu)造函數(shù)以大寫字母開頭,提醒調(diào)用者使用正確的方式調(diào)用。

當一個函數(shù)用作構(gòu)造函數(shù)時(使用 new 關(guān)鍵字),它的 this 被綁定到正在構(gòu)造的新對象,也就是我們常說的實例化出來的對象。

function Person(name) {
  this.name = name;
}
 
var p = new Person("愚人碼頭");
console.log(p.name); // "愚人碼頭"

幾個陷阱

如果構(gòu)造函數(shù)具有返回對象的 return 語句,則該返回對象將是 new 表達式的結(jié)果。

function Person(name) {
  this.name = name;
  return { title : "前端開發(fā)" };
}
 
var p = new Person("愚人碼頭");
console.log(p.name); // undefined
console.log(p.title); // "前端開發(fā)"

相應的,JavaScript 中的構(gòu)造函數(shù)也很特殊,如果不使用 new 調(diào)用,則和普通函數(shù)一樣, this 仍然執(zhí)行全局:

function Person(name) {
  this.name = name;
  console.log(this); // Window 
}
 
var p = Person("愚人碼頭");
箭頭函數(shù)中的 this

在箭頭函數(shù)中,this 與封閉詞法上下文的 this 保持一致,也就是說由上下文確定。

var obj = {
    x: 10,
    foo: function() {
        var fn = () => {
            return () => {
                return () => {
                    console.log(this);      //{x: 10, foo: ?} 即 obj
                    console.log(this.x);    //10
                }
            }
        }
        fn()()();
    }
}
obj.foo();

obj.foo 是一個匿名函數(shù),無論如何, 這個函數(shù)中的 this 指向它被創(chuàng)建時的上下文(在上面的例子中,就是 obj 對象)。這同樣適用于在其他函數(shù)中創(chuàng)建的箭頭函數(shù):這些箭頭函數(shù)的this 被設(shè)置為外層執(zhí)行上下文。

// 創(chuàng)建一個含有bar方法的obj對象,bar返回一個函數(shù),這個函數(shù)返回它自己的this,
// 這個返回的函數(shù)是以箭頭函數(shù)創(chuàng)建的,所以它的this被永久綁定到了它外層函數(shù)的this。
// bar的值可以在調(diào)用中設(shè)置,它反過來又設(shè)置返回函數(shù)的值。
var obj = {
    bar: function() {
        var x = (() => this);
        return x;
    }
};
 
// 作為obj對象的一個方法來調(diào)用bar,把它的this綁定到obj。
// x所指向的匿名函數(shù)賦值給fn。
var fn = obj.bar();
 
// 直接調(diào)用fn而不設(shè)置this,通常(即不使用箭頭函數(shù)的情況)默認為全局對象,若在嚴格模式則為undefined
console.log(fn() === obj); // true
 
// 但是注意,如果你只是引用obj的方法,而沒有調(diào)用它(this是在函數(shù)調(diào)用過程中設(shè)置的)
var fn2 = obj.bar;
// 那么調(diào)用箭頭函數(shù)后,this指向window,因為它從 bar 繼承了this。
console.log(fn2()() == window); // true

在上面的例子中,一個賦值給了 obj.bar 的函數(shù)(稱為匿名函數(shù) A),返回了另一個箭頭函數(shù)(稱為匿名函數(shù) B)。因此,函數(shù)B的this被永久設(shè)置為 obj.bar(函數(shù)A)被調(diào)用時的 this 。當返回的函數(shù)(函數(shù)B)被調(diào)用時,它this始終是最初設(shè)置的。在上面的代碼示例中,函數(shù)B的 this 被設(shè)置為函數(shù)A的 this ,即 obj,所以它仍然設(shè)置為 obj,即使以通常將 this 設(shè)置為 undefined 或全局對象(或者如前面示例中全局執(zhí)行上下文中的任何其他方法)進行調(diào)用。

填坑

我們回到上面 setTimeout 的坑:

var prop = 0;
var Obj = {
  prop: 37,
  getProp: function() {
    setTimeout(function() {
        console.log(this.prop) // 結(jié)果是 0 ,不是37!
    },1000)
  }
};
 
Obj.getProp();

通常情況我,我們在這里期望輸出的結(jié)果是 37 ,用箭頭函數(shù)解決這個問題相當簡單:

var Obj = {
  prop: 37,
  getProp: function() {
    setTimeout(() => {
        console.log(this.prop) // 37
    },1000)
  }
};
 
Obj.getProp();
原型鏈中的 this

相同的概念在定義在原型鏈中的方法也是一致的。如果該方法存在于一個對象的原型鏈上,那么 this 指向的是調(diào)用這個方法的對象,就好像該方法本來就存在于這個對象上。

var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
 
console.log(p.f()); // 5

在這個例子中,對象 p 沒有屬于它自己的f屬性,它的f屬性繼承自它的原型。但是這對于最終在 o 中找到 f 屬性的查找過程來說沒有關(guān)系;查找過程首先從 p.f 的引用開始,所以函數(shù)中的 this 指向 p 。也就是說,因為f是作為p的方法調(diào)用的,所以它的this 指向了 p 。這是 JavaScript 的原型繼承中的一個有趣的特性。

你也會看到下面這種形式的老代碼,道理是一樣的:

function Person(name) {
  this.name = name;
}
Person.prototype = {
  getName:function () {
    return this.name
  }
};
var p = new Person("愚人碼頭");
console.log(p.getName()); // "愚人碼頭"
getter 與 setter 中的 this

再次,相同的概念也適用時的函數(shù)作為一個 getter 或者 一個 setter 調(diào)用。用作 getter 或 setter 的函數(shù)都會把 this 綁定到正在設(shè)置或獲取屬性的對象。

function sum() {
  return this.a + this.b + this.c;
}
 
var o = {
  a: 1,
  b: 2,
  c: 3,
  get average() {
    return (this.a + this.b + this.c) / 3;
  }
};
 
Object.defineProperty(o, "sum", {
    get: sum, enumerable: true, configurable: true});
 
console.log(o.average, o.sum); // logs 2, 6

注:Object.defineProperty() 顧名思義,為對象定義屬性,方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性, 并返回這個對象。是ES5的屬性, 支持IE8以上。

Object.defineProperty(obj, prop, descriptor)

參數(shù)

object 必需。 要在其上添加或修改屬性的對象。 這可能是一個本機 JavaScript對象(即用戶定義的對象或內(nèi)置對象)或 DOM 對象。

propertyname 必需。 一個包含屬性名稱的字符串。

descriptor 必需。 屬性描述符。 它可以針對數(shù)據(jù)屬性或訪問器屬性。

在js中我們可以通過下面這幾種方法定義屬性

// (1) define someOne property name
someOne.name = "cover";
//or use (2) 
someOne["name"] = "cover";
// or use (3) defineProperty
Object.defineProperty(someOne, "name", {
    value : "cover"
})

屬性的狀態(tài)設(shè)置
其中descriptor的參數(shù)值得我們關(guān)注下,該屬性可設(shè)置的值有:
【value】 屬性的值,默認為 undefined。
【writable】 該屬性是否可寫,如果設(shè)置成 false,則任何對該屬性改寫的操作都無效(但不會報錯),對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。

var someOne = { };
Object.defineProperty(someOne, "name", {
    value:"coverguo" , //由于設(shè)定了writable屬性為false 導致這個量不可以修改
    writable: false 
});  
console.log(someOne.name); // 輸出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 輸出coverguo

【configurable]】如果為false,則任何嘗試刪除目標屬性或修改屬性以下特性(writable, configurable, enumerable)的行為將被無效化,對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。

var someOne = { };
Object.defineProperty(someOne, "name", {
    value:"coverguo" ,
    configurable: false 
});  
delete someOne.name; 
console.log(someOne.name);// 輸出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 輸出coverguo

【enumerable】 是否能在for-in循環(huán)中遍歷出來或在Object.keys中列舉出來。對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。
注意 在調(diào)用Object.defineProperty()方法時,如果不指定, configurable, enumerable, writable特性的默認值都是false,這跟之前所 說的對于像前面例子中直接在對象上定義的屬性,這個特性默認值為為 true并不沖突,如下代碼所示:

//調(diào)用Object.defineProperty()方法時,如果不指定
var someOne = { };
someOne.name = "coverguo";
console.log(Object.getOwnPropertyDescriptor(someOne, "name"));
//輸出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true}

//直接在對象上定義的屬性,這個特性默認值為為 true
var otherOne = {};
Object.defineProperty(otherOne, "name", {
    value:"coverguo" 
});  
console.log(Object.getOwnPropertyDescriptor(otherOne, "name"));
//輸出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}

【get】一旦目標對象訪問該屬性,就會調(diào)用這個方法,并返回結(jié)果。默認為 undefined。
【set】 一旦目標對象設(shè)置該屬性,就會調(diào)用這個方法。默認為 undefined。
從上面,可以得知,我們可以通過使用Object.defineProperty,來定義和控制一些特殊的屬性,如屬性是否可讀,屬性是否可枚舉,甚至修改屬性的修改器(setter)和獲取器(getter)
那什么場景和地方適合使用到特殊的屬性呢?

從上面,可以得知,我們可以通過使用Object.defineProperty,來定義和控制一些特殊的屬性,如屬性是否可讀,屬性是否可枚舉,甚至修改屬性的修改器(setter)和獲取器(getter)

實際運用
在一些框架,如vue、express、qjs等,經(jīng)常會看到對Object.defineProperty的使用。那這些框架是如何使用呢?

MVVM中數(shù)據(jù)‘雙向綁定’實現(xiàn)
待補充
優(yōu)化對象獲取和修改屬性方式
這個優(yōu)化對象獲取和修改屬性方式,是什么意思呢? 過去我們在設(shè)置dom節(jié)點transform時是這樣的。

//加入有一個目標節(jié)點, 我們想設(shè)置其位移時是這樣的
var targetDom = document.getElementById("target");
var transformText = "translateX(" + 10 + "px)";
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;

通過上面,可以看到如果頁面是需要許多動畫時,我們這樣編寫transform屬性是十分蛋疼的。
但如果通過Object.defineProperty, 我們則可以

//這里只是簡單設(shè)置下translateX的屬性,其他如scale等屬性可自己去嘗試

Object.defineProperty(dom, "translateX", {
set: function(value) {
         var transformText = "translateX(" + value + "px)";
        dom.style.webkitTransform = transformText;
        dom.style.transform = transformText;
}
//這樣再后面調(diào)用的時候, 十分簡單
dom.translateX = 10;
dom.translateX = -10;
//甚至可以拓展設(shè)置如scale, originX, translateZ,等各個屬性,達到下面的效果
dom.scale = 1.5;  //放大1.5倍
dom.originX = 5;  //設(shè)置中心點X
}

上面只是個簡單的版本,并不是最合理的寫法,但主要是為了說明具體的意圖和方法
增加屬性獲取和修改時的信息
如在Express4.0中,該版本去除了一些舊版本的中間件,為了讓用戶能夠更好地發(fā)現(xiàn),其有下面這段代碼,通過修改get屬性方法,讓用戶調(diào)用廢棄屬性時拋錯并帶上自定義的錯誤信息。

[
  "json",
  "urlencoded",
  "bodyParser",
  "compress",
  "cookieSession",
  "session",
  "logger",
  "cookieParser",
  "favicon",
  "responseTime",
  "errorHandler",
  "timeout",
  "methodOverride",
  "vhost",
  "csrf",
  "directory",
  "limit",
  "multipart",
  "staticCache",
].forEach(function (name) {
  Object.defineProperty(exports, name, {
    get: function () {
      throw new Error("Most middleware (like " + name + ") is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.");
    },
    configurable: true
  });
});
作為一個DOM事件處理函數(shù)

當函數(shù)被用作事件處理函數(shù)時,它的 this 指向觸發(fā)事件的元素(一些瀏覽器在使用非addEventListener 的函數(shù)動態(tài)添加監(jiān)聽函數(shù)時不遵守這個約定)。

// 被調(diào)用時,將關(guān)聯(lián)的元素變成藍色
function bluify(e){
  console.log(this === e.currentTarget); // 總是 true
 
  // 當 currentTarget 和 target 是同一個對象是為 true
  console.log(this === e.target);        
  this.style.backgroundColor = "#A5D9F3";
}
 
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName("*");
 
// 將bluify作為元素的點擊監(jiān)聽函數(shù),當元素被點擊時,就會變成藍色
for(var i=0 ; i < elements.length; i++){
  elements[i].addEventListener("click", bluify, false);
}
作為一個內(nèi)聯(lián)事件處理函數(shù)

當代碼被內(nèi)聯(lián)on-event 處理函數(shù)調(diào)用時,它的this指向監(jiān)聽器所在的DOM元素:

上面的 alert 會顯示 button 。注意只有外層代碼中的 this 是這樣設(shè)置的:

在這種情況下,沒有設(shè)置內(nèi)部函數(shù)的 this,所以它指向 global/window 對象(即非嚴格模式下調(diào)用的函數(shù)未設(shè)置 this 時指向的默認對象)。

使用 apply 或 call 調(diào)用

JavaScript 中函數(shù)也是對象,對象則有方法,apply 和 call 就是函數(shù)對象的方法。這兩個方法異常強大,他們允許切換函數(shù)執(zhí)行的上下文環(huán)境(context),即 this 綁定的對象。很多 JavaScript 中的技巧以及類庫都用到了該方法。讓我們看一個具體的例子:

function Point(x, y){ 
   this.x = x; 
   this.y = y; 
   this.moveTo = function(x, y){ 
       this.x = x; 
       this.y = y; 
   } 
} 
 
var p1 = new Point(0, 0); 
p1.moveTo(1, 1); 
console.log(p1.x,p1.y); //1 1
 
var p2 = {x: 0, y: 0}; 
p1.moveTo.apply(p2, [10, 10]);
console.log(p2.x,p2.y); //10 10

在上面的例子中,我們使用構(gòu)造函數(shù)生成了一個對象 p1,該對象同時具有 moveTo 方法;使用對象字面量創(chuàng)建了另一個對象 p2,我們看到使用 apply 可以將 p1 的方法 apply 到 p2 上,這時候 this 也被綁定到對象 p2 上。另一個方法 call 也具備同樣功能,不同的是最后的參數(shù)不是作為一個數(shù)組統(tǒng)一傳入,而是分開傳入的:

function Point(x, y){ 
   this.x = x; 
   this.y = y; 
   this.moveTo = function(x, y){ 
       this.x = x; 
       this.y = y; 
   } 
} 
 
var p1 = new Point(0, 0); 
p1.moveTo(1, 1); 
console.log(p1.x,p1.y); //1 1
 
var p2 = {x: 0, y: 0}; 
p1.moveTo.call(p2, 10, 10); // 只是參數(shù)不同
console.log(p2.x,p2.y); //10 10
.bind() 方法

ECMAScript 5 引入了 Function.prototype.bind 。調(diào)用 f.bind(someObject) 會創(chuàng)建一個與 f 具有相同函數(shù)體和作用域的函數(shù),但是在這個新函數(shù)中,this 將永久地被綁定到了 bind 的第一個參數(shù),無論這個函數(shù)是如何被調(diào)用的。

function f(){
  return this.a;
}
 
//this被固定到了傳入的對象上
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
 
var h = g.bind({a:"yoo"}); //bind只生效一次!
console.log(h()); // azerty
 
var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty

填坑

上面我們已經(jīng)講了使用箭頭函數(shù)填 setTimeout 的坑,這次我們使用 bind 方法來試試:

var prop = 0;
var Obj = {
  prop: 37,
  getProp: function() {
    setTimeout(function() {
        console.log(this.prop) // 37
    }.bind(Obj),1000)
  }
};
 
Obj.getProp();

同樣可以填坑,但是看上去沒有使用箭頭函數(shù)來的優(yōu)雅。

Vue實例里this

vue文檔里的原話:

All lifecycle hooks are called with their "this" context pointing to the Vue instance invoking it.

意思是:在Vue所有的生命周期鉤子方法(如created,mounted, updated以及destroyed)里使用this,this指向調(diào)用它的Vue實例。

示例分析




    
    
    
    

示例定義了兩個message。一個是全局變量,即window.message,它的值為英文“Hello!”。另外一個是vue實例的數(shù)據(jù)message,它的值為中文的“你好!”。

運行示例,在瀏覽器得到:

第一個輸出英文"Hello!”,第二個輸出中文“你好!”。這說明了showMessage1()里的this指的是window,而showMessage2()里的this指的是vue實例。

//created

created: function() {
  this.showMessage1();    //this 1
  this.showMessage2();   //this 2
}

created函數(shù)為vue實例的鉤子方法,它里面使用的this指的是vue實例。

//showMessage1()

showMessage1:function(){
    setTimeout(function() {
       document.getElementById("id1").innerText = this.message;  //this 3
    }, 10)
}

對于普通函數(shù)(包括匿名函數(shù)),this指的是直接的調(diào)用者,在非嚴格模式下,如果沒有直接調(diào)用者,this指的是window。showMessage1()里setTimeout使用了匿名函數(shù),this指向window。

//showMessage2()

showMessage2:function() {
    setTimeout(() => {
       document.getElementById("id2").innerText = this.message;  //this 4
    }, 10)
}

箭頭函數(shù)是沒有自己的this,在它內(nèi)部使用的this是由它定義的宿主對象決定。showMessage2()里定義的箭頭函數(shù)宿主對象為vue實例,所以它里面使用的this指向vue實例。

綁定vue實例到this的方法

為了避免this指向出現(xiàn)歧義,有兩種方法綁定this。

使用bind

showMessage1()可以改為:

showMessage1:function(){
    setTimeout(function() {
       document.getElementById("id1").innerText = this.message;  //this 3
    }.bind(this), 10)
}

對setTimeout()里的匿名函數(shù)使用bind()綁定到vue實例的this。這樣在匿名函數(shù)內(nèi)的this也為vue實例。

賦值給另一個變量

showMessage1()也可以改為

showMessage1:function(){
    var self = this;
    setTimeout(function() {
       document.getElementById("id1").innerText = self.message;  //改為self
    }.bind(this), 10)
}

這里吧表示vue實例的this賦值給變量self。在使用到this的地方改用self引用。

參考文章:
1.全面理解 JavaScript 中的 this
2.不會Object.defineProperty你就out了
3.10道典型的JavaScript面試題

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/90281.html

相關(guān)文章

  • 手挽手帶你學React:二檔 React生命周期以及組件開發(fā)

    摘要:手挽手帶你學入門二檔組件開發(fā)的開始,合理運用生命周期和組件,能夠讓你的開發(fā)變地流利又這篇文章帶你學會創(chuàng)建組件,運用組建。 手挽手帶你學React入門二檔,組件開發(fā)的開始,合理運用生命周期和組件,能夠讓你的開發(fā)變地流利又happy,這篇文章帶你學會創(chuàng)建組件,運用組建。學起來吧! React 組件生命周期 學習React,生命周期很重要,我們了解完生命周期的各個組件,對寫高性能組件會有很大...

    izhuhaodev 評論0 收藏0
  • 文章帶你理解原型和原型鏈

    摘要:上面的代碼,運行以后,我們可以看到因為的原型是指向的實例上的,所以可以訪問他的屬性值,那如果我不想讓訪問的構(gòu)造函數(shù)里聲明的屬性值,那怎么辦呢只需要將指向的原型而不是實例就行了。 走在前端的大道上 本篇將自己讀過的相關(guān) javascript原型和原型鏈 文章中,對自己有啟發(fā)的章節(jié)片段總結(jié)在這(會對原文進行刪改),會不斷豐富提煉總結(jié)更新。 文章——深入理解javascript之原型 一般的...

    yintaolaowanzi 評論0 收藏0
  • 圖文 視頻雙管齊下,帶你全面徹底理解Retrofit源碼,Android開發(fā)五年

    摘要:協(xié)程的判斷條件下面我們來著重看下的源碼,因為從這里開始就涉及到協(xié)程的判斷。第二點是關(guān)鍵點,用來判斷該方法的調(diào)用是否使用到了協(xié)程。原理我們先來看下使用協(xié)程是怎么寫的這是一個標準的協(xié)程寫法,然后我們再套用上面的條件,發(fā)現(xiàn)完全匹配不到。 第一眼看,跟我之前印象中的有點區(qū)別(也不知道是什么版本),return的時候居然...

    不知名網(wǎng)友 評論0 收藏0
  • 文章帶你了解如何用Planting 為測試工程師開發(fā)的部署框架

    摘要:是一個為測試工程師開發(fā)的部署框架,使用語言編寫,為了解決測試團隊在測試過程中的部署問題。部署執(zhí)行方式簡單,支持命令行與自動化測試可緊密合作作為一個為測試工程師開發(fā)的部署框架,通過命令行進行自動化部署是第一選擇。 ...

    yiliang 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<