bind、apply、call的区别与实现
首先介绍一下bind、call、apply的作用,然后再介绍区别
作用
bind:改变this指向,第二个参数为参数列表,返回函数
call:改变this指向,第二个参数为参数列表,函数执行
apply:改变this指向,第二个参数为数组,函数执行
区别
apply、call异同
同:改变this指向,函数执行
异:apply第二个参数为数组;call第二个参数为参数列表
bind与call异同
同:改变this指向,第二个参数为参数列表
异:bind返回函数(未执行);call执行函数
实现
如何用Javascript实现call?
首先摆出成品代码
//ES6写法
Function.prototype.myCall = function (context) {
var context = context || window;
context.fn = this;
var args = [...arguments].slice(1);
var result = context.fn(...args);
delete context.fn;
return result;
}
//ES5写法
Function.prototype. myCall = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args + ')');
delete context.fn
return result;
}
接下来,按步骤实现
call()函数的作用:1.改变this指向2.第二个参数为参数列表
第一步,实现改变this指向
思路:把函数挂在对象上,然后执行函数,不就实现了改变this指向,最后再把对象上的函数删除。
具体实现如下:
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
//添加到对象上
foo.bar = function(){
console.log(this.value)
}
//执行
foo.bar();//1
//从对象上删除
delete foo.bar;
那么第一版本的myCall则如下
// 第一版
Function.prototype.myCall = function(context) {
// 首先要获取调用call的函数,用this可以获取
context.fn = this;
context.fn();
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.myCall(foo); // 1
控制台打印1,第一步成功.
但仍然存在一个小问题,如下:
this 参数可以传 null,当为 null 的时候,视为指向 window。则最终代码如下:
// 第一版
Function.prototype.myCall = function(context) {
var context = context || window;
// 给 context 添加一个属性
context.fn = this;
context.fn();
// 删除 fn
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
第二步,传递参数
插入一个知识点arguments:函数执行时的参数组成的类数组。
思路:我们截取arguments从下标1到最后一个,转化为数组,再转为列表即可(借用ES6扩展运算符,可以很简单的实现)
Function.prototype.myCall = function (context) {
var context = context || window;
// 给 context 添加一个属性
context.fn = this
// 将 context 后面的参数取出来
var args = [...arguments].slice(1)
var result = context.fn(...args)
// 删除 fn
delete context.fn
return result
}
这样call函数就实现了。
如何用Javascript实现apply?
由于apply和call只有参数格式的区别,所以实现类似,代码如下
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
如何用Javascript实现bind?
先贴上最终版本,在按步骤实现
Function.prototype.myBind= function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
第1步,返回函数
思路:关于返回函数的指定this,我们可以使用call模拟实现。
代码如下
//返回函数
Function.prototype.myBind = function (context) {
var self = this;
return function () {
self.apply(context);
}
第2步,传递参数
// 传递参数
Function.prototype.myBind = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}
通过bind最终返回函数,也能使用构造函数创建对象,此时提供的this无效,但是参数仍然生效。
看个例子
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'sleep';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = '王五';
var bindFoo = bar.bind(foo, '张三');
var obj = new bindFoo('18');
// undefined
//张三
// 18
console.log(obj.habit);//sleep
console.log(obj.friend);// '王五'
分析:虽然foo对象和全局都定义了value,但是返回了undefind,说明绑定的this失效了,而且this指向了obj。
思路:判断一下构造函数是否执行,可以通过instanceof判断。
//构造函数
//构造函数
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
fBound.prototype = this.prototype;
return fBound;
}
分析:我们直接将 fBound.prototype = this.prototype,直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype.可以用一个函数实例转化一下。
// 第四版
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
至此,成功实现。