call apply bind的用法理解
今天尝试了一下call apply bind 的手动实现,记录一下自己的理解
call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。(取自MDN)
call( thisArg,arg1,arg2,…)
thisArg:函数执行时使用的 this 值 (/*该值 为空,null,undefined时,this指向window)
arg1,arg2,… :参数列表,即函数调用使用的参数
可见 call() 就是用于调用一个函数,并且将被调用函数的this绑定给call()提供的第一个参数thisArg,
其次call() 返回函数执行后的结果
列举几个例子来了解一下call的用法
1.类数组对象使用push方法
var similarArr = {
0: 'a',
1: 'b',
length: 2
}
console.log(typeof similarArr);
// 'object' similarArr是一个对象,是模拟数组的一个类数组
Array.prototype.push.call(similarArr, 'c', 'd');
console.log(similarArr);
// {0: "a", 1: "b", 2: "c", 3: "d", length: 4}
2.构造函数继承(取自MDN)
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
// 使用call方法调用了Product函数,
// 将Product函数中的this指向改为自己的this指向,参数为name,price
// 返回执行结果 ==> this.name = name; this.price = price;
this.category = 'food';
}
var cheese = new Food('feta', 5);
// new会调用Food函数
// 此时Food函数可以理解为
/*
function Food(name, price) {
this.name = name;
this.price = price;
this.category = 'food';
}
*/
console.log(cheese)
打印结果 ↓
接下来我们可以通过手动实现一下call方法,加强理解
首先明确call都做了什么事:
1. 改变了函数的this的指向
2. 执行函数并返回结果
Function.prototype.myCall=function(thisArg,...args){
// 不是函数调用就返回错误提示
if(typeof this !== 'function') throw `${this}.call is not a function`;
// thisArg 为空,null,undefined 时,this指向window(node.js中指向global)
thisArg=thisArg || window || global;
// 改变函数的this指向为thisArg
// ==> 通过给thisArg添加新属性,值设为该函数,使该函数成为thisArg的一个方法,this就指向了thisArg
thisArg.fn=this;
// 调用thisArg.fn,参数为args,返回执行结果
var res=thisArg.fn(...args)
// 之前添加了新属性,需要删除掉
delete thisArg.fn;
return res;
}
检验一下
var obj={name:"obj"}
var fun=function(arg1,arg2){
return {
name:this.name,
res:arg1+arg2
}
}
var test=fun.myCall(obj,1,2)
console.log(test) // {name: "obj", res: 3}
var test2=obj.myCall(obj,1,2)// Uncaught TypeError: obj.myCall is not a function
this成功绑定到了obj上,若不是函数调用时,返回了错误提示
apply
call()方法的作用和 apply()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。(取自MDN)
apply()和call()就是接收的参数有区别,核心原理都是一样的,直接贴实现代码
Function.prototype.myApply=function(thisArg,args){
// 不是函数调用就返回错误提示
if(typeof this !== 'function') throw `${this}.myApply is not a function`;
// thisArg 为空,null,undefined 时,this指向window(node.js中指向global);
thisArg=thisArg || window || global;
// 改变this指向
thisArg.fn=this;
// 调用thisArg.fn,并返回结果
var res=thisArg.fn(...args);
// 添加了新属性,需要删除掉
delete thisArg.fn;
return res;
}
检验一下
var obj={name:"obj"}
var fun=function(arg1,arg2){
return {
name:this.name,
res:arg1+arg2
}
}
var test=fun.myApply(obj,[1,2])
console.log(test) // {name: "obj", res: 3}
var test2=obj.myApply(obj,[1,2]) // Uncaught TypeError: obj.myApply is not a function
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。(取自MDN)
bind方法的实现思路和call基本一样,区别在于bind不会直接执行函数,而是返回一个新的函数,之后再通过执行这个新函数得到调用函数的结果
Function.prototype.myBind=function(thisArg,...args1){
// 不是函数调用就返回错误提示
if(typeof this !== 'function') throw `${this}.myApply is not a function`;
// 将调用的函数保存下来,以便在返回的新函数函数中使用
var self=this;
return fn=function(...args2 /*返回的函数也可以接收参数*/ ){
// 因为调用返回的函数,就会执行内部函数并且绑定this,所以直接使用call()即可
return self.call(thisArg,...args1,...args2);
}
}
检验一下
var obj={name:"obj"}
var fun=function(arg1,arg2){
return {
name:this.name,
res:arg1+arg2
}
}
var test=fun.myBind(obj,1,2)
console.log(test()) // {name: "obj", res: 3}
但是,如果用new创建test的实例化对象,this指向因为绑定到了obj上,所以实例化的对象的this指向就不对了(本应该指向test,却指向了obj) (不是很理解可以看看文末的补充:new的手动实现,理解一下new的原理)
测试代码 ↓
var obj={name:"obj"}
var fun=function(arg1,arg2){
// 打印一下this.name
console.log(this.name)
return {
name:this.name,
res:arg1+arg2
}
}
var test=fun.bind(obj,1,2);
new test(); // undifind test没有name这个属性,所以是undefined
var test=fun.myBind(obj,1,2);
new test(); // obj obj的name属性值为obj
因此我们需要在绑定this值的时候判断一下,如果是创建实例的情况(通过验证是否是当前构造函数的实例),this指向就是当前构造函数,反之就是传入的thisArg
bind优化版
Function.prototype.myBind=function(thisArg,...args1){
// 不是函数则返回错误提示
if(typeof this !== 'function') throw `${this}.myApply is not a function`;
// 将调用的函数保存下来,方便在返回的函数内部使用
var self=this;
var fn=function(...args2/*返回的函数也可以接收参数*/){
// 因为调用返回的函数是就要执行内部函数并且绑定this,所以直接使用call()即可
// 并且判断是否是new的情况
return self.call(this instanceof fn? this:thisArg,...args1,...args2);
}
// 创建实例的情况,还需使实例能够继承绑定函数的原型
if(this.prototype){
fn.prototype = Object.create(this.prototype);
// 设置了新的原型,改变了初始的构造器,所以需要设置回来
fn.prototype.constructor=fn;
}
return fn;
}
再检验一下
var test=fun.bind(obj,1,2);
new test(); // undifind
var test=fun.myBind(obj,1,2);
new test(); // undifind
补充:new的手动实现
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。 当代码 new Foo(…) 执行时,会发生以下事情:
1 > 一个继承自 Foo.prototype 的新对象被创建。
2 > 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。
3 > 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤。)
(取自MDN)
var _new=function(fn,...args){
if(typeof fn !== "function"){
throw `${fn} is not a constructor`
}
// 1. 一个继承自 Foo.prototype 的新对象被创建
// 如果用obj.prototype = fn.prototype,修改obj.prototype也会造成fn.prototype的修改,为了防止篡改
var obj=Object.create(fn.prototype);
// 2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象
var res=fn.call(obj,...args);
// 3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象
return res instanceof Object? res : obj;
}
检验一下
function Student (){
this.name = '1';
// return {a:1}
}
Student.prototype.sayName = function (){}
var student = _new(Student);
console.log(student)
console.log(student.__proto__ === Student.prototype)
打印结果 ↓
成功返回一个Student实例
欢迎大家在评论区留言讨论