摘要:函數可計算某個字符串,并執行其中的的代碼男男成功啦實現了函數參數的傳遞,那么函數返回值怎么處理呢。而且,如果傳入的對象是,又該如何處理所以還需要再做一些工作處理返回值返回返回值男男判斷傳入對象的類型,如果為就指向對象。
本文共 1320 字,讀完只需 5 分鐘概述
JS 函數 call 和 apply 用來手動改變 this 的指向,call 和 apply 唯一的區別就在于函數參數的傳遞方式不同,call 是以逗號的形式,apply 是以數組的形式:
let person1 = { name: "person1", say: function(age, sex) { console.log(this.name + " age: " + age + " sex: " + sex); } } let person2 = { name: "person" } person1.say.call(person2, 20, "男"); person1.say.apply(person2, [20, "男"]);
本文就嘗試用其他方式來模擬實現 call 和 apply。
首先觀察 call 和 apply 有什么特點?
被函數調用(函數也是對象),相當于 call 和 apply 是函數的屬性
如果沒有傳入需要 this 指向對象,那么 this 指向全局對象
函數執行了
最后都改變了 this 的指向
一、初步實現基于 call 函數是調用函數的屬性的特點,call 的 this 指向調用函數,我們可以嘗試把調用函數的作為傳入的新對象的一個屬性,執行后,再刪除這個屬性就好了。
Function.prototype.newCall = function (context) { context.fn = this; // this 指的是 say 函數 context.fn(); delete context.fn; } var person = { name: "jayChou" }; var say = function() { console.log(this.name); } say.newCall(person); // jayChou
是不是就初步模擬實現了 call 函數呢,由于 call 還涉及到傳參的問題,所以我們進入到下一環節。
二、eval 方式在給對象臨時一個函數,并執行時,傳入的參數是除了 context 其余的參數。那么我們可以截取 arguments 參數數組的第一個后,將剩余的參數傳入臨時數組。
在前面我有講過函數 arguments 類數組對象的特點,arguments 是不支持數組的大多數方法, 但是支持for 循環來遍歷數組。
Function.prototype.newCall = function (context) { context.fn = this; let args = []; for(let i=1; i< arguments.length; i++) { args.push("arguments[" + i + "]"); } // args => [arguments[1], arguments[2], arguments[3], ...] context.fn(args.join(",")); // ??? delete context.fn; } var person = { name: "jayChou" }; var say = function(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); } say.newCall(person);
上面傳遞參數的方式最后肯定是失敗的,我們可以嘗試 eval 的方式,將參數添加子函數的作用域中。
eval() 函數可計算某個字符串,并執行其中的的 JavaScript 代碼
Function.prototype.newCall = function (context) { context.fn = this; let args = []; for(var i=1; i< arguments.length; i++) { args.push("arguments[" + i + "]"); } // args => [arguments[1], arguments[2], arguments[3], ...] eval("context.fn(" + args + ")"); delete context.fn; } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); } say.newCall(person, 18, "男"); // name: jayChou,age: 18, sex: 男
成功啦!
實現了函數參數的傳遞,那么函數返回值怎么處理呢。而且,如果傳入的對象是 null,又該如何處理?所以還需要再做一些工作:
Function.prototype.newCall = function (context) { if (typeof context === "object") { context = context || window } else { context = Object.create(null); } context.fn = this; let args = []; for(var i=1; i< arguments.length; i++) { args.push("arguments[" + i + "]"); } // args => [arguments[1], arguments[2], arguments[3], ...] var result = eval("context.fn(" + args + ")"); // 處理返回值 delete context.fn; return result; // 返回返回值 } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); return age + sex; } var check = say.newCall(person, 18, "男"); console.log(check); // 18男
判斷傳入對象的類型,如果為 null 就指向 window 對象。利用 eval 來執行字符串代碼,并返回字符串代碼執行的結果,就完成了模擬 call。
大功告成!
前面我們用的 eval 方式可以用 ES6 的解決還存在的一些問題,有沒有注意到,這段代碼是有問題的。
context.fn = this;
假如對象在被 call 調用前,已經有 fn 屬性怎么辦?
ES6 中提供了一種新的基本數據類型,Symbol,表示獨一無二的值,另外,Symbol 作為屬性的時候,不能使用點運算符。所以再加上 ES 的 rest 剩余參數替代 arguments 遍歷的工作就有:
Function.prototype.newCall = function (context,...params) { if (typeof context === "object") { context = context || window } else { context = Object.create(null); } let fn = Symbol(); context[fn] = this var result = context[fn](...params); delete context.fn; return result; } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); return age + sex; } var check = say.newCall(person, 18, "男"); console.log(check); // 18男四、apply
apply 和 call 的實現原理,基本類似,區別在于 apply 的參數是以數組的形式傳入。
Function.prototype.newApply = function (context, arr) { if (typeof context === "object") { context = context || window } else { context = Object.create(null); } context.fn = this; var result; if (!arr) { // 判斷函數參數是否為空 result = context.fn(); } else { var args = []; for (var i = 0; i < arr.length; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); } delete context.fn; return result; }
es6 實現
Function.prototype.newApply = function(context, parameter) { if (typeof context === "object") { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this; var result = context[fn](...parameter); delete context[fn]; return result; }總結
本文通過原生 JS 的 ES5 的方法和 ES 6 的方法模擬實現了 call 和 apply 的原理,旨在深入了解這兩個方法的用法和區別,希望你能有所收獲。
歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/98832.html
摘要:但是三作為構造函數時函數其實還有一個非常重要的特點返回的函數如果作為構造函數,搭配關鍵字出現的話,我們的綁定就需要被忽略。其次,當返回的函數作為構造函數時,之前綁定的會失效。 本文共 1100 字,讀完只需 4 分鐘 概述 前一篇文章我們嘗試模擬實現了 call 和 apply 方法,其實 bind 函數也可以用來改變 this 的指向。bind 和 call和 apply 兩者的區別...
摘要:模擬實現操作符構造函數返回結果創建一個空對象取傳入的第一個參數,即構造函數,并刪除第一個參數。二處理返回值構造函數也是函數,有不同類型返回值。有時候構造函數會返回指定的對象內容,所以要對這部分進行處理。 本文共 1230 字,讀完只需 5 分鐘 寫在前面 最近工作太忙,快接近兩周沒更新博客,總感覺有一些事情等著自己去做,雖然工作內容對自己提升挺大,但我總覺得,一直埋著頭走路,偶爾也...
摘要:寫在前面深入系列共計篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順底層知識的系列。深入系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計 15 篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順 JavaScript 底層知識的系列。重點講解了如原型、作用域、執行上下文、變量對象、this、...
摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調用自身的編程技巧稱為遞歸。然而非尾調用函數,就會創建多個執行上下文壓入執行上下文棧。所以我們只用把階乘函數改造成一個尾遞歸形式,就可以避免創建那么多的執行上下文。 JavaScript 專題系列第十八篇,講解遞歸和尾遞歸 定義 程序調用自身的編程技巧稱為遞歸(recursion)。 階乘 以階乘為例: function factorial(n...
閱讀 716·2021-11-16 11:44
閱讀 3548·2019-08-26 12:13
閱讀 3243·2019-08-26 10:46
閱讀 2357·2019-08-23 12:37
閱讀 1188·2019-08-22 18:30
閱讀 2532·2019-08-22 17:30
閱讀 1841·2019-08-22 17:26
閱讀 2292·2019-08-22 16:20