(4)new绑定(构造函数)
函数作为构造函数使用 new 调用时, this 绑定的是新创建的构造函数的实例:
function Person(name,age){ this.name = name; this.age = age; this.say = function(){ console.log(this.name + ":" + this.age); } } var person = new Person("CUGGZ",18); console.log(person.name); // CUGGZ console.log(person.age); // 18 person.say(); // CUGGZ:18 复制代码
可以看到,在上面代码中,this 就指向了构造函数 Person 的新对象person,所以使用 this 可以获取到 person 对象的属性和方法。
实际上,在使用 new 调用构造函数时,会执行以下操作:
- 创建一个新对象;
- 构造函数的 prototype 被赋值给这个新对象的 proto;
- 将新对象赋给当前的 this;
- 执行构造函数;
如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象,如果返回的不是对象将被忽略。
3. this 的优先级
this 存在很多使用场景,当多个场景同时出现时,就要根据优先级来判断 this 的指向。优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
下面来看例子:
function foo (a) { console.log(this.a) } const obj1 = { a: 1, foo: foo } const obj2 = { a: 2, foo: foo } obj1.foo.call(obj2) obj2.foo.call(obj1) 复制代码
这里会输出:2 和 1,也就是说 call
、apply
、bind
的显式绑定相对于隐式绑定优先级更高。
function foo (a) { this.a = a } const obj1 = {} var bar = foo.bind(obj1) bar(2) console.log(obj1.a) 复制代码
这里将会输出2。通过 bind
将 bar
函数中的 this
绑定为 obj1
对象。执行 bar(2)
后,obj1.a
值为 2。即经过 bar(2)
执行后,obj1
对象为:{a: 2}
。
当再使用 bar
作为构造函数时:
var baz = new bar(3) console.log(baz.a) 复制代码
这里将会输出 3。bar
函数本身是通过 bind
方法构造的函数,其内部已经对将 this
绑定为 obj1
,它再作为构造函数,通过 new
调用时,返回的实例已经与 obj1
解绑。 也就是说:new
绑定修改了 bind
绑定中的 this
,因此 new
绑定的优先级比显式 bind
绑定更高。
所以,new
的方式优先级最高,接下来是 bind
这些函数,然后是 obj.foo()
这种调用方式,最后是 foo
这种调用方式,同时,箭头函数的 this
一旦被绑定,就不会再被任何方式所改变。
下面是判断this指向的流程图:
4. 特殊的 this 指向
除了上面几种this指向的规则之外,还有一些特殊的情况,他们的this指向与上述情况有所不同,下面就来看看这些情况。
(1)箭头函数
箭头函数会根据其声明的地方来决定 this:
const foo = { fn: function () { setTimeout(function() { console.log(this) }) } } console.log(foo.fn()) // window 复制代码
这里,this
出现在 setTimeout()
中的回调函数里,因此 this
指向 window
对象。如果需要 this
指向 foo
这个 object 对象,可以使用箭头函数解决:
const foo = { fn: function () { setTimeout(() => { console.log(this) }) } } console.log(foo.fn()) // {fn: ƒ} 复制代码
在箭头函数中,如果多层的嵌套,像下面这种情况:
function a() { return () => { return () => { console.log(this) } } } console.log(a()()()) 复制代码
由于箭头函数没有 this
,箭头函数中的 this
只取决包裹箭头函数的第一个普通函数的 this
。在这个例子中,因为包裹箭头函数的第一个普通函数是 a
,所以此时的 this
是 window
。
需要注意,箭头函数的 this 绑定是无法通过 call、apply、bind 方法修改的。且因为箭头函数没有构造函数 constructor,所以也不可以使用 new 调用,即不能作为构造函数,否则会报错。
(2)数组方法
来看下面的代码,在属性 arr 的 forEach 回调函数中输出 this,指向的是什么呢?
var obj = { arr: [1] } obj.arr.forEach(function() { console.log(this) }) 复制代码
其实输出的仍然是全局对象。
forEach 方法语法如下:
array.forEach(function(currentValue, index, arr), thisValue) 复制代码
其参数如下:
1)function(currentValue, index, arr):必需。 数组中每个元素需要调用的函数。
- currentValue:必需,当前元素
- index:可选,当前元素的索引值
- arr:可选,当前元素所属的数组对象
2)thisValue:可选,传递给函数的值一般用 "this" 值。如果这个参数为空, "undefined" 会传递给 "this" 值。
可以看到,forEach方法有两个参数,第一个是回调函数,第二个是 this 指向的对象,这里只传入了回调函数,第二个参数没有传入,默认为 undefined,所以会输出全局对象。
除了forEach方法,需要传入 this 指向的函数还有:every()、find()、findIndex()、map()、some(),在使用的时候需要注意。
(3)立即执行函数
立即执行函数就是定义后立刻调用的匿名函数:
var name = 'hello' var obj = { name: 'world', sayHello: function() { console.log(this.name) }, hello: function() { (function(cb) { cb() })(this.sayHello) } } obj.hello() // hello 复制代码
执行结果是 hello,是 window.name 的值。立即执行函数作为一个匿名函数,通常就是直接调用,而不会通过属性访问器(obj.fn)的形式来给它指定一个所在对象,所以它的 this 是确定的,就是默认的全局对象 window。
(4)setTimeout 和 setInterval
setTimeout 和 setInterval 中函数的 this 指向规则是一样的:
var name = 'hello' var obj = { name: 'world', hello: function() { setTimeout(function() { console.log(this.name) }) } } obj.hello() // hello 复制代码
this.name 是在 obj.hello () 里被调用的,结果却输出了 window.name。其实,延时效果(setTimeout)和定时效果(setInterval)都是在全局作用域下实现的。无论是 setTimeout 还是 setInterval 里传入的函数,都会首先被交到全局对象手上。因此,函数中 this 的值,会被自动指向 window。