简言
最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记改变自己以前学习知识点的误区
关于Bind,Apply,Call
大家本能知道,当函数调用他们的时候就会将函数中的this,显示指向他们的第一个参数(新对象),那么为什么大家在面试或者其他场景下仍然会因为这个this指向而发愁呢?
答案是,大家没理解this在什么场景下才会作用
作用域,上下文
因为js是一门脚本语言,因此它不具备静态编译的特征,很多代码需要在程序执行过程中才会正确理解他的含义
但是js神奇之处在于,js有着静态(作用域)
和动态(执行上下文)
,两种表现。
静态 - 程序运行之前
当程序运行之前,程序会扫描所有的代码,会先以函数
为范围划分作用域(全局作用域,函数作用域)
作用域:以一个函数区域为边界的范围
然后各个作用域会将各自的变量和函数做提升,提升到当前作用域的顶层,当然变量提升也仅仅只局限于使用var
声明的变量,且只提升变量的声明
console.log(a); // undefined,因为只提升了a变量的声明,相当于 var a; console.log(a); a = 1;
var a = 1;
function o (){
console.log(b); // undefined,只提升了b变量的声明
var b = 2;
}
o();
接下来我们再看看下面这个例子
console.log(a);
a = 1;
function o (){
console.log(b);
b = 2;
}
o();
执行结果后,a输出1,b输出b is not defined
,这是为什么呢?
对比上述例子我们可以得到结论:
- 如果在
全局作用域
下没有使用var
声明变量,则当程序运行前扫描,会将没有使用声明标识符的变量直接绑定到window
上,而在程序运行时执行的console.log(a)相当于执行console.log(window.a)
- 如果在
函数作用域
下没有使用var
声明的变量,则变量不会进行变量提升,也不会被绑定在window上,因为函数已经将b限制在函数的作用域内了,外部无法访问。因此当函数o执行时,调用console.log(b),当前函数作用域下没有找到b的声明,那么就会根据作用域链
,向函数作用域外查找,发现全局作用域中也没有找到b的声明,则认定b未被定义
那么这就是在静态情况下,程序帮开发者做的预扫描,根据作用域范围,提升作用域中的变量以及函数
动态 - 执行上下文(this)
那么在动态情况下,this又是怎么表现的呢。大家也都知道this有几个指向,那也到这里就结束了,一做起面试题来就跪了,this指向又分为隐式指向和显式指向
隐式指向
这里有个儿歌启蒙法:
家门口前有条河,上面有座桥,里面有群鸭
如果程序没有执行的时候,程序肯定静态的按家门口前有条河
,上面有座桥
,里面有群鸭
,来一条条解析语句,那么这里有个问题,静态解析的时候,上面有座桥,里面有群鸭
,在我们不知道河的前提下,怎么知道上面
,里面
代指是什么地方呢,因此我们就需要唱出家门口前有条河
,那么这个家门口前的河就是this
,this的上面有座桥,this的里面有群鸭
总结就是:
- this是在程序运行时读取上下文获取的
- this指向调用(this)所在函数的那个对象
当程序运行时,函数真正被调用的时候,this就会指向那个调用函数的对象
第一点就是大家平时忽略最多的,因此总是做面试题时,找不到this指向
显式指向
显式改变this指向,这里大家都知道使用call,bind,apply
call,apply,bind的区别
- 传参方式不同
- 执行结果不同
传参方式不同
call和bind从第二个参数开始使用的是依次传入,而apply第二个参数接收的是一个参数ArrayList
执行结果不同
call和bind会将this指向到新对象后,立即执行原函数的函数体内容,而bind则会将这个原函数执行过程封装成一个函数作为结果返回,因此bind最后会返回一个函数
接下来我们就讲讲怎么去手写一个Bind
手写Bind
首先我们要确定以下需求
- bind要被挂载到什么地方 - Function.prototypes
- bind的输入(第一个参数为this所指向的新对象,第二个参数~最后一个参数依次传入)
- bind的输出(返回一个函数,这个函数中返回原函数改变this指向后的执行结果)
- 原函数改变this指向需要构建新的apply
- apply挂载到什么地方 - Function.prototypes
- apply的输入(第一个参数为新对象,也就是bind的第一个参数, 第二个参数为传入的参数列表-来源于bind第二个参数~最后一个参数)
- apply的输出(执行原函数-调用bind的那个函数)并返回函数执行结果
- 做apply的优化
// 1. bind要被挂载到 Function.prototypes
Function.prototype.myBind = function () {
// 8. 做apply的优化
if (typeof this !== "function") {
// 8-1. 如果this不是函数,则抛出错误
throw new TypeError("this is not a function");
}
// 2. bind的输入(第一个参数为this所指向的新对象,第二个参数~最后一个参数依次传入)
const args = Array.prototype.slice.call(arguments); // arguments是一个类数组,并不是一个真正的数组,因此需要转换成真正的数组
// 3-1. 获取新的this, 同时确保newThis一定有值
const newThis = args.shift() || window;
// 3-2, 获取原函数
const _this = this; // 当原函数调用mybind的时候,this就指向原函数
// 3. bind的输出(返回一个函数,这个函数中返回原函数改变this指向后的执行结果)
return function () {
// 3-3. 返回原函数改变this指向后的执行结果
return _this.myApply(newThis, args);
};
};
// 4. 原函数改变this指向需要构建新的apply
// 5. apply挂载到什么地方 - Function.prototypes
Function.prototype.myApply = function (context) {
// 8. 做apply的优化
if (typeof this !== "function") {
// 8-1. 如果this不是函数,则抛出错误
throw new TypeError("this is not a function");
}
// 6. apply的输入(第一个参数为新对象,也就是bind的第一个参数, 第二个参数为传入的参数列表-来源于bind第二个参数~最后一个参数)
// 6-1 传入的对象一定有值
context = context || window;
// 6-2 将调用myApply的函数(this)隐式添加到context对象上,这里的context就是上面的newThis
// 6-3 为了防止context上已经有fn,因此使用Symbol取唯一值
const fn = Symbol();
context[fn] = this;
// 7. apply的输出(执行原函数-调用bind的那个函数)并返回函数执行结果
let result = arguments[1] ? context[fn](...arguments[1]) : context[fn]();
delete context[fn];
return result;
};
结语
后面一段时间我会持续更新,希望多多三连
标签:函数,指向,作用域,bind,JS,参数,apply From: https://www.cnblogs.com/laowangcoding/p/18569836