前言
在JS的学习过程中,this
是我们不可忽略的一个非常重要的关键字,许多企业级开发的代码中都会大量使用它,许多人都称之为JS的一座大山。本文将引用大量实例带大家彻底搞懂this这个关键字,理清楚this的使用规则。
this是一个对象吗
用new一个构造函数创建实例对象的过程中,我们知道,构造函数中会创建一个this对象。那么this真的是一个对象吗?我们可以在浏览器上打印一下this:
你会发现this其实代指的是一个window对象,注意我说的是代指。this本身不是一个对象,this
关键字的值取决于它在哪个上下文中使用。
当在全局作用域中使用时,this
指向全局对象,通常在浏览器环境中是 window
对象。在函数内部使用时,this
的值取决于函数的调用方式。总体来说,this
的值是在运行时动态确定的,它取决于函数被调用的方式。因此,this
不是固定的对象,而是在特定上下文中引用的对象。
this究竟有什么用
我可以直接告诉你,this就是用来简化代码的。那么我们先看一段代码:
function identify(){
return this.name
}
function speak(){
var friend = identify.call(this)
console.log(friend);
}
var me = {
name = 'Bill'
}
speak.call(me)
上述代码就是一个函数的传参问题。我们会发现,打印出来的是Bill。而原代码并没有传参过程。这里其实就是this
实现的了一个隐式传参,也就是我们看不到的传参过程。
这样使我们可以通过this
在函数体内部便捷的引用运行环境中的变量而不需要传参,极大的方便了开发。
那么this的这个匪夷所思功能背后的 真相究竟是什么?
this的绑定规则
我们是不是在this的引用过程中会发现,this最关键的地方就是它究竟代指的是谁?那么这里就要引入一个新的概念——this的绑定。那么this的绑定种类有4种:
1.默认绑定
默认绑定是多用于函数中的,就比如上述的函数的传参。规则也很简单: 函数在哪个词法作用域里生效,this就指向哪里的作用域,最终指向全局。
var a=1
function foo(){
console.log(this.a);
}
foo()
输出结果是1 。那么很明显这里this
代指的是全局作用域,foo
函数在全局里被调用,全局又定义了一个值为1的a
变量。
—— 那如果一个函数在另一个函数体内调用呢?
function foo(){
var a =2;
this.bar()
}
function bar() {
console.log(this.a);
}
foo()
这里打印的是undefined。这里的this.a
拿不到值,你会发现很奇怪,bar
函数不是在foo
中被调用了吗,所以this代指的是foo中的a?答案是this没有代指foo的作用域,而是代指了全局。在es6中,this有一个硬性规则:this无法访问一个词法作用域内部的内容。所以this在foo中拿不到a的值,又会顺着去找foo的词法作用域——全局。而全局没有定义a变量,所以是undefined
。
2.隐式绑定
成立于:当函数被一个对象所拥有(引用),且再被调用时此时this会指向该 对象,该对象就是函数调用时的上下文。而对象没有作用域,这也说明this不一定要代指含一个作用域。
//定义一个函数 且在一个对象中分别调用和引用
function foo(){
console.log(this.a);
}
var obj = {
a : 2,
foo: foo, //引用(拥有)
foo1:foo() //调用
}
obj.foo()
console.log(obj.foo1);
上述会有两个打印值:2 和 undefined。很明显,obj 中 foo:foo完成对函数的引用,而 foo1:foo( )是调用。所以我们在隐式绑定的过程中,一定要区分清楚函数的引用和调用。
——而隐式绑定中还有一个特殊情况:隐式丢失
function foo(){
console.log(this.name);
}
var obj = {
name :'Alice',
foo:foo
}
var say = obj.foo // 函数引用被赋值给变量 say()
say()
这里的打印的是undefined。在这个例子中,name
函数在 obj
上被定义,正常情况下,this
应该绑定到 obj
,输出"Alice"。然而,将 name
赋值给 say
变量后,say
在全局上下文中被调用,此时 this
的绑定丢失,指向了全局,输出了 undefined。这是因为在全局上下文中,this
默认指向 window
(浏览器环境)或 global
(Node.js 等环境),而全局对象上并没有 name
属性。
3.显示绑定
显示绑定中会用到三种内置的方法:call方法 , apply方法 , bind方法。
显示绑定允许你明确地指定一个对象作为函数调用时的上下文,而不依赖于默认的绑定规则,非常方便于开发。
——使用 call
方法:
var obj = { name: '小花' }
function sayName() {
console.log('My name is ' + this.name);
}
sayName.call(obj) // 输出 "My name is 小花"
在上面的例子中,call
方法被用于将 sayName
函数与不同的对象进行显式绑定,确保 this
代指传递给 call
方法的对象。
——call
、apply
、bind
的区别:
var obj = { name: 'Alice' };
function sayName(greeting)
{
console.log(greeting + ' My name is ' + this.name);
}
sayName.call(obj,'Hello')
sayName.apply(obj, ['Hello'])
var bar = sayName.bind(obj, 'Hello')
bar()
上述代码会输出三"Hello My name is Alice",三者绑定的规则类似,但是传参的形式不同。
call
接受一个普通的参数列表,
apply
接受一个参数数组而不是单独的参数列表。而bind
会返回一个新的函数,该函数的 this
被绑定到指定的对象。
4.new绑定
new
绑定是一种用于创建新对象的特殊绑定规则。当一个函数通过 new
关键字来调用时,称之为构造函数调用,此时会创建一个新的对象,并将这个新对象绑定到函数调用中的 this
上。
下面是一个使用 new
绑定的简单例子:
function Person(name) {
// 在使用 new 时,this 将指向新创建的对象
this.name = name;
}
var alice = new Person('Alice');
console.log(alice.name); // 输出 "Alice"
在这个例子中,Person
是一个构造函数,通过 new Person('Alice')
创建了一个新的对象 alice
。在构造函数内部,this.name
将成为新对象 alice
的属性。
实际上:使用 new
关键字时,构造函数内部发生了以下几个步骤:
- 创建一个新的空对象。
- 将新对象的内部
[[Prototype]]
属性(指向构造函数的原型对象)设置为构造函数的prototype
属性。 - 将构造函数内部的
this
绑定到新创建的对象上。 - 执行构造函数内部的代码,对新对象进行初始化操作。
- 如果构造函数没有显式返回一个对象,则返回新创建的对象。
那么需要注意的是,new
绑定是优先级最高的,如果同时存在多种绑定规则,new
绑定会覆盖其他规则。
this与箭头函数
var bar = () => {
//函数体
}
箭头函数是是es6新增的一种新的函数写法,省略了一些代码,例如不用写function。而箭头函数在this的使用方面又有不同的地方:
箭头函数没有自己的 this
绑定或者说它没有this属性,相当于this写在了箭头函数外部,它会继承父作用域中的 this
。
function cool() {
this.value = 1;
setTimeout( () => {
// 箭头函数继承了父作用域中的 this
this.value++;
console.log(this.value); // 输出 2
}, 1000);
}
cool()
这使得箭头函数在一些情况下更易于理解和使用,避免了传统函数中 this
绑定可能导致的问题。
总结
文章参考:《你不知道的JavaScript》
有任何想法和建议欢迎大家在评论区留言哦~
点个免费的赞鼓励支持一下吧! 个人Gitee仓库:Code Space: 记录学习code中的点点滴滴 (gitee.com)