摘要:追夢子追夢子通過在方法,給第一個參數添加要把添加到哪個環境中,簡單來說,就會指向那個對象。追夢子追夢子還有一點就是雖然也是對象,但是在這里還是指向那個函數的實例,因為比較特殊。追夢子追夢子在嚴格版中的默認的不再是,而是。
走在前端的大道上
本篇將自己讀過的相關 this指向 的文章中,對自己有啟發的章節片段總結在這(會對原文進行刪改),會不斷豐富提煉總結更新。
版本一 一句話this的指向在函數定義的時候是確定不了的,只有函數執行的時候才能確定this到底指向誰,實際上this的最終指向的是那個調用它的對象(這句話有些問題,后面會解釋為什么會有問題,雖然網上大部分的文章都是這樣說的,雖然在很多情況下那樣去理解不會出什么問題,但是實際上那樣理解是不準確的,所以在你理解this的時候會有種琢磨不透的感覺) —— ——徹底理解js中this的指向,不必硬背。5大規則 (1)構造函數模式的時候,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 關鍵詞出現在被調用函數的前面,那么JavaScript引擎會創建一個新的對象,被調用函數中的this指向的就是這個新創建的函數。
function ConstructorExample() { console.log(this); this.value = 10; console.log(this); } new ConstructorExample(); // -> ConstructorExample {} // -> ConstructorExample { value: 10 }
構造函數版this:
function Fn(){ this.user = "追夢子"; } var a = new Fn(); console.log(a.user); //追夢子
這里之所以對象a可以點出函數Fn里面的user是因為new關鍵字可以改變this的指向,將這個this指向對象a,為什么我說a是對象,因為用了new關鍵字就是創建一個對象實例,我們這里用變量a創建了一個Fn的實例(相當于復制了一份Fn到對象a里面),此時僅僅只是創建,并沒有執行,而調用這個函數Fn的是對象a,那么this指向的自然是對象a,那么為什么對象a中會有user,因為你已經復制了一份Fn函數到對象a中,用了new關鍵字就等同于復制了一份。
(2)apply/call調用模式的時候,this指向apply/call方法中的第一個參數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的方式觸發函數,那么函數中的this指向傳入函數的第一個參數。
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是怎么回事呢?如果我們直接執行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方法,給第一個參數添加要把b添加到哪個環境中,簡單來說,this就會指向那個對象。
call方法除了第一個參數以外還可以添加多個參數,如下:
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也可以有多個參數,但是不同的是,第二個參數必須是一個數組,如下:
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的第一個參數寫的是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);
我們發現代碼沒有被打印,對,這就是bind和call、apply方法的不同,實際上bind方法返回的是一個修改過后的函數。
var a = { user:"追夢子", fn:function(){ console.log(this.user); } } var b = a.fn; var c = b.bind(a); console.log(c); //function() { [native code] }
那么我們現在執行一下函數c看看,能不能打印出對象a里面的user
var a = { user:"追夢子", fn:function(){ console.log(this.user); //追夢子 } } var b = a.fn; var c = b.bind(a); c();
ok,同樣bind也可以有多個參數,并且參數可以執行的時候再次添加,但是要注意的是,參數是按照形參的順序進行的。
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);
總結:call和apply都是改變上下文中的this并立即執行這個函數,bind方法可以讓對應的函數想什么時候調就什么時候調用,并且可以將參數在執行的時候添加,這是它們的區別,根據自己的實際情況來選擇使用。
(3)方法調用模式的時候,this指向方法所在的對象var a={}; a.name = "hello"; a.getName = function(){ console.log(this.name) } a.getName() //"hello"
如果一個函數是某個對象的方法,并且對象使用句點符號觸發函數,那么this指向的就是該函數作為那個對象的屬性的對象,也就是,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最終指向的是調用它的對象,這里的函數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,因為你調用這個fn是通過o.fn()執行的,那自然指向就是對象o,這里再次強調一點,this的指向在函數創建的時候是決定不了的,在調用的時候才能決定,誰調用的就指向誰,一定要搞清楚這個。
其實例子1和例子2說的并不夠準確,下面這個例子就可以推翻上面的理論。
如果要徹底的搞懂this必須看接下來的幾個例子
例子3:var o = { user:"追夢子", fn:function(){ console.log(this.user); //追夢子 } } window.o.fn();
這段代碼和上面的那段代碼幾乎是一樣的,但是這里的this為什么不是指向window,如果按照上面的理論,最終this指向的是調用它的對象,這里先說個而外話,window是js中的全局對象,我們創建的變量實際上是給window添加屬性,所以這里可以用window點o對象。
這里先不解釋為什么上面的那段代碼this為什么沒有指向window,我們再來看一段代碼。
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //12 } } } o.b.fn();
這里同樣也是對象o點出來的,但是同樣this并沒有執行它,那你肯定會說我一開始說的那些不就都是錯誤的嗎?其實也不是,只是一開始說的不準確,接下來我將補充一句話,我相信你就可以徹底的理解this的指向的問題。
- 情況1:如果一個函數中有this,但是它沒有被上一級的對象所調用,那么this指向的就是window,這里需要說明的是在js的嚴格版中this指向的不是window,但是我們這里不探討嚴格版的問題,你想了解可以自行上網查找。
- 情況2:如果一個函數中有this,這個函數有被上一級的對象所調用,那么this指向的就是上一級的對象。
- 情況3:如果一個函數中有this,這個函數中包含多個對象,盡管這個函數是被最外層的對象所調用,this指向的也只是它上一級的對象,例子3可以證明,如果不相信,那么接下來我們繼續看幾個例子。
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,是不是有些蒙了?其實是因為你沒有理解一句話,這句話同樣至關重要。
this永遠指向的是最后調用它的對象,也就是看它執行的時候是誰調用的,例子4中雖然函數fn是被對象b所引用,但是在將fn賦值給變量j的時候并沒有執行所以最終指向的是window,這和例子3是不一樣的,例子3是直接執行了fn。
this講來講去其實就是那么一回事,只不過在不同的情況下指向的會有些不同,上面的總結每個地方都有些小錯誤,也不能說是錯誤,而是在不同環境下情況就會有不同,所以我也沒有辦法一次解釋清楚,只能你慢慢地的去體會。
(4)函數調用模式的時候,this指向windowfunction aa(){ console.log(this) } aa() //window
如果一個函數作為FFI被調用,意味著這個函數不符合以上任意一種調用方式,this指向全局對象,在瀏覽器中,即是window。
function fn() { console.log(this); } // If called in browser: fn(); // -> Window {stop: ?, open: ?, alert: ?, ...}
注意,第4條規則和第3條很類似,不同的是當函數沒有作為方法被調用時,它將自動隱式編程全局對象的屬性——window。也就是當我們調用 fn(),可以理解為window.fn(),根據第三條規則,fn()函數中的this指向的就是window。
function fn() { console.log(this); } // In browser: console.log(fn === window.fn); // -> true(5) 如果出現上面對條規則的累加情況,則優先級自1至4遞減,this的指向按照優先級最高的規則判斷。
將規則應用于實踐
看一個代碼示例,并使用上面的規則判斷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() ,根據第三條規則this指向的就是obj。根據第四條規則print()是FFI,因此this指向window。
obj對象中printThis這一方法其實是函數的地址的一個引用,當我們將obj.printThis賦值給print時,print包含的也是函數的引用,和obj對象一點關系也沒有。obj只是碰巧擁有一個指向這個函數的引用的屬性。
當不適用obj對象觸發函數時,這個函數就是FFI。
應用多項規則
當出現多個上述規則時,將優先級高的“獲勝”,如果規則2和規則3同時存在,則規則2優先:
var obj1 = { value: "hi", print: function() { console.log(this); }, }; var obj2 = { value: 17 }; obj1.print.call(obj2); // -> { value: 17 }
如果規則1和規則3同時被應用,則規則1優先:
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還是指向函數的實例。
function fn() { this.user = "追夢子"; return undefined; } var a = new fn; console.log(a); //fn {user: "追夢子"}
還有一點就是雖然null也是對象,但是在這里this還是指向那個函數的實例,因為null比較特殊。
function fn() { this.user = "追夢子"; return null; } var a = new fn; console.log(a.user); //追夢子在嚴格版中的默認的this不再是window,而是undefined。 代碼中引用了庫?
有些庫會將this的指向綁定更有用的對象上,比如jQuery庫,在事件處理程序中,this的指向不是全局對象而被綁定到了元素對象上。因此,如果你發現一些不能用上述5項規則解釋的情況,請閱讀你所使用的庫的官方文檔,找到關于該庫是如何改變this的指向的,通常通過 bind 方法改變this的指向。
參考文章:
1.徹底理解js中this的指向,不必硬背。
2.javascript中this指向的規則
3.The Complete Rules to "this"
4.JavaScript中的this
每個函數調用都有與之相關的作用域和上下文。首先需要澄清的問題是上下文和作用域是不同的概念。很多人經常將這兩個術語混淆。
作用域(scope) 是在運行時代碼中的某些特定部分中變量,函數和對象的可訪問性。換句話說,作用域決定了代碼區塊中變量和其他資源的可見性。而上下文(context)是用來指定代碼某些特定部分中 this 的值。
從根本上說,作用域是基于函數(function-based)的,而上下文是基于對象(object-based)的。換句話說,作用域是和每次函數調用時變量的訪問有關,并且每次調用都是獨立的。上下文總是被調用函數中關鍵字 this 的值,是調用當前可執行代碼的對象的引用。說的通俗一點就是:this 取值,是在函數真正被調用執行的時候確定的,而不是在函數定義的時候確定的。
全局上下文無論是否在嚴格模式下,在全局執行上下文中(在任何函數體外部)this 都指向全局對象。當然具體的全局對象和宿主環境有關。
在瀏覽器中, window 對象同時也是全局對象:
console.log(this === window); // true
NodeJS 中,則是 global 對象:
console.log(this); // global函數上下文
由于其運行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數的調用方式。JavaScript 中函數的調用有以下幾種方式:作為函數調用,作為對象方法調用,作為構造函數調用,和使用 apply 或 call 調用。下面我們將按照調用方式的不同,分別討論 this 的含義
作為函數直接調用作為函數直接調用時,要注意 2 種情況:
非嚴格模式
在非嚴格模式下執行函數調用,此時 this 默認指向全局對象。
function f1(){ return this; } //在瀏覽器中: f1() === window; //在瀏覽器中,全局對象是window //在Node中: f1() === global;
嚴格模式 ‘use strict’
在嚴格模式下,this 將保持他進入執行上下文時的值,所以下面的 this 并不會指向全局對象,而是默認為 undefined 。
"use strict"; // 這里是嚴格模式 function test() { return this; }; test() === undefined; // true作為對象的方法調用
在 JavaScript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,內部的 this 指向該對象。
var Obj = { prop: 37, getProp: function() { return this.prop; } }; console.log(Obj.getProp()); // 37
上面的例子中,當 Obj.getProp() 被調用時,方法內的 this 將指向 Obj 對象。值得注意的是,這種行為根本不受函數定義方式或定義位置的影響。在前面的例子中,我們在定義對象 Obj 的同時,將成員 getProp 定義了一個匿名函數。但是,我們也可以首先定義函數,然后再將其附加到 Obj.getProp 。所以,下面的代碼和上面的例子是等價的:
var Obj = { prop: 37 }; function independent() { return this.prop; } Obj.getProp = independent; console.log(Obj.getProp()); // logs 37
JavaScript 非常靈活,現在我們把對象的方法賦值給一個變量,然后直接調用這個函數變量又會發生什么呢?
var Obj = { prop: 37, getProp: function() { return this.prop; } }; var test = Obj.getProp console.log(test()); // undefined
可以看到,這時候 this 指向全局對象,這個例子 test 只是引用了 Obj.getProp 函數,也就是說這個函數并不作為 Obj 對象的方法調用,所以,它是被當作一個普通函數來直接調用。因此,this 指向全局對象。
一些坑
我們來看看下面這個例子:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 結果是 0 ,不是37! },1000) } }; Obj.getProp();
正如你所見, setTimeout 中的 this 向了全局對象,這里不是把它當作函數的方法使用嗎?這一點經常讓很多初學者疑惑;這種問題是很多異步回調函數中也會普遍會碰到,通常有個土辦法解決這個問題,比如,我們可以利用 閉包 的特性來處理:
var Obj = { prop: 37, getProp: function() { var self = this; setTimeout(function() { console.log(self.prop) // 37 },1000) } }; Obj.getProp();
其實,setTimeout 和 setInterval 都只是在全局上下文中執行一個函數而已,即使是在嚴格模式下:
"use strict"; function foo() { console.log(this); // Window } setTimeout(foo, 1);
記住 setTimeout 和 setInterval 都只是在全局上下文中執行一個函數而已,因此 this 指向全局對象。 除非你實用箭頭函數,Function.prototype.bind 方法等辦法修復。至于解決方案會在后續的文章中繼續討論。
作為構造函數調用JavaScript 支持面向對象式編程,與主流的面向對象式編程語言不同,JavaScript 并沒有類(class)的概念,而是使用基于原型(prototype)的繼承方式。作為又一項約定通用的準則,構造函數以大寫字母開頭,提醒調用者使用正確的方式調用。
當一個函數用作構造函數時(使用 new 關鍵字),它的 this 被綁定到正在構造的新對象,也就是我們常說的實例化出來的對象。
function Person(name) { this.name = name; } var p = new Person("愚人碼頭"); console.log(p.name); // "愚人碼頭"
幾個陷阱
如果構造函數具有返回對象的 return 語句,則該返回對象將是 new 表達式的結果。
function Person(name) { this.name = name; return { title : "前端開發" }; } var p = new Person("愚人碼頭"); console.log(p.name); // undefined console.log(p.title); // "前端開發"
相應的,JavaScript 中的構造函數也很特殊,如果不使用 new 調用,則和普通函數一樣, this 仍然執行全局:
function Person(name) { this.name = name; console.log(this); // Window } var p = Person("愚人碼頭");箭頭函數中的 this
在箭頭函數中,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 是一個匿名函數,無論如何, 這個函數中的 this 指向它被創建時的上下文(在上面的例子中,就是 obj 對象)。這同樣適用于在其他函數中創建的箭頭函數:這些箭頭函數的this 被設置為外層執行上下文。
// 創建一個含有bar方法的obj對象,bar返回一個函數,這個函數返回它自己的this, // 這個返回的函數是以箭頭函數創建的,所以它的this被永久綁定到了它外層函數的this。 // bar的值可以在調用中設置,它反過來又設置返回函數的值。 var obj = { bar: function() { var x = (() => this); return x; } }; // 作為obj對象的一個方法來調用bar,把它的this綁定到obj。 // x所指向的匿名函數賦值給fn。 var fn = obj.bar(); // 直接調用fn而不設置this,通常(即不使用箭頭函數的情況)默認為全局對象,若在嚴格模式則為undefined console.log(fn() === obj); // true // 但是注意,如果你只是引用obj的方法,而沒有調用它(this是在函數調用過程中設置的) var fn2 = obj.bar; // 那么調用箭頭函數后,this指向window,因為它從 bar 繼承了this。 console.log(fn2()() == window); // true
在上面的例子中,一個賦值給了 obj.bar 的函數(稱為匿名函數 A),返回了另一個箭頭函數(稱為匿名函數 B)。因此,函數B的this被永久設置為 obj.bar(函數A)被調用時的 this 。當返回的函數(函數B)被調用時,它this始終是最初設置的。在上面的代碼示例中,函數B的 this 被設置為函數A的 this ,即 obj,所以它仍然設置為 obj,即使以通常將 this 設置為 undefined 或全局對象(或者如前面示例中全局執行上下文中的任何其他方法)進行調用。
填坑
我們回到上面 setTimeout 的坑:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 結果是 0 ,不是37! },1000) } }; Obj.getProp();
通常情況我,我們在這里期望輸出的結果是 37 ,用箭頭函數解決這個問題相當簡單:
var Obj = { prop: 37, getProp: function() { setTimeout(() => { console.log(this.prop) // 37 },1000) } }; Obj.getProp();原型鏈中的 this
相同的概念在定義在原型鏈中的方法也是一致的。如果該方法存在于一個對象的原型鏈上,那么 this 指向的是調用這個方法的對象,就好像該方法本來就存在于這個對象上。
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 屬性的查找過程來說沒有關系;查找過程首先從 p.f 的引用開始,所以函數中的 this 指向 p 。也就是說,因為f是作為p的方法調用的,所以它的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
再次,相同的概念也適用時的函數作為一個 getter 或者 一個 setter 調用。用作 getter 或 setter 的函數都會把 this 綁定到正在設置或獲取屬性的對象。
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() 顧名思義,為對象定義屬性,方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 并返回這個對象。是ES5的屬性, 支持IE8以上。
Object.defineProperty(obj, prop, descriptor)
參數
object 必需。 要在其上添加或修改屬性的對象。 這可能是一個本機 JavaScript對象(即用戶定義的對象或內置對象)或 DOM 對象。
propertyname 必需。 一個包含屬性名稱的字符串。
descriptor 必需。 屬性描述符。 它可以針對數據屬性或訪問器屬性。
在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" })
屬性的狀態設置
其中descriptor的參數值得我們關注下,該屬性可設置的值有:
【value】 屬性的值,默認為 undefined。
【writable】 該屬性是否可寫,如果設置成 false,則任何對該屬性改寫的操作都無效(但不會報錯),對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。
var someOne = { }; Object.defineProperty(someOne, "name", { value:"coverguo" , //由于設定了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循環中遍歷出來或在Object.keys中列舉出來。對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。
注意 在調用Object.defineProperty()方法時,如果不指定, configurable, enumerable, writable特性的默認值都是false,這跟之前所 說的對于像前面例子中直接在對象上定義的屬性,這個特性默認值為為 true并不沖突,如下代碼所示:
//調用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】一旦目標對象訪問該屬性,就會調用這個方法,并返回結果。默認為 undefined。
【set】 一旦目標對象設置該屬性,就會調用這個方法。默認為 undefined。
從上面,可以得知,我們可以通過使用Object.defineProperty,來定義和控制一些特殊的屬性,如屬性是否可讀,屬性是否可枚舉,甚至修改屬性的修改器(setter)和獲取器(getter)
那什么場景和地方適合使用到特殊的屬性呢?
從上面,可以得知,我們可以通過使用Object.defineProperty,來定義和控制一些特殊的屬性,如屬性是否可讀,屬性是否可枚舉,甚至修改屬性的修改器(setter)和獲取器(getter)
實際運用
在一些框架,如vue、express、qjs等,經常會看到對Object.defineProperty的使用。那這些框架是如何使用呢?
MVVM中數據‘雙向綁定’實現
待補充
優化對象獲取和修改屬性方式
這個優化對象獲取和修改屬性方式,是什么意思呢? 過去我們在設置dom節點transform時是這樣的。
//加入有一個目標節點, 我們想設置其位移時是這樣的 var targetDom = document.getElementById("target"); var transformText = "translateX(" + 10 + "px)"; targetDom.style.webkitTransform = transformText; targetDom.style.transform = transformText;
通過上面,可以看到如果頁面是需要許多動畫時,我們這樣編寫transform屬性是十分蛋疼的。
但如果通過Object.defineProperty, 我們則可以
//這里只是簡單設置下translateX的屬性,其他如scale等屬性可自己去嘗試 Object.defineProperty(dom, "translateX", { set: function(value) { var transformText = "translateX(" + value + "px)"; dom.style.webkitTransform = transformText; dom.style.transform = transformText; } //這樣再后面調用的時候, 十分簡單 dom.translateX = 10; dom.translateX = -10; //甚至可以拓展設置如scale, originX, translateZ,等各個屬性,達到下面的效果 dom.scale = 1.5; //放大1.5倍 dom.originX = 5; //設置中心點X }
上面只是個簡單的版本,并不是最合理的寫法,但主要是為了說明具體的意圖和方法
增加屬性獲取和修改時的信息
如在Express4.0中,該版本去除了一些舊版本的中間件,為了讓用戶能夠更好地發現,其有下面這段代碼,通過修改get屬性方法,讓用戶調用廢棄屬性時拋錯并帶上自定義的錯誤信息。
[ "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事件處理函數
當函數被用作事件處理函數時,它的 this 指向觸發事件的元素(一些瀏覽器在使用非addEventListener 的函數動態添加監聽函數時不遵守這個約定)。
// 被調用時,將關聯的元素變成藍色 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作為元素的點擊監聽函數,當元素被點擊時,就會變成藍色 for(var i=0 ; i < elements.length; i++){ elements[i].addEventListener("click", bluify, false); }作為一個內聯事件處理函數
當代碼被內聯on-event 處理函數調用時,它的this指向監聽器所在的DOM元素:
上面的 alert 會顯示 button 。注意只有外層代碼中的 this 是這樣設置的:
在這種情況下,沒有設置內部函數的 this,所以它指向 global/window 對象(即非嚴格模式下調用的函數未設置 this 時指向的默認對象)。
使用 apply 或 call 調用JavaScript 中函數也是對象,對象則有方法,apply 和 call 就是函數對象的方法。這兩個方法異常強大,他們允許切換函數執行的上下文環境(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
在上面的例子中,我們使用構造函數生成了一個對象 p1,該對象同時具有 moveTo 方法;使用對象字面量創建了另一個對象 p2,我們看到使用 apply 可以將 p1 的方法 apply 到 p2 上,這時候 this 也被綁定到對象 p2 上。另一個方法 call 也具備同樣功能,不同的是最后的參數不是作為一個數組統一傳入,而是分開傳入的:
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); // 只是參數不同 console.log(p2.x,p2.y); //10 10.bind() 方法
ECMAScript 5 引入了 Function.prototype.bind 。調用 f.bind(someObject) 會創建一個與 f 具有相同函數體和作用域的函數,但是在這個新函數中,this 將永久地被綁定到了 bind 的第一個參數,無論這個函數是如何被調用的。
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
填坑
上面我們已經講了使用箭頭函數填 setTimeout 的坑,這次我們使用 bind 方法來試試:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 37 }.bind(Obj),1000) } }; Obj.getProp();
同樣可以填坑,但是看上去沒有使用箭頭函數來的優雅。
Vue實例里thisvue文檔里的原話:
All lifecycle hooks are called with their "this" context pointing to the Vue instance invoking it.
意思是:在Vue所有的生命周期鉤子方法(如created,mounted, updated以及destroyed)里使用this,this指向調用它的Vue實例。
示例分析
示例定義了兩個message。一個是全局變量,即window.message,它的值為英文“Hello!”。另外一個是vue實例的數據message,它的值為中文的“你好!”。
運行示例,在瀏覽器得到:
第一個輸出英文"Hello!”,第二個輸出中文“你好!”。這說明了showMessage1()里的this指的是window,而showMessage2()里的this指的是vue實例。
//created created: function() { this.showMessage1(); //this 1 this.showMessage2(); //this 2 }
created函數為vue實例的鉤子方法,它里面使用的this指的是vue實例。
//showMessage1() showMessage1:function(){ setTimeout(function() { document.getElementById("id1").innerText = this.message; //this 3 }, 10) }
對于普通函數(包括匿名函數),this指的是直接的調用者,在非嚴格模式下,如果沒有直接調用者,this指的是window。showMessage1()里setTimeout使用了匿名函數,this指向window。
//showMessage2() showMessage2:function() { setTimeout(() => { document.getElementById("id2").innerText = this.message; //this 4 }, 10) }
箭頭函數是沒有自己的this,在它內部使用的this是由它定義的宿主對象決定。showMessage2()里定義的箭頭函數宿主對象為vue實例,所以它里面使用的this指向vue實例。
綁定vue實例到this的方法為了避免this指向出現歧義,有兩種方法綁定this。
使用bindshowMessage1()可以改為:
showMessage1:function(){ setTimeout(function() { document.getElementById("id1").innerText = this.message; //this 3 }.bind(this), 10) }
對setTimeout()里的匿名函數使用bind()綁定到vue實例的this。這樣在匿名函數內的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面試題
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90281.html
摘要:手挽手帶你學入門二檔組件開發的開始,合理運用生命周期和組件,能夠讓你的開發變地流利又這篇文章帶你學會創建組件,運用組建。 手挽手帶你學React入門二檔,組件開發的開始,合理運用生命周期和組件,能夠讓你的開發變地流利又happy,這篇文章帶你學會創建組件,運用組建。學起來吧! React 組件生命周期 學習React,生命周期很重要,我們了解完生命周期的各個組件,對寫高性能組件會有很大...
摘要:上面的代碼,運行以后,我們可以看到因為的原型是指向的實例上的,所以可以訪問他的屬性值,那如果我不想讓訪問的構造函數里聲明的屬性值,那怎么辦呢只需要將指向的原型而不是實例就行了。 走在前端的大道上 本篇將自己讀過的相關 javascript原型和原型鏈 文章中,對自己有啟發的章節片段總結在這(會對原文進行刪改),會不斷豐富提煉總結更新。 文章——深入理解javascript之原型 一般的...
摘要:協程的判斷條件下面我們來著重看下的源碼,因為從這里開始就涉及到協程的判斷。第二點是關鍵點,用來判斷該方法的調用是否使用到了協程。原理我們先來看下使用協程是怎么寫的這是一個標準的協程寫法,然后我們再套用上面的條件,發現完全匹配不到。 第一眼看,跟我之前印象中的有點區別(也不知道是什么版本),return的時候居然...
摘要:是一個為測試工程師開發的部署框架,使用語言編寫,為了解決測試團隊在測試過程中的部署問題。部署執行方式簡單,支持命令行與自動化測試可緊密合作作為一個為測試工程師開發的部署框架,通過命令行進行自動化部署是第一選擇。 ...
閱讀 2458·2021-11-23 09:51
閱讀 1872·2021-10-13 09:40
閱讀 1384·2021-09-30 10:01
閱讀 594·2021-09-26 09:46
閱讀 2251·2021-09-23 11:55
閱讀 1395·2021-09-10 10:51
閱讀 2261·2021-09-09 09:33
閱讀 2234·2019-08-29 17:25