【JavaScrip学习笔记】Part 2-this的那些事

1.关于this

1.1 对this的一些误解

  • 误解一:把this理解成指向函数自身
function foo(num){
    
    
    console.log('foo:' + num)
    this.count++
}
foo.count = 0

var i

for(i=0;i<10;i++){
    
    
    if(i>5){
    
    
        foo(i)
    }
}
console.log(foo.count) //打印0

好家伙居然打印0,可以看出来这样理解this是错误的,this不一定指向函数自身

实际上这里创建了一个全局变量count。

function foo(){
    
    
    foo.count = 4  //foo指向它自身
}
setTimeout(function(){
    
    
    //匿名函数无法指向自身
},10)

因此这样就可以了:

function foo(num){
    
    
    console.log('foo:' + num)
    foo.count++
}
foo.count = 0

var i

for(i=0;i<10;i++){
    
    
    if(i>5){
    
    
        foo(i)
    }
}
console.log(foo.count) //打印4

但这样回避了this的问题,所以我们可以强行让this指向函数本身。

function foo(num){
    
    
    console.log('foo:' + num)
    this.count++
}
foo.count = 0

var i

for(i=0;i<10;i++){
    
    
    if(i>5){
    
    
        foo.call(foo,i)
    }
}

console.log(foo.count)

call()可以确保this指向函数本身。

  • 误解二:this指向函数的作用域(某种情况下是正确的,但有些情况又是错误的)

this在任何情况下都不指向函数的词法作用域。

因此,this在运行时绑定,不是在编写时绑定,其上下文取决于函数调用的各种条件。

当一个函数被调用时,会创建一个活动记录,这个记录包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息

2.this全面解析

调用位置:函数在代码中被调用的位置

调用栈:为了到达当前执行位置所调用的所有函数

function baz(){
    
    
    //当前调用栈为baz
    //当前调用位置是全局作用域
    console.log('baz')
    bar() // <--bar的调用位置
}
function bar(){
    
    
    //当前调用栈:baz -> bar
    //当前调用位置在baz中
    console.log('bar')
    foo() // <--foo的调用位置
}
function foo(){
    
    
    //当前调用栈是baz -> bar -> foo
    //当前调用位置在bar中
    console.log('foo')
}
baz() // <--baz的调用位置

2.1 绑定规则

  1. 默认绑定
function foo(){
    
    
    console.log(this.a)
}
var a = 2
foo()  //打印2

this.a被解析成全局变量a,因为此时foo()是直接使用不带任何修饰的函数引用进行调用的,所以只能使用默认绑定。

  1. 隐式绑定
function foo(){
    
    
    console.log(this.a)
}
var obj = {
    
    
    a:2,
    foo:foo
}
obj.foo() //打印2

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,此处foo()被调用时this绑定到了obj上。

即在一个对象的内部包含一个指向函数的属性,并通过这个属性简介引用函数,从而把this隐式地绑定到这个对象上。

隐式丢失:

function foo(){
    
    
    console.log(this.a)
}
var obj = {
    
    
    a:2,
    foo:foo
}
var bar = obj.foo
var a = "???"
bar()  //打印???

此时bar引用了foo函数本身,因此此时的bar是一个不带任何修饰的函数调用,所以应用了默认绑定。

在回调函数中也会出现这样的情况。

function foo(){
    
    
    console.log(this.a)
}
function doFoo(fn){
    
    
    //fn其实引用的是foo
    fn()  // <-调用位置
}
var obj = {
    
    
    a:2,
    foo:foo
}
var a = '???'
doFoo(obj.foo) //打印???

这里其实和上面一样,因为参数传递是一种隐式赋值,所以我们传入函数也会被隐式赋值,最终结果和上面一样是在doFoo()中调用foo函数本身,应用默认绑定。

  1. 显示绑定

Javascript提供的大多数函数和自己创建的函数都可以使用call()方法和apply()方法。

function foo(){
    
    
    console.log(this.a)
}
var obj = {
    
    
    a:2
}
foo.call(obj)  //打印2

这里用call方法把foo的this显式绑定在obj上了。

如果传入一些字符串类型、布尔类型等的值来当作this的绑定对象,它会自动转换成对象形式,称为装箱

但是单纯的显式绑定无法解决丢失绑定的问题。

  • 硬绑定:es5提供了一个内置方法bind()
function foo(something){
    
    
    console.log(this.a,something)
    return this.a + something
}
var obj = {
    
    
    a:2
}
var bar = foo.bind(obj)
var b = bar(3)
console.log(b)  //打印5

bind会把指定的参数设置为this的上下文并调用原始函数。

  • API调用的"上下文"

许多内置函数都提供一个可选参数称为”上下文“,其作用和bind一样,确保回调函数使用指定的this。

function foo(el){
    
    
    console.log(el,this.id)
}
var obj = {
    
    
    id:"jjy"
}
//调用foo()时把this绑定到obj
[1,2,3].forEach(foo,obj)
// 打印1 jjy 2 jjy 3 jjy
  1. new绑定

使用new来调用函数时,会执行以下操作:

  • 创建一个新的对象
  • 这个对象会被执行[[Prototype]]连接
  • 这个对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a){
    
    
    this.a = a
}
var bar = new foo(2)
console.log(bar.a)  //2

2.2 优先级

new绑定 -> 显示绑定(call,apply,bind) -> 隐式绑定 -> 默认绑定

但凡事总有例外

  1. 当把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时候会被忽略,此时使用默认绑定。

    通常用apply来展开一个数组并当做参数传入时候会用上,或者用bind进行柯里化时:

    function foo(a,b){
          
          
        console.log("a:"+a+",b:"+b)
    }
    //把数组展开成参数
    foo.apply(null,[2,3])
    
    //使用bind进行柯里化
    var bar = foo.bind(null,2)
    bar(3)
    

    在ES6中可以使用...来展开数组,比如:foo(…[1,2])

    但这样不安全,可以用传入一个空对象{}或者用Object.create(null)来代替传入null,这样可以避免某些情况下污染全局对象

  2. 间接引用

    我们可能有时候会创建一个函数的“间接引用”,在这种情况下调用这个函数会应用默认绑定规则。

  3. 软绑定

    自己定义一个软绑定函数,区别于bind(),这个可以手动修改this的绑定对象

2.3 this词法

箭头函数无法使用前面这些规则,根据外层作用域来决定this。

function foo(){
    
    
    return (a) => {
    
    
        console.log(this.a)
    }
}
var obj1 = {
    
    a:2}
var obj2 = {
    
    a:3}

var bar = foo.call(obj1)
bar.call(obj2)  //打印2

foo()内部创建的箭头函数会捕获调用时foo()的this,由于foo()的this绑定到obj1,所以bar引用的箭头函数的this也会绑定到obj1,而且箭头函数的绑定无法修改

猜你喜欢

转载自blog.csdn.net/weixin_40764047/article/details/111088648