一、手动实现call()
1、我们想要实现call(),必须要了解call的特点:
1、首先call()位于Function.prototype中,所以我们定义myCall()的时候,也在此处定义
2、call()的作用是改变this指向,将call()中第一个参数的this指向赋值给调用它的函数
3、若是call()中的第一个参数为null,或者undefined,且这个函数处于非严格模式,则会将null或者undefined自动替换为全局对象
4、call()中除了第一个参数外,还接收一个参数列表
5、call()的返回值是当前调用call()的函数的返回值,若是当前调用的函数没有返回值,则返回undefined
2、实现call:
Function.prototype.myCall = function(context, ...args) {
if(this === Function.prototype) {//判断当前this是否为函数
return undefined;//用于防止 Function.prototype.myCall() 直接调用
}
// console.log(this);//此处的this指向foo,因为是foo调用的(隐式绑定规则)
context? context : window //context为可选的参数,如果不传的话默认上下文为window
const fn = Symbol(); //为context创建一个Symbol属性
context[fn] = this; //将当前调用myCall的函数赋值给这个属性
console.log(context[fn]);
const result = context[fn](...args); //处理参数,传入第一个参数后的其余参数,这里可以理解为obj.foo(...args)
delete context[fn] //调用函数后即删除该Symbol属性
return result
}
3、对自己创建的myCall的应用:
var obj = {
a:1
}
function foo(b,c) {
// console.log(this);//若是不用myCall改变this指向,this指向全局(默认绑定规则),但是用myCall改变this指向后,现在的this指向obj
return this.a + b + c
}
let bar = function(b,c) {
// console.log(this);//this指向全局变量,因为bar在全局中调用
return foo.myCall(obj,b,c)
}
let b = bar(2,3)
console.log(b);
二、手动实现apply()
1、apply与call唯一的区别就是,参数不一样,apply接收类数组或者数组
2、实现apply():
Function.prototype.myApply = function (context , args) {
if (this === Function.prototype) {
return undefined; // 用于防止 Function.prototype.myCall() 直接调用
}
context? context : window
const fn = Symbol();
context[fn] = this;
let result;
if (Array.isArray(args)) { //Array.isArray()用于确定传递的值是否为一个Array
result = context[fn](...args);
} else {
result = context[fn]();
}
delete context[fn];
return result;
}
三、手动实现bind()bind借鉴于此文
一句话介绍bind:
bind()方法会创建一个新函数,当这个新函数被调用时,bind()的第一个参数将作为它运行时的this,之后一序列参数将会在传递的实参前传入作为它的参数
bind函数的两个特点:返回一个函数,可以传入参数
1、返回函数的模拟实现
var foo = {
value : 1
}
function bar() {
console.log(this.value);
}
//返回了一个函数
var bindFoo = bar.bind(foo)
bindFoo()
我们来写第一版:
Function.prototype.myBind = function(context) {
var self = this
return function() {
return self.apply(context)
}
}
这里的return self.apply(context),是考虑到绑定函数可能是有返回值的
var foo = {
value : 1
}
function bar() {
return this.value
}
var bindFoo = bar.myBind(foo)//若是myBind中没有上述的返回值,则娶不到value的值
console.log(bindFoo());//1
2、传参的模拟实现
var foo = {
value : 1
}
function bar(name,age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo,'daisy')
bindFoo('18')
// 1
// daisy
// 18
由上述代码可知,函数需要传name和age两个参数,竟然还可以再bind的时候,只传一个name,在执行返回的时候,再传另一个参数age
第二版 :
Function.prototype.myBind = function (context) {
var self = this
//获取myBind函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments,1)
return function () {
//这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments)
return self.apply(context,args.concat(bindArgs))
}
}
3、构造函数的模拟实现
bind还有一个特点:
一个绑定函数也能使用new 操作符创建对象:这种行为就相当于把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数
也就是说当bind返回的函数作为构造函数的时候,bind时指定的this值就会失效,但传入的参数依然生效
var value = 2
var foo = {
value : 1
}
function bar(name,age) {
this.habit = 'shopping'
console.log(this.value);//undefined //此时的this会失效
console.log(name);//daisy
console.log(age);//18
}
bar.prototype.friend = 'kk'
var bindFoo = bar.bind(foo,'daisy')
var obj = new bindFoo('18')//此时的this已经指向了obj
console.log(obj.habit);
// shopping
console.log(obj.friend);
//kk
所以我们可以更改下原型,来我们实现第三版:
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
//当作为普通函数的时候,this指向window,此时结果为false,将绑定函数的this指向context
return self.apply(this instanceof fBound? this : context,args.concat(bindArgs))
}
//修改返回函数的prototype为绑定函数的prototype,实例就可以继承函数的原型中的值
fBound.prototype = this.prototype
return fBound
}
4、构造函数效果的优化实现
但是在第三版的写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
Function.prototype.bind2 = 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);
//当使用构造函数调用的时候,this指向的是实例对象,因为新创建的实例对象是空的,所以可以新建一个函数fNOP代替这个空的实例对象,所以只需要判断这个this是否指向这个fNOP就行
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}