作用
call、apply、bind 方法都可以改变函数运行时的上下文
区别
1、 call、apply 方法返回的是函数的调用, 而 bind 方法返回的是函数方便之后调用。
2、apply以数组的形式传参,call、bind 以序列化的形式传参。
示例代码:
function product () {}
product.prototype={
name:'product',
price: 5,
getPrice(number, discount){
console.log(this.price*number*discount)
return this.price*number*discount
}
}
let apple = {
price: 8
}
let rice = new product
rice.getPrice(10, 0.8) // 40
rice.getPrice.apply(apple, [10, 0.8]) // 64
rice.getPrice.call(apple, 10 , 0.8) // 64
rice.getPrice.bind(apple, 10 )(0.8) // 64 等同于rice.getPrice.bind(apple, 10, 0.8)()
源码分析
Bind
Function.prototype.bind = function (oThis) { // oThis:将要被绑定的上下文, 示例中指向o
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), // 将bind进来的arguments转换为数组,去掉第一个参数(oThis), 示例中结果为[1]
fToBind = this, // fToBind指调用bind的函数对象, 示例中指向test.printValue
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP // 调用bind的对象 是否是 fNOP 的实例
? this // 如果调用bind的对象是fNOP的实例,将fToBind 的上下文指向调用bind的对象
: oThis || this, // 调用bind的对象不是fNOP的实例,且存在oThis则将上下文指向oThis
aArgs.concat(Array.prototype.slice.call(arguments))); // 拼接bind进来的参数与bind之后调用的参数(参数的柯里化), 示例中则是拼接[1]和[2,3] 作为test的参数。
};
fNOP.prototype = this.prototype || this.__proto__; // 重写fNOP的原型,使得fNOP继承调用bind的对象的属性和方法
fBound.prototype = new fNOP(); // 重写fBound的原型,使得fBound继承fNOP的方法和属性
return fBound;
};
1、Array.prototype.slice.call(arguments, 1)
作用:将arguments 转换为数组并去掉第一个参数
自定义slice方法示例:
Array.prototype.Myslice = function (begin,end){
let start = begin || 0; // Array.prototype.slice.call(arguments, 1)这里 begin 为 1
let len = this.length || 0; //获取上下文的长度, 源码中 arguments 的长度
// 开始索引:
// 1、没有参数从0开始有就从索引处开始,
// 2、如果该参数为负数则表示从原数组中的倒数第几个元素开始提取
start = (start >= 0) ? start : Math.max(0, len + start);
// 结束索引:
// 1没有参数默认取到数组末尾,如果大于数组长度,取到数组末尾
// 2、如果该参数为负数则表示在原数组中的倒数第几个元素结束抽取
end = (typeof end == 'number') ? // Array.prototype.slice.call(arguments, 1) 这里 end 为 undefined
end>=0?Math.min(end, len):Math.max(end+len,0)
: len; //
let result = new Array();
for (let i = start; i < end; i++) {
result.push(this[i])
}
return result;
}
function testMySliceCall() {
// arguments 是属于函数内部变量,其值是一个具有长度属性的类数组对象(ex:{'1':1,'2':2,'3':3,'4':4})
return Array.prototype.Myslice.call(arguments,1);
}
console.log(testMySliceCall(1, 2, 3, 4)); // [2,3,4]
2、原型链
// 先创建一个临时性的构造函数
let fNOP = function () {}
// 重写fNOP的原型,使得fNOP继承调用bind的对象的属性和方法
fNOP.prototype = this.prototype;
// 重写fBound的原型,使得fBound继承fNOP的方法和属性
fBound.prototype = new fNOP();
// 返回兼具调用bind的对象的属性和方法,同时又具有即将被指定为上下文的对象的属性和方法的对象
return fBound;
apply
Function.prototype.apply = function (context, arr) {
context = Object(context) || window; // 示例中context 指向 apple 对象
context.fn = this; // 将函数添加到将要被绑定的上下文,示例中给apple 对象 添加 getPrice 方法
let result;
if (!arr) { // 没有传参,直接返回调用
result = context.fn();
}
else {
if (!(arr instanceof Array)) throw new Error('params must be array');
result = context.fn(...arr);// rice.getPrice(10, 0.8) ...Array ES6语法用于序列化数组元素
}
delete context.fn
return result; // 返回函数的调用
}
call
Function.prototype.call = function (context) {
context = Object(context) || window;
context.fn = this;
let args = [];
// arguments 为伪数组,有长度属性,但不是数组,示例中为{'1':10, '2':0.8}
for (let i = 1, i < arguments.length; i++) {
args.push(arguments[i]);
}
let result = context.fn(...args);
delete context.fn;
return result;
}