在js中call()、apply()与bind()的理解分析

一、call() 方法

  1. call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
  2. 语法格式
    fun.call(thisArg[,arg1[, arg2[, ...]]])
  3. 参数说明
    1)thisArg
    在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数在非严格模式下运行,则指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象
    2)arg1, arg2, …
    指定的参数列表
  4. 返回值
    使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
  5. 描述
    1)call() 允许为不同的对象分配和调用属于一个对象的函数/方法
    2)call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)
  6. 实例说明
    1)使用 call 方法调用父构造函数
    代码如下:
function Product(name, price) {
  this.name = name;
  this.price = price;
}

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


function Toy(name, price) {
  Product.call(this, name, price);
  this.category = 'toy';
              }
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

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)使用 call 方法调用函数并且指定上下文的 “this”
代码如下:

function greet() {
  var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
  console.log(reply);
}

var obj = {
  animal: 'cats', sleepDuration: '12 and 16 hours'
};

greet.call(obj);  // cats typically sleep between 12 and 16 hours

4)使用 call 方法调用函数并且不指定第一个参数(argument)
代码如下:

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen

5)在严格模式下,this 的值将会是 undefined
代码如下:

'use strict';
var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call(); // Cannot read the property of 'sData' of undefined

二、apply()方法

  1. apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
  2. call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
  3. 语法格式
    fun.apply(thisArg[, argsArray])
  4. 参数说明
    1)thisArg
    可选的,在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
    2)argsArray
    可选的,一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
  5. 返回值
    调用有指定this值和参数的函数的结果
  6. 描述
    1)在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
    2)apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),或数组对象。
    3)可以使用 arguments对象作为 argsArray 参数, arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象 ,被调用对象接下来就负责处理这些参数。
    4)可以使用任何种类的类数组对象,就是说只要有一个 length 属性和(0…length-1)范围的整数属性,但是Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
  7. 实例说明
    1)用 apply 将数组添加到另一个数组
    代码如下:
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

2)使用apply和内置函数
代码如下:

/* 找出数组中最大/小的数字 */
var numbers = [5, 6, 2, 3, 7];

/* 应用(apply) Math.min/Math.max 内置函数完成 */
var max = Math.max.apply(null, numbers); /* 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers);

/* 代码对比: 用简单循环完成 */
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];
}

3)使用apply来链接构造器
代码如下:

Function.prototype.construct = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};

4)替换Object.create()方法
a. Using Object.proto
代码如下:

Function.prototype.construct = function (aArgs) {
  var oNew = {};
  oNew.__proto__ = this.prototype;
  this.apply(oNew, aArgs);
  return oNew;
};

b. 使用闭包
代码如下:

Function.prototype.construct = function(aArgs) {
  var fConstructor = this, fNewConstr = function() {
    fConstructor.apply(this, aArgs);
  };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
};

c. 使用 Function 构造器
代码如下:

Function.prototype.construct = function (aArgs) {
  var fNewConstr = new Function("");
  fNewConstr.prototype = this.prototype;
  var oNew = new fNewConstr();
  this.apply(oNew, aArgs);
  return oNew;
};

三、bind()方法

  1. bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
  2. 语法格式
    fun.bind(thisArg[, arg1[, arg2[, ...]]])
  3. 参数说明
    1)thisArg
    调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。
    2)arg1, arg2, …
    当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。
  4. 返回值
    返回一个原函数的拷贝,并拥有指定的this值和初始参数。
  5. 描述
    1)bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数是一个exotic function object(怪异函数对象,ECMAScript 2015中的术语),它包装了原函数对象,调用绑定函数通常会导致执行包装函数。
    2)绑定函数的内部属性
    a. [[BoundTargetFunction]] - 包装的函数对象
    b. [[BoundThis]] - 在调用包装函数时始终作为this值传递的值
    c. [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表
    d. [[Call]] - 执行与此对象关联的代码,通过函数调用表达式调用,内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表
    3)绑定函数也可以使用new运算符构造,它会表现为目标函数已经被构建完毕了似的,提供的this值会被忽略,但前置参数仍会提供给模拟函数。
  6. 实例说明:
    1)创建绑定函数
    代码如下:
this.x = 9;    // 在浏览器中,this指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();   
// 返回9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

2)偏函数
代码如下:

function list() {
 return Array.prototype.slice.call(arguments);
}

function addArguments(arg1, arg2) {
   return arg1 + arg2
}

var list1 = list(1, 2, 3); // [1, 2, 3]

var result1 = addArguments(1, 2); // 3

// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);

// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37); 

var list2 = leadingThirtysevenList(); 
// [37]

var list3 = leadingThirtysevenList(1, 2, 3); 
// [37, 1, 2, 3]

var result2 = addThirtySeven(5); 
// 37 + 5 = 42 

var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略

3)配合 setTimeout
代码如下:

function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
  this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后, 调用'declare'方法

4)作为构造函数使用的绑定函数
代码如下:

function Point(x, y) {
 this.x = x;
 this.y = y;
}

Point.prototype.toString = function() { 
 return this.x + ',' + this.y; 
};

var p = new Point(1, 2);
p.toString(); // '1,2'

var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);

// 以下这行代码在 polyfill 不支持,
// 在原生的bind方法运行没问题:
//(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

5)快捷调用
代码如下:

var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

slice(arguments);

6)在没有内置实现支持的环境中也可以部分地使用bind
代码如下:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

四、call()、apply()和bind() 方法的总结

  1. call() 、apply()可以看作是某个对象的方法,通过调用方法的形式来间接调用函数,bind() 就是将某个函数绑定到某个对象上。
  2. call() 方法的第二个参数是允许传递参数列表,而apply()方法的第二个参数必须是数组,也可以是类数组对象,但是在传递给调用函数的时候,是以参数列表的形式进行传递的。
  3. call()和apply()方法会对目标函数自动执行,从而导致它无法在事件绑定函数中使用。事件绑定函数不需要我们手动执行,它是在事件被触发时由JS内部自动执行的,但是bind()方法可以在实现改变函数this的同时又不会自动执行目标函数,
    举例:
  var obj6 = {name:"张三"};
    //  张三 p1 p2  bind()不会立即执行,需要点击页面,然后控制台才会输出,可作为事件绑定函数
    document.addEventListener("click",EventClick.bind(obj6,"p1","p2"),false);
    // 张三 p1 p2   call()方法会自动立即执行,一打开控制台之间输出
   // document.addEventListener("click",EventClick.call(obj6,"p1","p2"),false);
    function EventClick(a,b){
        console.log(this.name,a,b);
    }

说明:在绑定事件监听函数以后,使用call()方法,在打开浏览器执行以后,控制台会自动输出值,绑定的事件监听函数失效了。但是在使用bind()方法,在打开浏览器执行以后,控制台不会自动输出值,当点击页面以后,事件监听函数生效,控制台才会输出值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值