您的位置:首页 > 移动开发

JavaScript实现call、apply和bind

2017-12-13 17:29 896 查看
  每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。详细内容查看JavaScript中call()、apply()和 bind()方法

1、call方法的实现

下面看一个使用call方法的实例:

function add(c, d){
return this.a + this.b + c + d
}
var o = {a:1, b:3}
add.call(o, 5, 7) // 16


  add.call(o, 5, 7)使add函数中的this指向o对象,并且执行了add函数。依据上例把o对象改造成如下:

var o= {
a: 1,
b: 3,
add: function(c, d) {
return this.a + this.b + c + d
}
};

o.add(5, 7); // 16


  给o对象添加一个add属性,这个时候 this 就指向了 o,o.add(5,7)得到的结果和add.call(o, 5, 6)相同。但是给对象o添加了一个额外的add属性,这个属性我们是不需要的,所以可以使用delete删除它。

所以我们使用js实现call方法的步骤可以分为:

将函数设为对象的属性

执行该函数

删除该函数

以上个例子为例,就是:

// 第一步
o.fn = bar
// 第二步
o.fn()
// 第三步
delete o.fn


使用ES3实现call方法

/**
* @description 使用ES3实现call方法
* @param {Object} context call方法一个指定的this值
* @returns {Object, String, Number, Boolean} 返回调用函数的值
*/
Function.prototype.call = function (context) {
// context为null的时候,context为window
var context = context || window
// 获取调用call的函数
context.fn = this
// 获取call方法的不定长参数
var args = []
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']')
}
// 运行fn函数并返回结果,
// eval(string)通过计算string得到的值
var result = eval('context.fn(' + args +')')
// 删除fn属性
delete context.fn
// 返回结果
return result;
}
/**
* @description 测试call方法
* @param {Number} c,d 函数的参宿
* @returns {Number} 返回add函数的计算结果
*/
function add(c, d){ return this.a + this.b + c + d } var o = {a:1, b:3} add.call(o, 5, 7) // 16


使用ES6实现call方法

/**
* @description 使用ES6数组的扩展运算符(...)实现call方法
* @param {Object} context call方法一个指定的this值
* @returns {Object, String, Number, Boolean} 返回调用函数的值
*/
Function.prototype.call = function(context) {
// context为null的时候,context为window
var context = context || window
// 获取调用call的函数
context.fn = this
// 获取call方法的不定长参数
var args = []
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
// 使用ES6扩展运算符(...)执行函数,返回结果
var result = context.fn(...args)
// 删除fn属性
delete context.fn
// 返回结果
return result;
}


更简单的ES6写法:

/**
* @description 使用ES6函数的rest参数和数组的扩展运算符实现call方法
* @param {Object} context call方法一个指定的this值
* @param {Object, String, Number, Boolean} context call方法一个指定的this值
* @returns {Object, String, Number, Boolean} 返回调用函数的值
*/
Function.prototype.call = function(context, ...args) {
// 使用ES6函数的rest参数(形式为...变量名),args是数组
// context为null的时候,context为window
var context = context || window
// 获取调用call的函数
context.fn = this
// 使用ES6扩展运算符(...)执行函数,返回结果
var result = context.fn(...args)
// 删除fn属性
delete context.fn
// 返回结果
return result;
}


2、apply方法的实现

  apply和call方法实现类似,apply和call方法的区别仅在于接收参数的方式不同。对于apply()方法而言,第一个参数是作用域没有变化,变化的只是其余的参数apply使用数组或者arguments对象,call直接传递给函数。

使用ES3实现apply方法

/**
* @description 使用ES3实现apply方法
* @param {Object} context apply方法一个指定的this值
* @param {Array} arr apply方法传递给调用函数的参数
* @returns {Object, String, Number, Boolean} 返回调用函数的值
*/
Function.prototype.apply= function (context, arr) {
// context为null的时候,context为window
var context = context || window
// 获取调用apply的函数
context.fn = this
var result
// 判断apply是否只有一个参数
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 + ')')
}
// 删除fn属性
delete context.fn
// 返回结果
return result;
}
/**
* @description 测试apply方法
* @param {Number} c,d 函数的参宿
* @returns {Number} 返回add函数的计算结果
*/
function add(c, d){
return this.a + this.b + c + d
}
var o = {a:1, b:3}
add.apply(o, [5, 7]) // 16


使用ES6实现apply方法

/**
* @description 使用ES6数组的扩展运算符实现apply方法
* @param {Object} context apply方法一个指定的this值
* @param {Array} arr apply方法传递给调用函数的参数
* @returns {Object, String, Number, Boolean} 返回调用函数的值
*/
Function.prototype.call = function(context, arr) {
// context为null的时候,context为window
var context = context || window
// 获取调用apply的函数
context.fn = this
// 使用ES6扩展运算符(...)执行函数,返回结果
var result = context.fn(...arr)
// 删除fn属性
delete context.fn
// 返回结果
return result
}


3、bind方法的实现

  bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。先看一个使用bind方法的实例:

function foo(c, d) {
this.b = 100
console.log(c)
console.log(d)
console.log(this.a)
}
var func = foo.bind({a: 1}, 'cc')
func('dd') //cc dd 1
new func('dd') //cc dd undefined


  当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。所以上例中使用func为构造函数时,this不会指向{a: 1}对象,this.a的值为undefined。

/**
* @description 实现bind方法
* @param {Object} context bind方法一个指定的this值
* @returns {Function} 返回一个函数
*/
Function.prototype.bind = function (context) {
// 判断绑定bind方法的是不是函数
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
// 绑定函数赋值给self
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
// 调用bind方法返回的函数
var fBound = function () {
// 获取函数的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 返回函数的执行结果
// 判断函数是作为构造函数还是普通函数
// 构造函数this instanceof fNOP返回true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值。
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs))
}
// 创建空函数
var fNOP = function () {};
// fNOP函数的prototype为绑定函数的prototype
fNOP.prototype = this.prototype;
// 返回函数的prototype等于fNOP函数的实例实现继承
fBound.prototype = new fNOP();
// 以上三句相当于Object.create(this.prototype)
// 返回函数
return fBound;
}


使用ES6实现bind方法

if (typeof Function.prototype.bind !== 'function') {
Function.prototype.bind = function(context, ...rest) {
if (typeof this !== 'function') {
throw new TypeError('invalid invoked!')
}
var self = this
return function F(...args) {
if (this instanceof F) {
return new self(...rest, ...args)
}
return self.apply(context, rest.concat(args))
}
}
}


参考链接:

JavaScript深入之call和apply的模拟实现

不能使用call,apply,bind,如何用js实现call或者apply的功能?

JavaScript深入之bind的模拟实现
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: