js中的 call()、apply()、bind()

之前在 javascript this 相关总结一文中说要对call()、apply()、bind() 要再写一篇总结详谈,今天把坑填起来。

先说call()和apply()

call() apply() 可以改变函数执行上下文中 this 的指向。当某个方法并不存在于某个对象, 可以借助call()或apply()在该对象上调用该方法。

一个最简单的例子

//有一个函数定义如下:
function sayHi( species,country) {
  console.log(`Hello I am ${species} ${this.name}, live in ${country}`);
}

//有一个对象pandaBob定义如下:
var pandaBob={name:'Bob'}

//对象pandaBob上并不存在sayHi(),但可借助call或apply在/pandaBob对象上调用sayHi()。
sayHi.call(pandaBob, 'panda', 'china');//Hello I am panda Bob, live in china
sayHi.apply(pandaBob, [ 'panda', 'china'])//Hello I am panda Bob, live in china

可以看到:对于 apply()、call() 二者而言,作用基本一样,只是接受参数的方式不太相同。
两者接收的第一个参数的作用都是指定要被调用的函数 运行时的 执行上下文的this

两者的区别在于:

  • 借用call调用某方法时,该方法的参数会通过call()的第一个之后的参数 以若干个参数的列表的形式传入。
  • 借用apply调用某方法时,该方法的参数会通过apply()的第二个参数 以一个包含多个参数的数组或类数组对象的形式传入。

注意:通常多是以数组或 arguments这个类数组对象的形式传入。事实上对其他类数组对象的支持是ES5 之后的事情了。也就是说ES5之前只能以数组或arguments的形式传入。

正因这点不同两者分别有各自更适合的应用场景。

call() 的使用场景及例子:

参数数量明确或者不需要参数时用建议用 call ,这样更简高效。

举几个例子:

  1. 在继承中使用call,调用父函数的构造函数。(参数数量确定)
function Product(name, price) {
  this.name = name;
  this.price = price;

  if (price < 0) {
    throw RangeError('Cannot create product ' +this.name + ' with a negative price');
  }
}

function Food(caloriesPerGram,name, price) {

  Product.call(this, name, price); 
}
var cheese = new Food('feta', 5);

其实上例中的子类的构造函数 如果不需要传入更多的的参入(即caloriesPerGram)或者把额外的参数放在 name, price之后还可以这么用:

function Food(name, price,caloriesPerGram) {
  this.category = 'food';
  this.caloriesPerGram=caloriesPerGram;
  Product.apply(this, arguments); 
}

这种情况下用call()可以适应更灵活的参数列表。

2.使用call方法调用匿名函数。(参数数量明确)

var animals = [
  {species: 'Lion', name: 'King'},
  {species: 'Whale', name: 'Fail'}
];

for (var i = 0; i < animals.length; i++) {
  (function (i) { 
    this.print = function () { 
      console.log('#' + i  + ' ' + this.species + ': ' + this.name); 
    } 
    this.print();
  }).call(animals[i], i);
}

3.安全的类型检查(不需要参数)

function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}

4.使用call方法调用函数并且指定上下文的'this'(不需要参数)

function greet() {
  var reply = [this.person, 'Is An Awesome', this.role].join(' ');
  console.log(reply);
}

var i = {
  person: 'Douglas Crockford', role: 'Javascript Developer'
};

greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer

这几个例子 归根结底都是为了指定在函数被调用时执行上下文中 this 的指向。

apply() 的使用场景和例子

参数数量不明确时只能用 apply(),然后把参数 push 进数组传递进去。当然 函数内部也可以通过 arguments 这个类数组对象来遍历所有的参数。

1.redux 的 bindActionCreators()中的bindActionCreator()定义如下

//ES6 写法
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}
//被bable 转译到ES5后
function bindActionCreator(actionCreator, dispatch) {
  return function () {
    return dispatch(actionCreator.apply(undefined, arguments));
  };
}

因为被返回的函数调用时传入的参数个数不确定,所以这里只能用apply().
要注意该例中的arguments 是类数组对象。并不是数组(并不是Array 的实例)

对数组或类数组对象, 用apply()调用参数列表与之相对应的已存在函数。

1.数组之间追加

var array1 = [12 , "foo" , {name "Joe"} , -2458]; 
var array2 = ["Doe" , 555 , 100]; 
Array.prototype.push.apply(array1, array2); 
/* array1 值为  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

2.获取数组中的最大值和最小值

var numbers = [5, 6, 2, 3, 7]
var max = Math.max.apply(null, numbers); 
var min = Math.min.apply(null, numbers);

/*若不用apply() 就要做复杂的数组遍历*/
max = -Infinity, min = +Infinity;
for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max)
    max = numbers[i];
  if (numbers[i] < min) 
    min = numbers[i];
}

/*ES6中也可以使用数组扩展运算符配合call()代替,但还是略麻烦*/
var max = Math.max.call(null, ...numbers); 

再谈bind()

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

bind 和与其他两者有个重要区别: bind 是在函数定义阶段指定函数执行上下文的this指向,绑定之后不会立即去调用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容