在前端面试中,可能会出现一些手撕模拟实现的题目,这里进行一些记录。
原生api
js中中提供了 call, apply, bind
用于手动指定this的指向
概念
调用 call 和 apply 的对象,必须是一个函数 Function
由于apply()
方法(或者call()
方法)不仅绑定函数执行时所在的对象,还会立即执行函数
第一个参数,是一个对象, 由该对象调用Function
s.Function.call(obj)
// 相等于 obj.Function
A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window
//如果call方法没有参数,或者参数为null或undefined,则等同于指向全局对象。
call 和 apply 的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别。
bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。
call(),apply(),bind()都是用来重定义this对象的
这三个函数的第一个参数都是 this 的指向对象;
第二个参数的差别:
call 的参数直接放进去,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,‘n1’, … ,‘n’ )。
apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,[‘n1’, …, ‘m’ ])。
bind 除了返回是函数以外,它的参数和 call 一样。
当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!
var obj = {
name:'tzb',
age:18,
myFunc:function(n, a){
console.log(this.name + "年龄" + this.age);
console.log(n + "年龄" + a);
}
}
var db = {
name :'zhi',
age:22
}
// call apply bind 都是挂到Object上的函数
//1.call
obj.myFunc(); // tzb 年龄18
// obj.myFunc().call()是错误写法 ()表示立即执行函数 myFunc()执行完成之后并没有返回值
obj.myFunc.call(db,'bin',33) // zhi年龄22 /n bin年龄33
//2.apply
obj.myFunc.apply(db,['bin',33]) // zhi年龄22 bin年龄33
//3.bind
var f = obj.myFunc.bind(db,'bin',33);
// bind返回的是一个函数 加上()才会执行
f(); // zhi年龄22 bin年龄33
模拟实现
bind的模拟实现使用到了 apply
,这里先给出apply
的模拟实现
apply
// args传入的需要是一个数组
function myApply(context, args) {
if (typeof this !== 'function') {
throw new Error('调用者必须是函数');
}
context = context || window;
// 手动给调用者添加一个方法
context.fn = this;
let res;
if (args) {
res = context.fn(...args);
}else {
res = context.fn();
}
// 获得结果后删除该属性
delete context.fn;
return res;
}
Function.prototype.myApply = myApply;
bind模拟实现
// bind(thisObj, a1, a2, a3);
function myBind(...args) {
if (typeof this !== 'function') {
throw new Error('调用者必须是函数');
}
const fn = this;
const context = args[0] || window;
const myArgs = args.slice(1);
return function(...newArgs) {
fn.apply(context, myArgs.contact(newArgs));
}
}
上述模拟实现没有考虑bind返回的函数作为构造函数的情况。
在构造函数中,this指向新生成的对象。那么下面这个就不合适了。
return function(...newArgs) {
fn.apply(context, myArgs.contact(newArgs));
}
考虑构造函数的情况
// bind(thisObj, a1, a2, a3);
function myBind(...args) {
if (typeof this !== 'function') {
throw new Error('调用者必须是函数');
}
// this为调用bind的函数对象
const fn = this;
const context = args[0] || window;
const myArgs = args.slice(1);
let Temp = function(){};
Temp.prototype = fn.prototype;
// bind返回的函数 f 作为构造函数时
let f = function(...newArgs) {
return fn.apply(this instanceof f ? this : context, myArgs.contact(newArgs));
}
// 继承原型链上的属性
f.prototype = new Temp();
return f;
}