js中this指向全面总结

this指向是在函数被调用的时候绑定的

有小括号才是函数调用, 不要看到像obj.foo, 就认为this指向obj, 人家还没被调用呢
所以说, 分析问题不要想当然, 得根据规则一步一步走

以下总结并非绝对, 需要根据实际情况实际分析

一. 运算符优先级

运算符优先级 - JavaScript | MDN (mozilla.org)

二.this指向

2.1 默认指向

函数独立调用 => 没有被绑定到某个对象上调用 => 指向 window

2.1.1 普通函数调用

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

2.1.2 函数嵌套调用

function a() {
  console.log(this);  // window
  b()
}

function b() {
  console.log(this);  // window
}

a()

function aa() {
  console.log(this);	// window
  function bb() {
    console.log(this);	// window
  }
  bb()
}

aa()

2.1.3 函数作为参数

参数函数也被称作是回调函数, 也就是说回调函数中的this一般也指向window

function foo(fn) {
  // 没有进行任何绑定 => window
  fn()
}

function bar() {
  console.log(this);    // wibdow
}

foo(bar)

做一个变形

function foo(bar) {
  bar()
}

let obj = {
  bar() {
    console.log(this);		// window
  }
}

foo(obj.bar)

为什么是window ?

  • 因为foo中的bar没有任何绑定, 那你可能会想, 实参传入了obj.bar, 不是绑定在obj上吗
  • 首先: 实参传递参数没有进行函数调用, 此时this没有绑定, 其次, 要考虑 实参赋值给形参 的执行过程

上面的代码等价于:

let obj = {
  bar() {
    console.log(this);		// window
  }
}

let bar = obj.bar
bar()

2.2 对象调用

对象.函数名的方式调用 => 指向该对象

2.2.1

function foo() {
  console.log(this)  // obj
}

let obj = {
  foo
}

obj.foo()

2.2.2

function foo() {
  console.log(this); // obj1
}

var obj1 = {
  foo
}

var obj2 = {
  obj1
}

obj2.obj1.foo();
  • 首先是运算符的优先级: 成员访问和函数调用优先级相同, 因此从左向右顺序执行
  • obj2的obj1属性引用了obj1对象,再通过obj1对象调用foo方法 => obj1.foo() => obj1

2.3 call、apply、bind

这三个方法将this指向绑定在传入的对象上

2.3.1

function foo() {
  console.log(this.name)
}
let obj = {
  name: 'obj'
}

var name = 'window'  // 提问: 此处使用let定义, 下面一行打印什么
foo.call()  // window
foo.call(obj)  // obj
foo.apply(obj)  // obj
foo.bind(obj)() // obj

2.4 setTimeout、setInterval

指向window

setInterval(() => {
  console.log(this)  // window
}, 1000);

setTimeout(() => {
  console.log(this)  // window
}, 1000);

2.5 数组方法

像forEach、filter、some、every、map等数组方法

默认指向window, 但是可以指定this指向

let arr = [1]
// 注意: 这里用的不是箭头函数
arr.filter(function () {
  console.log(this)  // window
})

arr.filter(function () {
  console.log(this)  // { }
}, {})

2.6 DOM事件

默认指向事件对象

let div = document.querySelector("div");
div.onclick = function() {
  console.log(this); // div对象
}

2.7 new 实例化

默认指向实例对象

function Person(name) {
  this.name = name
  console.log(this)
}

let p1 = new Person(1)	// Person {name: 1}
let p2 = new Person(2)	// Person {name: 2}

2.8 箭头函数

箭头函数中的this是在定义函数的时候绑定, 而不是在函数执行的时候绑定

根据外层作用域来决定this

2.8.1

let obj = {
  foo: () => { console.log(this) }  // window
}

obj.foo()
// 因为箭头函数所在作用域是obj, 上层作用域是window

2.8.2

let obj = {
  foo: function () {
    let fn = () => { console.log(this) }  // obj
    return fn
  }
}

obj.foo()()
// fn所在作用域是 foo函数, 外层作用域是obj

三. 例题

示例1

function foo() {
  console.log(this);
}
var obj = {
  foo
}

new obj.foo()  // foo对象

执行顺序: obj.foo => 找到foo函数 => 对foo函数进行实例化

示例2

var obj = {
  items: [1, 2, 3],
  print: function () {
    console.log(this);				// obj

    this.items.forEach(function (item) {
      console.log(this);			// window,默认就是window,this指向通过传递参数改变
    })
  }
}
obj.print()

实例3

var name = 222
var a = {
  name: 111,
  say: function() {
    console.log(this.name)
  }
}

var fun = a.say
fun()  // 函数独立调用  =>  this指向window    
a.say()  // 对象调用  =>  指向调用的对象         

var b = {
  name: 333,
  say: function(fun) {
    fun()		// 函数独立调用  =>  this指向window
  }
}

b.say(a.say)      
b.say = a.say      
b.say()  // 对象调用  =>  指向调用的对象

// 总结: 花里胡哨的没有用, 记住上面的规则,一步步分析

示例4

var myObject = {
  foo: "bar",
  func: function() {
    var self = this
    console.log(this.foo)  // bar
    console.log(self.foo); // bar 
    (function() {
      console.log(this.foo)  // undefined =>  自执行函数  =>  函数独立调用  =>  window
      console.log(self.foo)  // bar
    }());
  }
};
myObject.func()

示例5

看自己的答案跟运行结果是否一致, 不一致的话再对比一下跟示例3 哪里不一样

let a = '全局'
let obj1 = {
  a: '局部',
  say: function () {
    console.log(this.a)
  }
}

obj1.say()  
let res = obj1.say
res()

let obj2 = {
  a: '局部2',
  say: function (fn) {
    fn()
  }
}
obj2.say(obj1.say)

obj2.say = obj1.say
obj2.say()

几道难一点的题目

示例6

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);





person1.foo1(); // person1
person1.foo1.call(person2); // person2

// 箭头函数的this指向只看上层作用域
person1.foo2() // window
person1.foo2.call(person2) // window

person1.foo3()() // window
// 虽然改变了this, 但是foo3执行后返回的函数在全局下自执行
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2

// 箭头函数的this指向只看上层作用域
person1.foo4()() // person1

// foo4()绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
// 这里不懂的, 建议看一下call方法的实现原理
// 简单来说, 就是把foo4当作person2内部的一个方法进行调用
person1.foo4.call(person2)() // person2

// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1

示例 7

示例6 变形

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
posted @ 2021-08-10 23:19  只猫  阅读(851)  评论(0编辑  收藏  举报