成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

理解 JavaScript call()/apply()/bind()

duan199226 / 3130人閱讀

摘要:理解文章中已經(jīng)比較全面的分析了在中的指向問題,用一句話來總結(jié)就是的指向一定是在執(zhí)行時(shí)決定的,指向被調(diào)用函數(shù)的對(duì)象。與和直接執(zhí)行原函數(shù)不同的是,返回的是一個(gè)新函數(shù)。這個(gè)新函數(shù)包裹了原函數(shù),并且綁定了的指向?yàn)閭魅氲摹?/p>

理解 JavaScript this 文章中已經(jīng)比較全面的分析了 this 在 JavaScript 中的指向問題,用一句話來總結(jié)就是:this 的指向一定是在執(zhí)行時(shí)決定的,指向被調(diào)用函數(shù)的對(duì)象。當(dāng)然,上篇文章也指出可以通過 call() / apply() / bind() 這些內(nèi)置的函數(shù)方法來指定 this 的指向,以達(dá)到開發(fā)者的預(yù)期,而這篇文章將進(jìn)一步來討論這個(gè)問題。

先來回顧一下,舉個(gè)簡(jiǎn)單的例子:

var leo = {
  name: "Leo",
  sayHi: function() {
    return "Hi! I"m " + this.name;
  }
};

var neil = {
  name: "Neil"
};

leo.sayHi(); // "Hi! I"m Leo"
leo.sayHi.call(neil); // "Hi! I"m Neil"
基本用法

在 JavaScript 中,函數(shù)也是對(duì)象,所以 JS 的函數(shù)有一些內(nèi)置的方法,就包括 call(), apply() 和 bind(),它們都定義在 Function 的原型上,所以每一個(gè)函數(shù)都可以調(diào)用這 3 個(gè)方法。

Function.prototype.call(thisArg [, arg1 [, arg2, ...]]),對(duì)于 call() 而言,它的第一個(gè)參數(shù)為需要綁定的對(duì)象,也就是 this 指向的對(duì)象,比如今天的引例中就是這樣。

第一個(gè)參數(shù)也可以是 null 和 undefined,在嚴(yán)格模式下 this 將指向?yàn)g覽器中的 window 對(duì)象或者是 Node.js 中的 global 對(duì)象。

var leo = {
  name: "Leo",
  sayHi: function() {
    return "Hi! I"m " + this.name;
  }
};

leo.sayHi.call(null); // "Hi! I"m undefined"

▲ this 指向 window,window.name 沒有定義

除了第一個(gè)參數(shù),call() 還可以選擇接收剩下任意多的參數(shù),這些參數(shù)都將作為調(diào)用函數(shù)的參數(shù),來看一下:

function add(a, b) {
  return a + b;
}

add.call(null, 2, 3); // 5

▲ 等同于 add(2, 3)

apply() 的用法和 call() 類似,唯一的區(qū)別是它們接收參數(shù)的形式不同。除了第一個(gè)參數(shù)外,call() 是以枚舉的形式傳入一個(gè)個(gè)的參數(shù),而 apply() 是傳入一個(gè)數(shù)組。

function add(a, b) {
  return a + b;
}

add.apply(null, [2, 3]); // 5

注意:apply() 接受的第二個(gè)參數(shù)為數(shù)組(也可以是一個(gè)類數(shù)組對(duì)象),但不意味著調(diào)用它的函數(shù)接收的是數(shù)組參數(shù)。這里的 add() 函數(shù)依舊是 a 和 b 兩個(gè)參數(shù),分別賦值為 2 和 3,而不是 a 被賦值為 [2, 3]。

接下來說說 bind(),它和另外兩個(gè)大有區(qū)別。

var leo = {
  name: "Leo",
  sayHi: function() {
    return "Hi! I"m " + this.name;
  }
};
var neil = {
  name: "Neil"
};
var neilSayHi = leo.sayHi.bind(neil);
console.log(typeof neilSayHi); // "function"
neilSayHi(); // "Hi! I"m Neil"

與 call() 和 apply() 直接執(zhí)行原函數(shù)不同的是,bind() 返回的是一個(gè)新函數(shù)。簡(jiǎn)單說,bind() 的作用就是將原函數(shù)的 this 綁定到指定對(duì)象,并返回一個(gè)新的函數(shù),以延遲原函數(shù)的執(zhí)行,這在異步流程中(比如回調(diào)函數(shù),事件處理程序)具有很強(qiáng)大的作用。你可以將 bind() 的過程簡(jiǎn)單的理解為:

function bind(fn, ctx) {
  return function() {
    fn.apply(ctx, arguments);
  };
}
如何實(shí)現(xiàn)

這一部分應(yīng)該是經(jīng)常出現(xiàn)在面試中。最常見的應(yīng)該是 bind() 的實(shí)現(xiàn),就先來說說如何實(shí)現(xiàn)自己的 bind()。

◆ bind() 的實(shí)現(xiàn)

上一節(jié)已經(jīng)簡(jiǎn)單地實(shí)現(xiàn)了一個(gè) bind(),稍作改變,為了和內(nèi)置的 bind() 區(qū)別,我么自己實(shí)現(xiàn)的函數(shù)叫做 bound(),先看一下:

Function.prototype.bound = function(ctx) {
  var fn = this;
  return function() {
    return fn.apply(ctx);
  };
}

這里的 bound() 模擬了一個(gè)最基本的 bind() 函數(shù)的實(shí)現(xiàn),即返回一個(gè)新函數(shù)。這個(gè)新函數(shù)包裹了原函數(shù),并且綁定了 this 的指向?yàn)閭魅氲?ctx。

對(duì)于內(nèi)置的 bind() 來說,它還有一個(gè)特點(diǎn):

var student = { id: "2015" };

function showDetail (name, major) {
    console.log("The id " + this.id +
                " is for " + name +
                ", who major in " + major);
}

showDetail.bind(student, "Leo")("CS");
// "The id 2015 is for Leo, who major in CS"

showDetail.bind(student, "Leo", "CS")();
// "The id 2015 is for Leo, who major in CS"

在這里兩次調(diào)用參數(shù)傳遞的方式不同,但是具有同樣的結(jié)果。下面,就繼續(xù)完善我們自己的 bound() 函數(shù)。

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  var fn = this;
  var _args = slice.call(arguments, 1);
  return function() {
    var args = _args.concat(slice.call(arguments));
    return fn.apply(ctx, args);
  };
}

這里需要借助 Array.prototype.slice() 方法,它可以將 arguments 類數(shù)組對(duì)象轉(zhuǎn)為數(shù)組。我們用一個(gè)變量保存?zhèn)魅?bound() 的除第一個(gè)參數(shù)以外的參數(shù),在返回的新函數(shù)中,將傳入新函數(shù)的參數(shù)與 bound() 中的參數(shù)合并。

其實(shí),到現(xiàn)在整個(gè) bound() 函數(shù)的實(shí)現(xiàn)都離不開閉包,你可以查看文章 理解 JavaScript 閉包。

在文章 理解 JavaScript this 中,我們提到 new 也能改變 this 的指向,那如果 new 和 bind() 同時(shí)出現(xiàn),this 會(huì)聽從誰?

function Student() {
  console.log(this.name, this.age);
}

Student.prototype.name = "Neil";
Student.prototype.age = 20;

var foo = Student.bind({ name: "Leo", age: 21 });
foo(); // "Leo" 21

new foo(); // "Neil" 20

從例子中已經(jīng)可以看出,使用 new 改變了 bind() 已經(jīng)綁定的 this 指向,而我們自己的 bound() 函數(shù)則不會(huì):

var foo = Student.bound({ name: "Leo", age: 21 });
foo(); // "Leo" 21

new foo(); // "Leo" 21

所以我們還要接著改進(jìn) bound() 函數(shù)。要解決這個(gè)問題,我們需要清楚原型鏈以及 new 的原理,在后面的文章中我再來分析,這里只提供解決方案。

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  if (typeof this !== "function") {
    throw TypeError("Function.prototype.bound - what is trying to be bound is not callable");
  }
  var fn = this;
  var _args = slice.call(arguments);
  var fBound = function() {
    var args = _args.concat(slice.call(arguments));

    // 在綁定原函數(shù) fn 時(shí)增加一次判斷,如果 this 是 fBound 的一個(gè)實(shí)例
    // 那么此時(shí) fBound 的調(diào)用方式一定是 new 調(diào)用
    // 所以,this 直接綁定 this(fBound 的實(shí)例對(duì)象) 就好
    // 否則,this 依舊綁定到我們指定的 ctx 上
    return fn.apply(this instanceof fBound ? this : ctx, args);
  };

  // 這里我們必須要聲明 fBound 的 prototype 指向?yàn)樵瘮?shù) fn 的 prototype
  fBound.prototype = Object.create(fn.prototype);

  return fBound;
}

大功告成。如果看不懂最后一段代碼,可以先放一放,后面的文章會(huì)分析原型鏈和 new 的原理。

◆ call() 的實(shí)現(xiàn)
function foo() {
  console.log(this.bar);
}
var obj = { bar: "baz" };
foo.call(obj); // "baz"

我們觀察 call 的調(diào)用,存在下面的特點(diǎn):

當(dāng)函數(shù) foo 調(diào)用 call,并傳入 obj 時(shí),似乎是在 obj 的原型上增加了一個(gè) foo 方法。

foo.call() 除第一個(gè)參數(shù)外的所有參數(shù)都應(yīng)該傳給 foo(),這一點(diǎn)在實(shí)現(xiàn) bind() 時(shí)已處理過。

不能對(duì) foo 和 obj 做任何修改。

那就來看看,以示區(qū)別,我們自己實(shí)現(xiàn)的 call 叫做 calling。

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  ctx.fn();
}

我們完成了第一步。

在完成第二步時(shí),我們需要用到 eval(),它可以執(zhí)行一段字符串類型的 JavaScript 代碼。

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push("arguments[" + i + "]");
  }
  eval("ctx.fn(" + args + ")");
}

這里我們避免采用和實(shí)現(xiàn) bind() 同樣的方法獲取剩余參數(shù),因?yàn)橐褂玫?call,所以這里采用循環(huán)。我們需要一個(gè)一個(gè)的將參數(shù)傳入 ctx.fn(),所以就用到 eval(),這里的 eval() 中的代碼在做 + 運(yùn)算時(shí),args 會(huì)發(fā)生類型轉(zhuǎn)換,自動(dòng)調(diào)用 toString() 方法。

實(shí)現(xiàn)到這里,大部分的功能以及完成,但是我們不可避免的為 ctx 手動(dòng)添加了一個(gè) fn 方法,改變了 ctx 本身,所以要把它給刪除掉。另外,call 應(yīng)該有返回值,且它的值是 fn 執(zhí)行過后的結(jié)果,并且如果 ctx 傳入 null 或者 undefined,應(yīng)該將 this 綁定到全局對(duì)象。我們可以得到下面的代碼:

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push("arguments[" + i + "]");
  }
  var result = eval("ctx.fn(" + args + ")");
  delete ctx.fn;
  return result;
}
◆ apply() 的實(shí)現(xiàn)

apply() 的實(shí)現(xiàn)與 call() 類似,只是參數(shù)的處理不同,直接看代碼吧。

var slice = Array.prototype.slice;

Function.prototype.applying = function(ctx, arr) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var result = null;
  var args = [];
  if (!arr) {
    result = ctx.fn();
  } else {
    for (var i = 1; i < args.length; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("ctx.fn(" + args + ")");
  }
  delete ctx.fn;
  return result;
}
小結(jié)

這篇文章在上一篇文章的基礎(chǔ)上,更進(jìn)一步地討論了 call() / apply() / bind() 的用法以及實(shí)現(xiàn),其中三者的區(qū)別和 bind() 的實(shí)現(xiàn)是校招面試的??键c(diǎn),初次接觸可能有點(diǎn)難理解 bind(),因?yàn)樗婕暗介]包、new 以及原型鏈。

我會(huì)在接下來的文章中介紹對(duì)象、原型以及原型鏈、繼承、new 的實(shí)現(xiàn)原理,敬請(qǐng)期待。

本文原文發(fā)布在公眾號(hào) cameraee,點(diǎn)擊查看

文章參考

Function.prototype.call() / apply() / bind() | MDN

Invoking JavaScript Functions With "call" and "apply" | A Drop of JavaScript

Implement your own - call(), apply() and bind() method in JavaScript | Ankur Anand

JavaScript .call() .apply() and .bind() - explained to a total noob | Owen Yang

JavaScript call() & apply() vs bind()? | Stack Overflow

Learn & Solve: call(), apply() and bind() methods in JavaScript

JavaScript 系列文章

理解 JavaScript this

理解 JavaScript 閉包

理解 JavaScript 執(zhí)行棧

理解 JavaScript 作用域

理解 JavaScript 數(shù)據(jù)類型與變量

Be Good. Sleep Well. And Enjoy.

前端技術(shù) | 個(gè)人成長(zhǎng)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100639.html

相關(guān)文章

  • 理解JavaScript中的call,applybind方法

    摘要:輸出的作用與和一樣,都是可以改變函數(shù)運(yùn)行時(shí)上下文,區(qū)別是和在調(diào)用函數(shù)之后會(huì)立即執(zhí)行,而方法調(diào)用并改變函數(shù)運(yùn)行時(shí)上下文后,返回一個(gè)新的函數(shù),供我們需要時(shí)再調(diào)用。 前言 js中的call(), apply()和bind()是Function.prototype下的方法,都是用于改變函數(shù)運(yùn)行時(shí)上下文,最終的返回值是你調(diào)用的方法的返回值,若該方法沒有返回值,則返回undefined。這幾個(gè)方法...

    chaosx110 評(píng)論0 收藏0
  • 理解javascriptapply( ), call( ), bind( )

    摘要:,,和都是用來改變函數(shù)執(zhí)行時(shí)的上下文也就是說改變的指向問題,是的方法,引入是因?yàn)闆]有將設(shè)置成行參。一般都是庫(kù)里面用不推薦自己使用和。和唯一區(qū)別是參數(shù)不一樣,是的語法糖是返回一個(gè)新函數(shù)供以后調(diào)用,相比其他兩個(gè)比較常用。而和是立即調(diào)用。 apply(),call(),和bind()都是用來改變函數(shù)執(zhí)行時(shí)的上下文也就是說改變this的指向問題,是prototype的方法,引入是因?yàn)閖s沒有將...

    LiveVideoStack 評(píng)論0 收藏0
  • JavaScript函數(shù)的call,applybind

    摘要:它們有明確的和成員函數(shù)的定義,只有的實(shí)例才能調(diào)用這個(gè)的成員函數(shù)。用和調(diào)用函數(shù)里用和來指定函數(shù)調(diào)用的,即指針的指向。同樣,對(duì)于一個(gè)后的函數(shù)使用或者,也無法改變它的執(zhí)行,原理和上面是一樣的。 函數(shù)里的this指針 要理解call,apply和bind,那得先知道JavaScript里的this指針。JavaScript里任何函數(shù)的執(zhí)行都有一個(gè)上下文(context),也就是JavaScri...

    alighters 評(píng)論0 收藏0
  • ES5 call,apply,bind方法總結(jié)(包括理解this的指向問題)

    總結(jié)call,apply,bind方法的理解使用和區(qū)別。 call,apply,bind這三個(gè)方法在JavaScript中是用來改變函數(shù)調(diào)用的this指向。那么改變函數(shù)this指向有什么用呢?我們先來看一段代碼 var a= { name:harden, fn:function () { console.log(this.name); } } var b =...

    nanchen2251 評(píng)論0 收藏0
  • javascript高級(jí)程序設(shè)計(jì)》函數(shù)調(diào)用模式 & this深度理解

    在上一篇文章(《javascript高級(jí)程序設(shè)計(jì)》筆記:Function類型)中稍微提及了一下函數(shù)對(duì)象的屬性—this,在這篇文章中有深入的說明: 函數(shù)的三種簡(jiǎn)單調(diào)用模式 1 函數(shù)模式 定義的函數(shù),如果單獨(dú)調(diào)用,不將其與任何對(duì)象關(guān)聯(lián),那么就是函數(shù)調(diào)用模式 function fn(num1, num2) { console.log(this); } // 直接在全局調(diào)用 fn();// w...

    wyk1184 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<