JS 函数的 this 指向
1. this 在全局作用域下的指向
在浏览器中,this在全局作用域下指向 window
console.log(this)
var title = "hello" console.log(this.title) console.log(window.title)
|
2. this 的四个绑定规则
2.1 规则一:默认绑定
在独立函数调用的情况下会使用默认绑定
独立函数调用简单来说就是它没有绑定到某个对象上进行调用,简单看几个例子
function foo() { console.log(this) } foo()
|
function foo1() { console.log(this); }
function foo2() { foo1() }
function foo3() { foo2() }
foo3()
|
var obj = { name: 'hello', foo: function() { console.log(this); } }
var bar = obj.foo bar()
|
以上的函数调用的时候,都是没有被绑定到对象中调用的,所以 this 都指向 window
2.2 规则二:隐式绑定
函数通过某个对象进行调用的时候,这个对象会被 js 引擎绑定到函数中的 this 里
下面看几个例子:
function foo() { console.log(this); } var obj = { name: 'hello', fn: foo }
obj.fn()
|
var obj1 = { foo: function() { console.log(this); } }
var obj2 = { title: 'hello', bar: obj1.foo }
obj2.bar()
|
2.3 显式绑定
利用 call,apply,bind手动指定 this 的绑定,这种明确的绑定称为显式绑定
(1) call,apply
call,apply 都是可以调用函数的
fn.call(自定的this指向,参数1,参数2) fn.apply(this指向,[参数1,参数2])
两者的区别在于传参的方式,下面例子可以看出来 call 传入参数列表,apply是数组
function sum(num1, num2, num3) { console.log(num1 + num2 + num3, this); }
var obj = { title: "hello" }
sum.call("123", 20,30,40) sum.apply(obj, [20,30,40])
|
(2) bind
bind不会调用函数,而是返回一个新的对象。
function sum(num1, num2, num3) { console.log(num1 + num2 + num3, this); }
var newSum = sum.bind('aaa',10,20,30) newSum()
|
咦?newSum 不也是独立函数调用吗,怎么不指向 window 了
这就是默认绑定和显式绑定bind的冲突,显式绑定的优先级更高!
2.4 new绑定
function Person(name, age) { this.name = name this.age = age }
var p1 = new Person("jenny", 12) var p2 = new Person("tony", 15)
|
3. 一些内置函数的 this 绑定
有时候我们会调用一些 JS 的内置函数,这些函数要求我们传入另外一个函数,并且我们不会自己去调用这些函数,而是 JS 内部会帮助我们执行。
这个时候,this 的绑定就跟内部帮我们指向函数的方式有关系了。
看几个例子吧
3.1 setTimeout
内部通过 apply 调用函数,并绑定了this对象,是window
setTimeout(function() { console.log(this); }, 2000)
|
3.2 数组的内置函数 forEach
默认情况下传入的函数是自动调用函数(默认绑定)
var names = ['aaa', 'bbb','ccc'] names.forEach(function(item) { console.log(this); })
|
也可以通过传入第二个参数,改变this指向
var names = ['aaa', 'bbb','ccc'] names.forEach(function(item) { console.log(this); }, 'obj')
|
3.3 点击事件
在发生点击时,回调函数被调用,会将 this 绑定到该函数中
var box = document.querySelector(".box") box.onclick = function() { console.log(this); }
|
所以说,某些内置的函数,我们很难确定它内部是如何调用传入的回调函数的,可以自己测试一下,记一下,看源码当然更好啦
4. 几个规则的优先级
从高到低:new绑定 > 显式绑定(bind) > 隐式绑定 > 默认绑定
new绑定和call,apply是不允许同时使用的,(毕竟都会调用函数),所以不存在谁的优先级更高
显式绑定高于隐式绑定
function foo() { console.log(this); }
var obj = { name: 'obj', foo: foo }
obj.foo() obj.foo.call("aaa")
|
new 绑定高于隐式绑定
function foo() { console.log(this); }
var obj = { name: 'hello', foo: foo }
new obj.foo()
|
new 绑定高于bind
function foo() { console.log(this, title); }
var obj = { name: 'hello' }
var bar = foo.bind(obj) new bar()
|
5. 两种特殊情况
5.1 在显式绑定中传入 null 或 undefined
这时候,显式绑定会被忽略,使用默认规则
function foo() { console.log(this); }
var obj = { name: "hello", foo: foo }
foo.call(obj) foo.call(null) foo.call(undefined)
|
5.2 间接函数引用
function foo() { console.log(this); }
var obj1 = { name: 'obj1', foo: foo }
var obj2 = { name: 'obj2', }
console.log((obj2.foo = obj1.foo));
(obj2.foo = obj1.foo)()
|
6. 箭头函数的this
箭头函数不绑定 this,也就是说它没有自己的 this,而是根据外层作用域来决定this
var name = "hello"
var foo = () => { console.log(this); }
var obj = { foo: foo }
obj.foo() obj.foo.call("aaa")
|
再来看一个案例:
使用setTimeout模拟一个网络请求,如果我们想把返回来的数据保存到 data 中,该怎么做
var obj = { data: '', getData: function() { setTimeout(() => { console.log(this); this.data = 'hello' }, 2000); } } obj.getData()
|
7. this 的面试题
如果每题都会了,那么 this 绑定的问题,就是你的送分题而不是送命题了哈哈哈
建议把我的答案跟注释都删掉,自己一点点理清哦
题一
var name = "window";
var person = { name: "person", sayName: function () { console.log(this.name); } };
function sayName() { var sss = person.sayName; sss(); person.sayName(); (person.sayName)(); (b = person.sayName)(); }
sayName();
|
题二
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);
|
题三
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)
|
题四
var name = 'window' function Person (name) { this.name = name this.obj = { name: 'obj', foo1: function () { return function () { console.log(this.name) } }, foo2: function () { return () => { console.log(this.name) } } } } var person1 = new Person('person1') var person2 = new Person('person2')
person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)
person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
|