前言:
任何存在的东西,都有一定的目的,对于程序而言,就是解决具体的问题。call,apply,bind是改变this 的指向的,但是为什么需要改变呢?
1.为什么会有this的存在
2.this的指向
this是由执行的上下文环境决定的,箭头函数除外
2.严格的函数体里面,指向的是一个 undefined.这个时候,调用和this的指向没有关系。
3.作为对象方法的话,指向本身。
4.如果this隐式丢失,会指向最近的调用this。
5.call apply bind 会修正this 的指向,传入unll,undefined 指向window
7.当是箭头函数的时候,会指向定义时运行环境。
3.call,apply,bind的产生
call、apply、bind都是和this指向有关的,为什么和需要改变this呢?
假如A有个刀子可以削苹果,B有个苹果,B如果想用刀子削苹果,就要买个刀子,如果B能借用A的刀子,就可以不用买刀子这件事情。在程序中,就能达到功能复用的逻辑。
4.举个例子理解
假设有个类数组对象,它想使用数组方法,把数组方法指向处理的数据的指向,就能复用逻辑。简单理解就是,A把对象给B,B处理数据指向A,返回的处理结果就是A的。
let arrLike = { 0:"0-1",1:"0-2",length:2 };
let result = Array.prototype.slice.call(arrLike);
// 先把数据截取出来,就能变成数组 ["0-1", "0-2"]
Array.prototype.push.call(arrLike,'0-3');
// 借用push方法,arrLike = {0: "0-1", 1: "0-2", 2: "0-3", length: 3}
5.使用场景
一句话:借用方法
1.只用函数能用。
2.参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。
3.考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
4.参数集合已经是一个数组的情况,用apply。
6.实现一个call方法
在实现一个call 的前面,先看个小例子:
function B () {console.log(this.name) };
let testO = {name:"12",getName:B};
testO.getName() // 12,这里看到,作为一个对象的方法调用,this指向这个对象。
前面已经说过这个结论,这里利用这个特性,实现call.
6.1目的:
改变this的指向,为什么要改变,因为我本身这个对象没有对应的方法处理这个事情,我要借用别人的方法执行这个事情。
6.2 分析要的结果
/**
* 1.传入一个对象,改变this的指向
* 2.可能传入参数,也可能不传入
* 3.执行返回结果
** 综上可以看到:问题在于this的指向实现
*/
6.3思路:来源call的实现标准
/**
* 1.设置一个 thisArg 也就是this的指向
* 2.将thisArg 封装成一个Object
* 3.通过thisArg 创建一个临时的方法,这样thisArg就能调用自己的方法了,
该临时的方法 this会指向到thisArg
* 4.执行thisArg的临时方法,并传递方法
* 5.删除临时方法,返回方法的执行结果
**/
6.4 代码实现
/**
* 1.在函数的原型上定义一个方法myCall ,因为要保证任意的函数都能调用。
Function.prototype.myCall = function() {}
** 2.myCall 接受两个参数,一个要改变this的指向,另一个可能存在的参数
function(thisArg,...arr) {}
** 3\. 如果thisArg是null undefined 要指向 window,不是指向4
** 4\. 创建一个对象,thisArg 指向这个对象
** 5.把当前mycall的this作为这个对象的属性方法调用,那这个this 就指向了对象本身,也就是thisArg
** 6\. 删除临时方法
** 7.返回结果*/
6.5 具体的代码
Function.prototype.myCall = function (thisArg,...arr) { // 1,2
if(thisArg === null || thisArg ===undefined) { // 3
thisArg = window;
} else {
thisArg = Object(thisArg); // 4
}
const speMethod = Symbol('anything'); // 创建一个不常用的常量
thisArg[speMethod] = this; //5 把mycall 函数里面的this给thisArg 的一个属性方法调用。
let reault = thisArg[speMethod](...arr);
delete thisArg[speMethod]; //6删除此临时方法
return reault;//7
}
7.apply 的实现
实现基本和上面是一样的,之时apply接受的是一个类数组和数组。
// 实现apply ,思路和上面一样,就是参数处理不一样.
Function.prototype.myApply = function (thisArg) {
if(thisArg === undefined || thisArg === undefined) {
thisArg = window;
} else {
thisArg = Object(thisArg);
}
const applyFunkey = Symbol('anything');
thisArg[applyFunkey] = this;
let result ;
// 参数的处理
let params = arguments[1];
if(params) { // 参数存在的情况下
if(params instanceof Object) {
result= thisArg[applyFunkey](...params);
} else {
throw new TypeError('第二个参数为数组,或者类数组');
}
} else {
result = thisArg[applyFunkey]();
}
delete thisArg[applyFunkey];
return result;
}
8.bind 的实现
8.1分析
/**
* 1.传入一个函数,返回一个代执行的函数 ,利用闭包。return function() {}
** 2.作用域绑定 ,通过 call,apply
** 3.支持二次参数传递。
** 4.新返回的函数可以通过new调用。返回的函数需要继承原函数的原型链方法
** 5\. 返回一个新函数
**/
8.2 代码实现
Function.prototype.myBind = function (objThis,...firstParms) {
let thisFn = this; // 存储函数调用,以及 函数参数firstParms // 1
// 返回一个函数
let funcForBind = function (...secParams) {
// 判断是否通过new 调用
const isNewCall = this instanceof funcForBind;
// 如果是new调用,还是this,不是的话,保证传入的是一个对象
const thisArg = isNewCall?this:Object(objThis);
return thisFn.call (thisArg,...firstParms,...secParams); //2,3
}
funcForBind.prototype = Object.create(thisFn.prototype); // 4
return funcForBind;
};