萌新小白基础理解篇之 this 关键字
前言
早在我们前几篇文章中,就有出现过 this ,但是我们一直没有详细解释 this 是什么,this 可以出现在哪,this 的用法又是如何?那这篇文章我们一起来看看吧!
一、为什么要有this?
this 是 js 中的一个关键字,它提供了一种更优雅的方式隐式的传递一个对象的引用,可以让代码更简洁易于复用,js 关键字是内置好的,拥有特殊语法含义的词,不能作为变量名,函数名,还有if,else,for等等关键字。我们来看一段代码感受一下。
function identify(context) {
return context.name.toUpperCase() //.toUpperCase() 让小写全转化为大写
}
function speek(context) {
var greeting = 'hello, I am ' + identify(context)
console.log(greeting);
}
var me = {
name: 'tom'
}
speek(me)
当代码运行到14行时带来speek()函数的调用,把me作为实参传进去,此时运行speek()函数又带来了identify() 的调用,将me作为实参传进去,返回得到大写的 TOM ,console.log(greeting)得到 hello,I am TOM。
如果用 this 我们可以怎么写
function identify() {
return this.name.toUpperCase()
}
function speek() {
var greeting = 'hello, I am ' + identify.call(this)
console.log(greeting);
}
var me = {
name: 'tom'
}
speek.call(me)
我们可以看到,上述结果是相同的,函数 speek 和 identify 不再接收 context 参数。
- 使用
this关键字直接访问调用上下文中的属性(如this.name)。 - 调用时,通过
.call(me)显式绑定this指向目标对象。下文会详细解释.call()用法 - 逻辑链条变为:对象 → 绑定为 this → 函数内部直接通过 this 访问。
它提供了一种更优雅的方式隐式的传递一个对象的引用,可以让代码更简洁易于复用。
二、this 可以出现在哪?
- 1.全局 (this === window)
- 2.函数体内
理论上,this 可以出现在任何地方,如果出现在全局,那么 统一代指 的是window,所以我们主要区分函数体内的 this 代指的是哪个,this 用在不同的地方,代指的内容是不一样的。
this 可以出现在块级作用域但是毫无意义
三、 this的绑定规则
1.默认绑定 --- 当函数独立调用时,函数中的 this 指向 window
var a = 1 // ===window:{a:1} 等于往window里面增加了 a为1
function foo(){
console.log(this.a) // 1
}
function bar () {
var a = 2
foo() //独立调用
}
bar()
this 如果出现在全局,那么它代指 window , 此时 this 出现在foo函数内,但是这个foo函数是被独立调用的,那么此时 this 依旧指向 window ,console.log(this.a) 为 1,什么叫独立调用呢? 独立调用 = 函数名直接加括号执行,没有任何对象或上下文“牵着”它。
2.隐式绑定 --- 当一个函数被一个上下文对象所拥有并被该对象调用,那么函数中的 this 指向该对象
var a = 1 // ===window:{a:1} 等于往window里面增加了 a为1
function foo(){
console.log(this.a) //3
}
function bar () {
var a = 2
foo() //独立调用
}
bar()
var test = {
a : 3,
foo :foo //引用函数
}
test.foo() //隐式绑定
我们可以看到 前面的 foo() 就是单独的函数名+括号的形式, 后面的为 test.foo() ,打个比方,就像你一个人逛街和你女朋友牵着你逛街的区别,你一个人逛街就叫独立调用,有女朋友牵着就不叫独立调用,此处我们称之为隐式绑定,而此时 this 指向的对象 就是 test ,所以此时 console.log(this.a) 为3
3.隐式丢失 --- 当一个函数被多层对象调用,函数的 this 指向最近的对象
function foo(){
console.log(this.a)
}
var obj = {
a:1,
foo : foo //key :value ,key 的名字可以随便取, 但 value 不可以随便
}
var oo = {
a : 2,
foo : obj
}
oo.foo.foo() //this 指向 obj
我们来捋一捋这个代码的逻辑,v8运行这段代码,运行到13行前,知道有一个 foo函数 ,有一个obj 对象,一个 oo 对象,当运行到13行时,有函数的调用,才开始读取它们的内容,那代码是从左往右执行,先读取 oo.foo ,那v8就要去oo里面找这个 foo 是什么,我们可以看到此时的 foo 值为 obj 对象,那就相当于 obj.foo(), 在去obj中 找 foo 是什么,此时 foo 的值 为foo 函数 ,然后() 开始foo函数的调用,所以相当于是 obj 调用了这个函数,此时 this 指向 obj ,也即当一个函数被多层对象调用,函数的 this 指向最近的对象。
4.显示绑定 --- 强行''掰弯'' this 指向一个对象 (三种方法)
-
fn.call(obj, x, y)
-
fn.apply(obj, [x,y])
-
fn.bind(obj, x, y)()
function foo(x,y){
console.log(this.a, x+y)
}
var A = {
a : 1
}
foo() //独立调用 指向 window
foo.call(A,1,2) //this 指向A 传递参数 1,2
foo.apply(A,[2,3]) // this 指向A 传入参数2,3
foo.bind(A,1,2)() //this 指向A,传入参数1,2
.call( obj, x, y) : 让 this 强行指向 A,可以逐个传递参数 (较为零散的方式传递参数)
.apply( obj, [x,y] ) : 让 this 强行指向 A,以数组的模式逐个传递参数 (较为集中的方式传递参数)
.bind( obj, x, y)() : 让 this 强行指向 A,但是执行完后一定会返回一个函数出来,并且要把它触发掉,也是零散的传递参数,也可以 const bar 来接收 返回的函数 再调用触发,可以分开传参
const bar = foo.bind(obj,x,y) const bar = foo.bind (obj,x) const bar = foo.bind (obj) bar() bar(y) bar(x,y)
5.new 绑定 --- new 的原理会导致函数的 this 指向实例对象
function Person(){
// var obj = {} //1
//Person.call(obj) //2
this.name = '杰哥' //3 等同于 obj.name = '杰哥'
// obj.__proto__ = Person.prototype //4
//return obj //5
}
const p = new Person() //此时的 p = obj
console.log(p) // {name : 杰哥}
我们在万物皆对象那篇文章中有讲到过 new 的工作原理,但当时并没有详细解释 this 所以表述其实并不准确,new 的具体工作原理应该是这样
-
创建一个空对象 即 var obj = {}
-
让函数体的 this 强行指向 实例对象 即 Person.call(obj)
-
运行函数内的代码逻辑
-
让对象的原型等于函数的原型 即 obj.proto = Person.prototype
-
返回这个对象 即 return obj
四、箭头函数
箭头函数没有 this 这个概念,写在箭头函数中的 this,也是它外层那个非箭头函数的
var bar = function(){ //函数表达式
}
bar()
var baz = (x,y) => { //函数表达式
}
如果不用到 this ,两种写法都是可以的,但如果用到 this 那我们需要注意一下了
function foo(){
var fn = () =>{ //箭头函数没有 this 这个概念
this.a = 2
}
fn()
}
var obj = {
a : 1,
bar:foo
}
obj.bar()
console.log(obj)
由于箭头函数没有 this 这个概念,写在箭头函数中的 this,也是它外层那个非箭头函数的,所以此时 this 是 foo的 ,而foo是通过obj.bar()调用的,所以 foo 的 this 指向 obj 对象,console.log(obj) 得到 { a : 1, bar : foo }
箭头函数不可以被new调用 (new的第二步无法执行,用了就会报错)
(如有补充,请大佬指点)