「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
写在前面
在前端领域,this对我来说曾经像梦一样,猜不透,摸不着。
相信有不少小伙伴们对于this也有过我曾经的处境,本文将带领大家一起彻底搞懂this。
在开始之前,我们需要先消除一些对this的误解,人们很容易把this理解成指向函数自身。
我们来看一个栗子🌰
function foo() {
console.log(this.count)
}
foo.count = 1
window.count = 2
foo() // 2
console.log(foo.count) // 1
按照上面的逻辑,foo输出的应该是1但是输出的2,这明显是与上面的想法矛盾的,那么具体为什么呢,我们慢慢来看。
what is this
区别于其他语言,js中的this总是会指向一个对象,而这个对象在运行时由函数的执行环境动态确定,并非函数被声明时的环境。
去除with和eval这两种不常用的存在欺骗作用域的的用法,this一般有四种指向:
- 作为对象的方法调用(隐式绑定) ----->
this指向该对象
- 作为普通函数调用(默认绑定) ----->
this指向window
在
ES5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined
- 构造器调用(
new绑定) ----->this指向实例对象
call/apply/bind(显示绑定) ----->this指向传递的对象
跟普通的函数调用相比,call/apply/bind可以动态地改变函数的this,我们会在接下来的篇幅中更详细的介绍他们。
where is this
我们再看一段代码
window.name = 'Varlet'
var joker = {
name: 'Jokerrr',
getName(){
console.log(this.name)
}
}
joker.getName() // Jokerrr
const fun = joker.getName
fun() // Varlet
我们来分析一下
-
在执行
joker.getName时getName作为joker对象的属性被调用,此时的this指向joker,所以输出Jokerrr -
当我们用
fun来引用joker.getName并调用时,为普通函数调用模式,此时this指向window
call apply bind
先说说call和apply,他们的作用是一模一样,如果要说区别,估计只有参数的传入方式不同。
apply
apply接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组。
apply把这个集合中的元素作为参数传给被调用的函数。
举个栗子🌰
function fun(a,b,c){
console.log(a,b,c)
}
fun.apply(null,[1,2,3]) // 1 2 3
在上面的代码中,我们将1,2,3放在一个数组中一起传入到fun中,他们也分别对应fun的a,b,c三个参数。
call
call传入的参数的数量是不固定的,但是跟apply相同的是,第一个参数也是代表函数体内的this指向。从第二个参数开始,每个参数被依次传入函数。
function fun(a,b,c){
console.log(a,b,c)
}
fun.call(null,1,2,3) // 1,2,3
其实,js函数接收的参数在内部也是以类数组的形式表示的。
function fun(){
console.log(arguments)
}
fun.call(null,1,2,3)
以上我们可以看出apply比call的使用率更高,使用apply的话我们可以不去关注到底传入多少个参数,直接塞数组里推过去就行了。
call其实是apply的一个语法糖,如果我们明确的知道传入参数而且想一目了然的看清楚形参和实参的对照关系,也可以使用call当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指 向默认的宿主对象,在浏览器中则是window
bind
用来指定函数内部this指向,区别于apply和call的是,bind后函数不会立即执行而是返回一个新的函数,而另外两个则会直接执行返回执行结果。
function fun(){
console.log(this)
}
let a = {name:'a'}
console.log(fun.bind(a))
需要注意的是,
bind参数传递的方式与call一致。
实现 call apply bind new
先看看实现call apply的主要思路
- 第一个参数为
undefined或null的时候,将this指向window - 改变了
this指向,让新的对象执行该函数。
手写apply
Function.prototype.myApply = function(context){
if(typeof context === 'undefined' || context === null){
context = window
}
context.fn = this
const args = arguments[1]||[]
const result =context.fn(...args)
delete context.fn
return result
}
看看效果
手写call
Function.prototype.myCall = function(context){
if(typeof context === 'undefined' || context === null){
context = window
}
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
看看效果
手写new
new的流程
- 创建一个空的
js对象 - 必须为一个函数且不能为箭头函数
- 设置新对象的__proto__为构造函数的prototype
- 使用新的对象执行函数,函数的上下文为新的对象
- 如果函数设置了返回值并且返回值是一个对象的话返回该对象,否则返回新创建的
js对象
function myNew(fun){
if(typeof fun !== "function" || !fun.prototype) throw Error(`fun is not a constructor`);
const res = Object.create(fun.prototype);
const result = fun.apply(res,[...arguments].slice(1))
if(
(typeof result === "object" || typeof result === "function")
&&typeof result !== null
){
return result
}
return res
}
看看效果
手写bind
bind方法不会立即执行,需要返回一个待执行的函数- 实现作用域绑定
- 参数传递
Function.prototype.myBind = function () {
let that = this
const args = [...arguments]
return function () {
that.call(...args)
}
}
看看效果
最后
以上,就是本人对于this的一些浅显的理解,希望能对你有所帮助。
同时向大家推荐本人参与的vue3移动端开源组件库 varlet 欢迎大家 star && pr