关于this的总结和理解

172 阅读3分钟

约定以下只讨论浏览器下的this

函数的this是在调用时绑定的,this的指向取决于函数调用的位置。

调用位置:函数在代码中被调用的位置,而不是声明的位置。难点在于编程模式可能会隐藏真正的调用位置。

绑定规则

默认绑定

  1. 非严格模式下,在全局作用域中,即所有函数体的外部,this指向Window
<script type="text/javascript" charset="utf-8">
	console.log(this) //Window
</script>
  1. 非严格模式下,在函数作用域中,this指向Window
<script type="text/javascript" charset="utf-8">
	function foo(){
	    console.log(this) // Window
	}
	foo() 
</script>
  1. 严格模式下,在全局作用域中,即所有函数体的外部,this指向Window或者undefined
//在js脚本中
<script type="text/javascript" charset="utf-8">
       "use strict"
	console.log(this) //Window
</script>

//在模块中,自动开启严格模式
<script type="module" charset="utf-8">
	console.log(this) //undefined
</script>
  1. 严格模式下,在函数作用域中,this的值为undefined
<script type="text/javascript" charset="utf-8">
	function foo(){
	   "use strict"
	    console.log(this) // Window
	}
	foo() 
</script>

隐式绑定

调用位置上存在上下文

  1. 实例
//普通版
 function foo (){
     console.log(this.a)
 }
 var obj = {
     a:2,
     foo:foo
 }
 obj.foo() //2
 
 //加强版:对象属性引用链中只有最后一层会影响调用位置
 function foo (){
     console.log(this.a)
 }
 var obj2 = {
     a : 42
     foo:foo
 }
 var obj1 = {
     a : 2,
     obj2 : obj2
 }
 obj1.obj2.foo() //42
  1. 方式二 绑定丢失
function foo(){
    console.log(this.a)
}
var obj = {
    a : 2,
    foo : foo
}
var a = 'oops,global'
obj.foo()  // '2'
var bar = obj.foo //注意:函数作为参数传递给函数的时候也相当于一次赋值。 
bar() // 'oops,global'

显示绑定

  1. call、apply、bind进行绑定
funciton foo(){
    console.log(this)
}
var obj = {
    a : 2
}
foo.call(obj) // 2
foo.apply(obj) // 2
foo.bind(obj)() //2
  1. 硬绑定:将显示绑定包裹在函数中
function foo (){
    console.log(this.a)
}
var obj = {
    a : 2
}
var bar = function(){
    foo.call(obj)
}
bar() // 2
setTimeout(bar, 100) // 2
bar.call(window) //2  硬绑定的this一旦绑定了就不能修改了
  • 如何实现一个bind函数
//ES5
Function.prototype.bind = function(){
    var self = this
    //获取调用bind时候传递的参数
    var rest1 = Array.prototype.slice.call(arguments)
    var context = rest1.shift()
    return function(){
        var rest2 = Array.prototype.slice.call(arguments)
        return self.apply(context,rest1.concat(rest2))
    }
}
//ES6
Function.prototype.bind = function(...rest1)
{
    const context = rest1.shit()
    return (...rest2)=>{
        return self.apply(context,[...rest1,...rest2])
    }
    
}

new绑定

使用new创建一个对象的时候发生了什么

function myNew = function(func){
    var o = Object.create(func.prototype) //对应下面第一条 和 第二条
    var k =  func.call(o)  //对应第三条 第四条
    //对应第5条
    if(typeof k ==='object'){
        return k
    }else{
        return o
    }
}
  1. 创建一个全新的空对象
  2. 这个新对象会被执行[[Prototype]]连接(构造函数的prototype被赋值给这个新对象的_proto_)
  3. 构造函数的this指向这个对象
  4. 执行构造方法,属性和方法被添加到这个对象中
  5. 如果函数没有返回其他对象,this指向这个新对象。如果有返回值则返回构造函数中返回的对象
function Foo (a){
    this.a = a
}
var bar = new Foo(2)
bar.a // 2
  1. 如果函数的返回值是fuction或object,this指向返回的对象。
function Foo (a){
    this.a = a
    return {
        a:2
    }
}
var bar = new Foo(4)
bar.a  // 2

绑定的优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

  1. 显式绑定和隐式绑定的比较
function foo (){
    console.log(this.a)
}
var obj1 = {
    a : 1,
    foo : foo
}
var obj2 = {
    a : 2,
    foo : foo
}
obj1.foo() //1
obj2.foo() //2
obj1.foo.call(obj2) //2
obj2.foo.call(obj1) //1 
说明:显示绑定 > 隐式绑定
  1. new绑定和隐式绑定
function foo (){
    console.log(this.a)
}
var obj1 = {
    a : 1,
    foo : foo
}
var obj2 = {
    a : 2,
    foo : foo
}
obj1.foo() //1
obj1.foo.call(obj2) //2

var bar = new obj1.foo()

例外

  1. 如果把null、undefine作为this的绑定对象传入call、apply、bind,非严格模式下,实际应用的是默认绑定
function foo (){
    console.log(this.a)
}
var a = 1
foo.call(null) //1

箭头函数

待补充

练习题

var a = 20

var obj = {
    a: 40,
    foo:() => {
        console.log(this.a)
        function func() {
            this.a = 60
            console.log(this.a)
        }
        func.prototype.a = 50
        return func
    }
}
var bar = obj.foo()        // 20
bar()                      // 60
new bar()                  // 60
var a = 20

var obj = {
    a: 40,
    foo: function() {
        console.log(this.a)
        
        function func() {
            this.a = 60
            console.log(this.a)
        }
        
        func.prototype.a = 50
        return func
    }
}

var bar = obj.foo()        // 40
bar()                      // 60
new bar()                  // 60