likes
comments
collection
share

好吧,javaScript基础——this

作者站长头像
站长
· 阅读数 10

前言

this是什么?怎么改变this指向?

什么是this

中国文化博大精深。 小明看见小文迎面走来,小明问:“你吃了这个?” 问:小明所问的“这个”是什么?

一看这个问题真变态,你问我我怎么知道?奶茶、吃鸡腿、大饼子,好像都有嫌疑。 重来。

今天是中秋节,小明刚领完公司发的月饼,刚转身就看见小文拿着一个空月饼带着迎面走来,小明开口问道:“你吃了这个?” 问:小明所问的“这个”是什么?

如果不出意外,“这个”指代的是月饼。

文字的理解需要结合语境,同一句话在不同的语境下会被赋予不同的意义,函数也是。所以我们需要有一个东西或者规则来明确当前函数的运行环境,于是this就这么诞生了。

记住,this代表的是函数运行的环境,而不是函数自己的作用域。

函数定义之后,可以在不同的环境下运行,this则是用来描述函数运行的环境。我们可以从this获取到运行环境中的变量。

懂了?那我们来试一试吧!

console.log(this)

// this指向window,直接打印输出global对象
// Window {window: Window, self: Window, document: document, name: '', location: Location, …}

var a = 1
function test(){
   console.log(this.a)
}

test() // 1 函数在window环境下运行,打印this.a输出window.a,即输出1
console.log(this.a) // 1 输出window.a

是不是很简单?是不是可以理解为在哪个上下文中执行,哪里就是函数的执行环境呢?那再看看这几个例子。

const b = {
    name: 'jack',
    myName: function(){
        console.log(this.name)
    }
}
b.myName() // jack  说好的在哪执行,this就是当前所在的执行环境呢?
const myName = b.myName // 引用赋值
myName() // undefined 为什么这个又是window?

刚总结的规则,怎么到这里又不适用了呢? 这种变化有没有规律可循呢? 答案是肯定的。 this 绑定大概分以下几种:

  1. 默认绑定
  2. 隐式绑定
  3. 硬绑定
  4. new

this 的绑定规则

默认绑定

独立函数的调用。没有应用其他规则时的默认规则。 如:

console.log(this)

// this指向window,直接打印输出global对象
// Window {window: Window, self: Window, document: document, name: '', location: Location, …}

var a = 1
function test(){
   console.log(this.a)
}
function testTwice(){
    "use strict"
    console.log(this.a)
}
test() // 1 函数在window环境下运行,打印this.a输出window.a,即输出1
console.log(this.a) // 1 输出window.a

testTwice() // TypeError: this is undefined 

test()是直接调用的,没有经过其他修饰符来修饰,因此使用的是默认绑定,且,当前为非 strict mode模式下,所以this绑定到了全局对象上。 strict mode模式下,this会绑定到undefined

隐式绑定

当函数有上下文对象时,隐式绑定会把函数中的this绑定到这个上下文对象。且,对象属性的引用链中只有上一层,或者说最后一层在调用位置中起作用。

const b = {
    name: 'jack',
    myName: function(){
        console.log(this.name)
    }
}
b.myName() // jack  
// 这里调用的上下文是b, myName属于b的一个属性,  
// 所以myName中的this绑定到了上下文对象b中。
const myFunc = b.myName 
// 引用赋值,此时myFunc被赋予了b.myName函数的引用  
// b.myName这个引用指向函数myName,所以实际上myFunc得到的是一个函数  
// 则myFunc()执行的时候使用的是默认绑定,this绑定到了window,而window对象中并没有name属性
// 故输出undefined
myFunc() // undefined 

这里出现了一个指针丢失的情况,如果不能准确定位this指代的运行环境,那在使用函数的时候,很有可能最后得到的结果并不是我们所预期的。 另外,参数传递也是一种隐式赋值,这种更微妙,更容易让人觉得出乎意料。

function doFoo(fn){
    // fn传入的实际上是fn函数的引用,调用的时候实际上用的函数本身,所以this指针应用了默认绑定
    fn()
}
const b = {
    name: 'jack',
    myName: function (){
    console.log(this.name)
    }
}
var name = 'lucy'
doFoo(b.myName) // lucy

同理,js里有一些内置的函数,这些函数可以看成为一个生命好的全局函数,那当我们把自定义函数传入内置函数的时候,实际上就是上述的这种隐式赋值。

setTimeout(function(){
var b = 2
    console.log(this.b)
}, 500)

// undefined 

setTimeout 在js的内置执行环境中,等价于

function setTimeout(func, delay){
    // 等待delay毫秒后执行func
    func() // 执行位置,按照执行位置的理解,这里的this应该是setTimeout函数对象  
    // 但实际上因为这里是隐式赋值,实际上调用的就是函数本身,所以最终使用的是默认绑定
}

setTimeout() // 函数执行的环境在window,所以setTimeout的this指向window, 
// func this->setTimeout this = window

显然这种隐式操作导致的this丢失的问题稍不留神就会让人判断错误,这让函数的运行结果似乎变得不稳定起来。 如果我们不想在对象内部包含函数的引用,而是想在某个对象上强制使用函数,该怎么做呢? call apply

显示绑定

call apply除一些十分特殊的函数外,绝大多数内置函数和自定义函数都可以应用call apply方法改变this的绑定对象。

function foo(){
    console.log(this.name)
}
    
const b = {
    name: 'jack',
}

foo.call(b) // jack 通过调用call,成功的把this绑定到对象b上

这样能完美解决隐式丢失的问题么?很可惜,不能。但是它的一个变种可以解决这个问题。

硬绑定

ES5提供了一个内置方法Function.prototype.bindbind(...)会返回一个硬编码的新函数,他会把你指定的参数设置为this的上下文并且调用。

function foo(){
    console.log(this.name)
}
    
const b = {
    name: 'jack',
}

foo.bind(b)() // jack 通过调用bind,成功的把this绑定到对象b上

猜一下这个函数是怎么实现的呢?如果让你来实现一个bind函数你会怎么写? 首先猜它有暗箱操作,在bind函数内部调用了call 或者 apply,并且bind函数把这种绑定当作一种结果返回了。 尝试写一下:

function myBind(fn, obj){
    return fn.apply(obj, arguments)
}

function foo(){
    console.log(this.name)
}
    
const b = {
    name: 'jack',
}
myBind(foo, b)

但是我们只是想绑定,并不想绑定的时候就立即执行,那我们可以改造一下。
function myBind(obj){
    const fn = this // 将fn指定为myBind前面的调用对象(函数也是对象)
    return function(){
        fn.apply(obj, arguments)
    }
}
Function.prototype.myBind = myBind
foo.myBind(b)() // jack

new

new 操作符做了什么?

  1. 构造一个全新的对象
  2. 将新对象的[[prototype]]连接
  3. 这个新对象绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function test(a){
    this.a = a
   console.log(this.a)
}

test() // 默认绑定会将this绑定到window,故而输出1

var newTest = new test(2) // 2 构造了新对象,并将this绑定到新对象上,故输出2
console.log(newTest) // {a: 2}

优先级比较

默认绑定是再没有其他绑定修饰的时候应用的绑定规则,故默认绑定优先级最低 上面的例子里有显示绑定改变this的绑定对象的例子,故默认绑定<隐式绑定<显式绑定 显示绑定、硬绑定、new的比较:

function foo(e){
    this.a = e
}

var obj_1={}
var bar_bind = foo.bind(obj_1)
bar(2)
console.log(obj_1.a) // 2 成功改变this

var bar_new = new bar_bind(3) // new 构造了一个新对象
console.log(obj_1.a) // 2 原有对象属性未改变
console.log(bar_new.a) // 3 构造的新对象,并且成功修改值

箭头函数

箭头函数的this指向:

  1. 默认绑定外层this
  2. 箭头函数this无法改变,在定义的时候就定了。 ,但是:
var b = {
    name: 'jack',
    myName: ()=>console.log(this.name)
}

b.myName() // window

——————————————————————————————————————————————————————————————
多层嵌套
const obj = { 
a: function() {
    console.log(this)
   },
b: {
    c: () => {console.log(this)}
   }
}
obj.a() //没有使用箭头函数打出的是obj
obj.b.c() //打出的是window对象
——————————————————————————————————————————————————————————————
var b = {
    name: 'jack',
    myName(){console.log(this)}
}

b.myName() // {name: 'jack', myName: ƒ}

更多关于this的疑问可以留言,暂时想到这么多。

转载自:https://juejin.cn/post/7144914509948272647
评论
请登录