摘要:深入系列第十篇,通過和的模擬實現,帶你揭開和改變的真相一句話介紹方法在使用一個指定的值和若干個指定的參數值的前提下調用某個函數或方法。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。
callJavaScript深入系列第十篇,通過call和apply的模擬實現,帶你揭開call和apply改變this的真相
一句話介紹 call:
call() 方法在使用一個指定的 this 值和若干個指定的參數值的前提下調用某個函數或方法。
舉個例子:
var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call(foo); // 1
注意兩點:
call 改變了 this 的指向,指向到 foo
bar 函數執行了
模擬實現第一步那么我們該怎么模擬實現這兩個效果呢?
試想當調用 call 的時候,把 foo 對象改造成如下:
var foo = { value: 1, bar: function() { console.log(this.value) } }; foo.bar(); // 1
這個時候 this 就指向了 foo,是不是很簡單呢?
但是這樣卻給 foo 對象本身添加了一個屬性,這可不行吶!
不過也不用擔心,我們用 delete 再刪除它不就好了~
所以我們模擬的步驟可以分為:
將函數設為對象的屬性
執行該函數
刪除該函數
以上個例子為例,就是:
// 第一步 foo.fn = bar // 第二步 foo.fn() // 第三步 delete foo.fn
fn 是對象的屬性名,反正最后也要刪除它,所以起成什么都無所謂。
根據這個思路,我們可以嘗試著去寫第一版的 call2 函數:
// 第一版 Function.prototype.call2 = function(context) { // 首先要獲取調用call的函數,用this可以獲取 context.fn = this; context.fn(); delete context.fn; } // 測試一下 var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call2(foo); // 1
正好可以打印 1 哎!是不是很開心!(~ ̄▽ ̄)~
模擬實現第二步最一開始也講了,call 函數還能給定參數執行函數。舉個例子:
var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call(foo, "kevin", 18); // kevin // 18 // 1
注意:傳入的參數并不確定,這可咋辦?
不急,我們可以從 Arguments 對象中取值,取出第二個到最后一個參數,然后放到一個數組里。
比如這樣:
// 以上個例子為例,此時的arguments為: // arguments = { // 0: foo, // 1: "kevin", // 2: 18, // length: 3 // } // 因為arguments是類數組對象,所以可以用for循環 var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push("arguments[" + i + "]"); } // 執行后 args為 [foo, "kevin", 18]
不定長的參數問題解決了,我們接著要把這個參數數組放到要執行的函數的參數里面去。
// 將數組里的元素作為多個參數放進函數的形參里 context.fn(args.join(",")) // (O_o)?? // 這個方法肯定是不行的啦!!!
也許有人想到用 ES6 的方法,不過 call 是 ES3 的方法,我們為了模擬實現一個 ES3 的方法,要用到ES6的方法,好像……,嗯,也可以啦。但是我們這次用 eval 方法拼成一個函數,類似于這樣:
eval("context.fn(" + args +")")
這里 args 會自動調用 Array.toString() 這個方法。
所以我們的第二版克服了兩個大問題,代碼如下:
// 第二版 Function.prototype.call2 = function(context) { context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push("arguments[" + i + "]"); } eval("context.fn(" + args +")"); delete context.fn; } // 測試一下 var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call2(foo, "kevin", 18); // kevin // 18 // 1
(??????)??
模擬實現第三步模擬代碼已經完成 80%,還有兩個小點要注意:
1.this 參數可以傳 null,當為 null 的時候,視為指向 window
舉個例子:
var value = 1; function bar() { console.log(this.value); } bar.call(null); // 1
雖然這個例子本身不使用 call,結果依然一樣。
2.函數是可以有返回值的!
舉個例子:
var obj = { value: 1 } function bar(name, age) { return { value: this.value, name: name, age: age } } console.log(bar.call(obj, "kevin", 18)); // Object { // value: 1, // name: "kevin", // age: 18 // }
不過都很好解決,讓我們直接看第三版也就是最后一版的代碼:
// 第三版 Function.prototype.call2 = function (context) { var context = context || window; context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push("arguments[" + i + "]"); } var result = eval("context.fn(" + args +")"); delete context.fn return result; } // 測試一下 var value = 2; var obj = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.call(null); // 2 console.log(bar.call2(obj, "kevin", 18)); // 1 // Object { // value: 1, // name: "kevin", // age: 18 // }
到此,我們完成了 call 的模擬實現,給自己一個贊 b( ̄▽ ̄)d
apply的模擬實現apply 的實現跟 call 類似,在這里直接給代碼,代碼來自于知乎 @鄭航的實現:
Function.prototype.apply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")") } delete context.fn return result; }下一篇文章
JavaScript深入之bind的模擬實現
重要參考知乎問題 不能使用call、apply、bind,如何用 js 實現 call 或者 apply 的功能?
深入系列JavaScript深入系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列預計寫十五篇左右,旨在幫大家捋順JavaScript底層知識,重點講解如原型、作用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91675.html
摘要:也就是說當返回的函數作為構造函數的時候,時指定的值會失效,但傳入的參數依然生效。構造函數效果的優化實現但是在這個寫法中,我們直接將,我們直接修改的時候,也會直接修改函數的。 JavaScript深入系列第十一篇,通過bind函數的模擬實現,帶大家真正了解bind的特性 bind 一句話介紹 bind: bind() 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數...
摘要:深入系列第十二篇,通過的模擬實現,帶大家揭開使用獲得構造函數實例的真相一句話介紹運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象類型之一也許有點難懂,我們在模擬之前,先看看實現了哪些功能。 JavaScript深入系列第十二篇,通過new的模擬實現,帶大家揭開使用new獲得構造函數實例的真相 new 一句話介紹 new: new 運算符創建一個用戶定義的對象類型的實例或具...
摘要:第一版首先要獲取調用的函數,用可以獲取的指向為,因為是的實例相當于把掛載到上,所以可以取到測試一下但是第一版不可以傳遞多個參數第二版這里會自動調用這個方法。 // 第一版 Function.prototype.call2 = function(context) { // 首先要獲取調用call的函數,用this可以獲取 // this的指向為bar,因為bar是Func...
摘要:寫在前面深入系列共計篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順底層知識的系列。深入系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計 15 篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順 JavaScript 底層知識的系列。重點講解了如原型、作用域、執行上下文、變量對象、this、...
摘要:之前文章詳細介紹了的使用,不了解的查看進階期。不同的引擎有不同的限制,核心限制在,有些引擎會拋出異常,有些不拋出異常但丟失多余參數。存儲的對象能動態增多和減少,并且可以存儲任何值。這邊采用方法來實現,拼成一個函數。 之前文章詳細介紹了 this 的使用,不了解的查看【進階3-1期】。 call() 和 apply() call() 方法調用一個函數, 其具有一個指定的 this 值和分...
閱讀 1951·2021-09-07 10:24
閱讀 2087·2019-08-30 15:55
閱讀 2038·2019-08-30 15:43
閱讀 671·2019-08-29 15:25
閱讀 1044·2019-08-29 12:19
閱讀 1927·2019-08-23 18:32
閱讀 1515·2019-08-23 17:59
閱讀 947·2019-08-23 12:22