文章首發於 我的博客
記得差很少在兩年多以前寫過一篇文章 兩句話理解js中的this,當時總結的兩句話原話是這樣的:前端
{}
構成做用域,對象的{}
以及 if(){}
都不構成做用域;當時對this的內部原理什麼的都理解的不是很深入,就只能憑藉遇到不少坑以後,總結了出了那時候本身用來判斷的標準。這裏會再次略微深刻的說一下。思路仍是圍繞上面總結的那兩句話。git
var a = 'luckyStar'; function foo() { console.log(this.a); } foo(); // luckyStar
foo()直接調用非嚴格模式下是this是指向 window上的,嚴格模式 this 指向的是undefined;github
var a = 'luckyStar'; var obj = { a: 'litterStar', foo() { console.log(this.a); } } obj.foo(); // ① // litterStar var bar = obj.foo; bar(); // ② // luckyStar setTimeout(obj.foo, 100); // ③ // luckyStar
位置①,obj.foo(),是obj經過.
運算符調用了 foo(),因此指向的值 obj。
位置②,是把 obj.foo賦值給了 bar,其實是把 foo函數賦值給了bar, bar() 調用的時候,沒有調用者,因此使用的是默認綁定規則。
位置③,是把 obj.foo賦值給了 setTimeout,實際上調用的仍是 foo函數,調用的時候,沒有調用者,因此使用的是默認綁定規則。面試
位置②和位置 位置③ 的必定要注意。c#
function foo() { console.log(this.name); } const obj = { name: 'litterStar' } const bar = function() { foo.call(obj); } bar(); // litterStar
使用 call,apply能夠顯式修改 this的指向,下面會詳細介紹該部分。數組
function Foo(name) { this.name = name; } var luckyStar = new Foo('luckyStar'); luckyStar.name; // luckyStar
要解釋上面的結果就要從 new 的過程提及了微信
function _new(constructor, ...arg) { // ① 建立一個新的空對象 obj const obj = {}; // ② 將新對象的的原型指向當前函數的原型 obj.__proto__ = constructor.prototype; // ③ 新建立的對象綁定到當前this上 const result = constructor.apply(obj, arg); // ④ 若是沒有返回其餘對象,就返回 obj,不然返回其餘對象 return typeof result === 'object' ? result : obj; } function Foo(name) { this.name = name; } var luckyStar = _new(Foo, 'luckyStar'); luckyStar.name; //luckyStar
箭頭函數中其實沒有 this 綁定,由於箭頭函數中this指向函數所在的所用域。箭頭函數不能做爲構造函數app
const obj = { name: 'litterStar', say() { console.log(this.name); }, read: () => { console.log(this.name); } } obj.say(); // litterStar obj.read(); // undefined
call,apply,bind 這三個函數是 Function原型上的方法 Function.prototype.call()
,Function.prototype.apply
,Function.prototype.bind()
,全部的函數都是 Funciton
的實例,所以全部的函數能夠調用call,apply,bind 這三個方法。函數
call,apply,bind 這三個方法的第一個參數,都是this。若是你使用的時候不關心 this是誰的話,能夠直接設置爲 nullpost
可是有了 ES6引入的 ...
展開運算符,其實不少狀況下使用 call和apply沒有什麼太大的區別。
舉個例子,找到數組中最大的值
const arr = [1, 2, 3, 5]; Math.max.call(null, ...arr); Math.max.apply(null, arr);
Math.max
是數字的方法,數組上並無,可是咱們能夠經過 call, apply 來使用 Math.max
方法來計算當前數組的最大值。
實現一個call:
Function.prototype.myCall = function(thisArg = window) { // thisArg.fn 指向當前函數 fn (fn.myCall) thisArg.fn = this; // 第一個參數爲 this,因此要取剩下的參數 const args = [...arguments].slice(1); // 執行函數 const result = thisArg.fn(...args); // thisArg上並不存在fn,因此須要移除 delete thisArg.fn; return result; } function foo() { console.log(this.name); } const obj = { name: 'litterStar' } const bar = function() { foo.myCall(obj); } bar(); // litterStar
實現一個apply
過程很call相似,只是參數不一樣,再也不贅述
Function.prototype.myApply = function(thisArg = window) { thisArg.fn = this; let result; // 判斷是否有第二個參數 if(arguments[1]) { // apply方法調用的時候第二個參數是數組,因此要展開arguments[1]以後再傳入函數 result = thisArg.fn(...arguments[1]); } else { result = thisArg.fn(); } delete thisArg.fn; return result; } function foo() { console.log(this.name); } const obj = { name: 'litterStar' } const bar = function() { foo.myApply(obj); } bar(); // litterStar
實現一個bind
MDN上的解釋:bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。
Function.prototype.myBind = function(thisArg) { // 保存當前函數的this const fn = this; // 保存原先的參數 const args = [...arguments].slice(1); // 返回一個新的函數 return function() { // 再次獲取新的參數 const newArgs = [...arguments]; /** * 1.修改當前函數的this爲thisArg * 2.將屢次傳入的參數一次性傳入函數中 */ return fn.apply(thisArg, args.concat(newArgs)) } } const obj1 = { name: 'litterStar', getName() { console.log(this.name) } } const obj2 = { name: 'luckyStar' } const fn = obj1.getName.myBind(obj2) fn(); // luckyStar
手寫部分的代碼大部分參考了網上比較多的一些寫法。手寫代碼的前提是必定要搞清楚這個函數是什麼,怎麼用,幹了什麼。
最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。