javascript當中的this詳解
- this是呼叫上下文,上下文被建立或者初始化時才確定
- 非嚴格模式:this是全域性物件;嚴格模式:this是undefined
- 函式呼叫
a. 以函式形式呼叫的函式通常不使用this關鍵字,非嚴格模式下函式呼叫模式的this就是全域性物件。
b. 以方法形式呼叫的函式的this指的是當前呼叫方法的物件
c. 以建構函式形式呼叫的函式的this指的是被構造的物件,具體操作為:
i. 建立一個新物件
ii. 將建構函式的作用域賦給新物件(因此建構函式中的this就指向這個新物件)
iii. 執行建構函式的程式碼(為這個新物件新增屬性,這個時候this才確定下來就是該物件)
iv. 返回新物件
v. 另外要注意的是,建構函式通常不使用return關鍵字,如果使用,則呼叫表示式的值就是return的這個物件,如果使用卻沒有指定返回值,或者返回一個原始值,那麼就忽略返回 值,同時使用構造出來的新物件作為返回結果。
4. this是一個關鍵字,js的語法不允許給this賦值,關鍵字this沒有作用域的限制,巢狀的函式不會從呼叫它的函式中繼承this。即:如果巢狀函式作為方法呼叫,其this的值指向呼叫它的物件(與規則3相符合)
如果巢狀函式作為函式呼叫,其this的值不是全域性物件(非嚴格模式)就是undefined(嚴格模式)。(與規則2、3符合)
例子:巢狀函式訪問外部函式的this(來自《javascript權威指南》P171)
var o={ //物件o m:function(){ //物件中的方法m() var self=this; //將this的值儲存至一個變數中 console.log(this===o); //輸出true,this就是這個物件 f(); //呼叫輔助函式f()(此時呼叫外部函式的m()時此處的f()呼叫巢狀函式就屬於函式呼叫了) function f(){ //定義一個巢狀函式 console.log(this===o); //"false":this的值是全域性物件或者undefined; console.log(self===o); //"true":self指外部函式的this值 } }
5. 建構函式的原型模式中的this
如果要在基本包裝型別String添加了一個名為startsWith()的方法。
String.prototype.startsWith=function(text){ return this.indexOf(text)==0; } var msg="hello world"; alert(msg.startsWith("Hello")); //true
這裡的原型函式中的this也是指向呼叫者的,要知道prototype中的這些方法和屬性是所有例項物件所共享的,也就是說當新建立的物件msg以方法呼叫模式呼叫startsWith()方法,雖然在例項中沒有找到這個方法,但在原型物件中找到了這個方法,故返回的就是這個方法的值,注意,在原型中查詢值的過程是一次過程。
6. 匿名函式中的this
匿名函式屬於函式表示式,不能變數提升,函式表示式必須等到js引擎執行到它所在行時,才會從上而下一行一行地解析,匿名函式的執行環境具有全域性性,因此其this物件通常指向window,來看( function(){…} )()和( function (){…} () )這兩種立即執行函式的寫法,要知道在函式體後面加括號就能立即呼叫,則這個函式必須是函式表示式,不能是函式宣告。
這樣的寫法有什麼用呢?
javascript中沒用私有作用域的概念,如果在多人開發的專案上,你在全域性或區域性作用域中聲明瞭一些變數,可能會被其他人不小心用同名的變數給覆蓋掉,根據javascript函式作用域鏈的特性,可以使用這種技術可以模仿一個私有作用域,用匿名函式作為一個“容器”,“容器”內部可以訪問外部的變數,而外部環境不能訪問“容器”內部的變數,所以( function(){…} )()內部定義的變數不會和外部的變數發生衝突,俗稱“匿名包裹器”或“名稱空間”。
例子:
- 箭頭函式中的this
let name='windows'; let obj={ name:'windows', stack: () => { console.log(this.name); //undefine } } console.log(obj.stack()); //undefine
說明:箭頭函式表示式的語法比函式表示式更短,並且沒有自己的this,arguments,super或者new.target。這些函式表示式更適用於那些本來需要匿名函式的地方,並且它們不能用作建構函式。箭頭函式不會建立自己的this,它只會從作用鏈的上一層繼承this。
function Stack() { this.age = 0; setInterval(() => { this.age++; // this正確地指向Stack物件 }, 1000); } var p = new Stack(); console.log(p.age);
箭頭函式不能用作構造器,和new一起會丟擲錯誤
let Foo = () => {}; let foo = new Foo(); // TypeError: Foo is not a constructor let Stack=()=> { let name='outside'; class Stack{ constructor(){ this.name=name; } } return Stack; }; let b=new Stack(); console.log(b.name);//Stack is not a constructor let Stack = () => { let name = 'outside'; console.log(this); class Stack { constructor() { this.name = name; } } return Stack; }; console.log(new Stack().name); //Stack is not a constructor
箭頭函式沒有prototype屬性
let Foo = () => {}; console.log(Foo.prototype); // undefined
- 箭頭函式用於閉包
let Stack = () => { let name = 'outside'; console.log(this);//Window class Stack { constructor() { //this.name = 'inside'; this.name = name; } } return new Stack(); }; console.log(Stack().name);//outside
let Stack = () => { let name = 'outside'; console.log(this);//Window class Stack { constructor() { this.name = 'inside'; } } return Stack; }; console.log(Stack().name);//Stack
- 匿名函式用於閉包
let Stack = function() { let name = 'outside'; console.log(this);//Window class Stack { constructor() { this.name = name; } } return Stack; }(); let b = new Stack(); console.log(b.name);//outside let Stack = function() { let name = 'outside'; console.log(this);//Window class Stack { constructor() { this.name = name; } } return Stack; }; //let b = new Stack(); let b = Stack(); let a=new b(); console.log(a.name);//outside