this指向问题

492 阅读3分钟

默认绑定(函数直接调用)

function fn() {
  console.log(this) // window
}
fn()

function fn() {
  'use strict'
  console.log(this) // undefined
}
fn()
var a = 1
function fn() {
  var a = 2
  console.log(this.a) // 1
}
fn()
// 问:把最外层 var a = 1 -> let a = 1,输出结果是?
// 答:undefined
// 因为 var 默认是全局,而let有自己的作用域不在全局。
var b = 1
function outer () {
  var b = 2
  function inner () { 
    console.log(this.b) // 1
  }
  inner()
}
outer()
const obj = {
  a: 1,
  fn: function() {
    console.log(this.a)
  }
}
obj.fn() // 1
const f = obj.fn; // 赋值之后,this 指向丢失,重新指向 window
f() // undefined

隐式绑定(属性访问调用)

隐式绑定的 this 指的是调用堆栈的上一级.前面个)

function fn() {
    console.log(this.a)
}
const obj = {
    a: 1
}
obj.fn = fn
obj.fn() // 1
function fn() {
    console.log(this.a)
}
const obj1 = {
    a: 1,
    fn
}
const obj2 = {
    a: 2,
    obj1
}
obj2.obj1.fn() // 1

隐式绑定失效问题:

// 第一种 是前面提过的情况
const obj1 = {
    a: 1,
    fn: function () {
        console.log(this.a)
    }
}
const fn1 = obj1.fn // 将引用给了 fn1,等同于写了 function fn1() { console.log(this.a) }
fn1() // 所以这里其实已经变成了默认绑定规则了,该函数 fn1 执行的环境就是全局环境

// 第二种 setTimeout
setTimeout(obj1.fn, 1000) // 这里执行的环境同样是全局

// 第三种 函数作为参数传递
function run(fn) {
  fn()
}
run(obj1.fn) // 这里传进去的是一个引用

// 第四种 一般匿名函数也是会指向全局的
var name = 'The Window';
var obj = {
    name: 'My obj',
    getName: function() {
        return function() { // 这是一个匿名函数
            console.log(this.name)
        };
    }
}
obj.getName()()

// 第五种 函数赋值也会改变 this 指向
// 第六种 IIFE

显示绑定(callbindapply

function fn() {
    console.log(this.a)
}
const obj = {
    a: 100
}
fn.call(obj) // 100
function fn() {
    console.log(this)
}
fn.bind(1)(); // 1
fn.bind(1).bind(2)(); // 1
// boxing(装箱) -> (1 ----> Number(1))
// bind 只看第一个 bind(堆栈的上下文,上一个,写的顺序来看就是第一个)

let obj = {
    a: 2
}
fn.bind(obj)(); // {a: 2}

new

new 做了什么

  1. 创建一个空的简单 JavaScript 对象(即{})
  2. 链接该对象(设置该对象的constructor)到另一个对象
  3. 将步骤1新创建的对象作为 this 的上下文
  4. 如果该函数没有返回对象,则返回this

如果函数 constructor 里没有返回对象的话,this 指向的是 new 之后得到的实例

function foo(a) {
    this.a = a;
}
const f = new foo(2);
f.a // 2
function bar(a) {
    this.a = a;
    return {
        a: 100
    }
}
const b = new bar(3);
b.a // 100 因为如果this返回对象,那则返回对象

箭头函数

箭头函数这种情况比较特殊,编译期间确定的上下文,不会被改变,哪怕你 new,指向的就是上一层的上下文,

箭头函数本身是没有 this 的,继承的是外层的

function fn() {
    return {
        b: ()=>{
            console.log(this)
        }
    }
}
fn().b(); // window
fn().b.bind(1)(); // window
fn.bind(2)().b.bind(3)(); // 2

优先级

优先级「new 绑」 > 「显绑」 > 「隐绑」 > 「默认绑定」

// 隐式 vs 默认  -> 结论:隐式 > 默认
function fn() {
    console.log(this)
}
const obj = {
    fn
}
obj.fn() // {fn: ƒ}

// 显式 vs 隐式  -> 结论:显式 > 隐式
obj.fn.bind(5)() // 5

// new vs 显式 -> 结论:new > 显式
function foo (a) {
    this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // 2
// new
var baz = new bar(3)
console.log( baz.a ) // 3

// 箭头函数没有 this,比较没有意义

习题

function foo() {
    console.log(this.a) // 2
}
var a = 2;
(function () {
    "use strict" // 迷惑你的
    foo(); 
})();
var name="the window"

var object={
  name:"My Object", 
  getName: function(){ 
    return this.name
  } 
}
object.getName() // My Object
(object.getName)() // My Object // 加括号不影响
(object.getName = object.getName)() // window
(object.getName, object.getName)() // window
// 一旦有赋值,或者运算符的操作,都会丢失 this 指向
var x = 3
var obj3 = {
  x: 1,
  getX: function() {
    var x = 5
    return function() {
      return this.x
    }();
  }
}
console.log(obj3.getX()) // 3 上文 绑定失效问题,立即执行函数指向全局
function a(x) {
    this.x = x
    return this
}
var x = a(5)
var y = a(6)

console.log(x.x) // undefined 其实执行的是 Number(6).x
console.log(y.x) // 6

// 等价于
window.x = 5;
window.x = window;

window.x = 6;
window.y = window;
var foo = 123;
function print() {
    this.foo = 234;
    console.log(foo)
};
// print(); // 234 print里面没有foo,foo在全局,this.foo改的是全局
new print() // 123 此时this指的是print,print里面的this.foo=234,但是打印的是foo,所以是全局的foo
var a = 5;
function test() {
    a = 0;
    console.log(a);
    console.log(this.a); 
    var a;
    console.log(a)
}
// test(); // 0 5 0 // this指向全局
new test(); // 0 undefined 0 // this指向test,this里面没有a,所以是undefined