前言
在this之前,js当中没有一个机制可以让对象中的函数去访问对象自己的属性,如下面这段代码所示:调用对象obj中的方法bar访问obj内部属性,直接打印myname
会报错,倘若打印this.myname
就不会。所以,为了让对象中的函数有能力访问对象自己的属性,我们需要this
这样一个机制。
let obj = {
myname: 'qing',
bar: function(){ //在对象内部方法使用对象内部的属性
console.log(myname);
}
}
obj.bar()
this的指向
this
关键字能在全局、函数体内和欺骗词法作用域内被使用,不过欺骗词法用的太少,本文暂且先不讨论。
全局的this,在浏览器上代指Window
对象,在node上代指global
对象。那么在函数体内的this指向,有这么几个规则:
this的绑定规则
- 默认绑定:当一个函数独立调用,即不带任何修饰符的时候,函数在哪个词法作用域下生效,函数中的
this
就指向哪里。通俗点说,只要是默认绑定,this
就指向window
(因为一个函数不带任何前缀被独立调用的时候,就说明它被声明在了全局中) - 隐式绑定:当函数的引用有上下文对象的时候(当函数被某个对象所拥有时),函数的
this
指向引用它的对象 - 隐式丢失:当一个函数被多个对象链式调用时,函数的
this
指向就近的那个对象 - 显式绑定:调用方法
call apply bind
强行掰弯this
指向我们想要的地方去 - new绑定:当使用构造函数创建对象时,构造函数内部的
this
会绑定到新创建的对象实例上
词法作用域就是定义在词法阶段的作用域,函数被声明在哪里,哪里就是它的词法作用域。词法作用域是一生下来就决定了,并且一般不会再改变
请看以下例子来一一解释上面的规则:
- 函数在全局中被独立调用时,this指向window
function foo() {
console.log(this); // window
}
foo()
- 尽管foo函数在bar函数中被调用,foo函数的this还是指向window对象
function foo() {
console.log(this); // window
}
function bar() {
foo()
}
bar()
- 当foo函数被obj对象所拥有时,foo的this指向obj
var obj = {
a:1,
foo: function(){
console.log(this); // { a: 1, foo: [Function: foo] }
}
}
obj.foo()
- 函数obj2引用了函数obj,函数obj又引用了函数foo,foo被链式调用,this指向离它最近的那个对象obj
var obj = {
a: 1,
foo: foo //引用foo函数体
}
var obj2 = {
a: 2,
bar: obj
}
function foo() {
console.log(this.a); // 1
}
obj2.bar.foo()
- ①
call
方法将函数bar的this强行指向obj,它是Function
构造函数的显式原型上的方法
var obj = {
a:1
}
function bar() {
console.log(this.a); // 1
}
bar.call(obj)
② 若函数bar本身就有参数,就这样给call
方法传参:
var obj = {
a:1
}
function foo(x,y) {
console.log(this.a, x + y); // 1 5
}
foo.call(obj, 2, 3)
apply
方法与call
方法的作用一样:将函数foo的this指向强行变成函数obj,只不过接收参数的方式不一样,call
方法是零散地接收参数,而apply
方法会把参数用数组装起来
var obj = {
a:1
}
function foo(x,y) {
console.log(this.a, x + y); // 1 5
}
foo.apply(obj, [2, 3])
bind
方法也是将函数foo的this指向强行变成函数obj,但是调用bind
方法后默认返回一个函数体
var obj = {
a:1
}
function foo(x, y) {
console.log(this.a, x + y); // 1 5
}
var bar = foo.bind(obj, 2, 3)
bar();
bind
方法接收参数同样是零散的,但是不仅可以由bind()
自己接收,可以由返回的那个函数体bar()
接收,还可以两个混合帮忙接收;当混合接收了多余的参数时,就近算bind()
接收的参数为有效参数
// 参数由 bind 调用后返回的函数体接收
var bar = foo.bind(obj)
bar(2, 3)
// 由返回的函数体和 bind 混合接收
var bar = foo.bind(obj, 2)
bar(3)
// 接收了多余的参数,返回的函数体bar接收的参数就作废,最终打印出 1 6
var bar = foo.bind(obj, 2, 4)
bar(3)
Person
构造函数内部的“this”指向新创建的john
对象。this.name
和this.greet
都被绑定到了john
实例上
function Person(name) {
this.name = name;
this.greet = function() {
console.log("Hello, my name is " + this.name);
};
}
var john = new Person("John");
john.greet(); // 输出: "Hello, my name is John"
箭头函数
ES6起引入了一种函数的新写法————箭头函数,箭头函数没有this这个机制,但这并不是说箭头函数中不能写this,而是写在箭头函数中的this只能是它外层函数的this而非箭头函数自己的
function foo() {
var bar = () => {
console.log(this); // 是外层函数foo的this,指向window
}
bar()
}
foo()
总结
this只在全局和函数体中作用,关于this的指向问题应该重点记函数体内的情况:独立调用时指向window、被拥有时指向引用它的对象、链式调用时就近指向、call(),apply(),bind()可强行掰弯指向、构造函数内部this指向实例对象。