JS(ES5)里面有四种函数调用形式: 方法调用模式(当一个函数被保存为对象的一个属性时,称它为方法) 函数调用模式(当一个函数并非一个对象的属性时)、 构造器调用模式、 call或者apply调用模式
// 函数调用模式
func(p1, p2)
// 方法调用模式
obj.child.method(p1, p2)
// call或者apply调用模式
func.call(context, p1, p2)
func.call(context, p1, p2)才是正常调用形式, 其他两种都是语法糖,可以等价地变为 call 形式:
func(p1, p2)等价于 func.call(undefined, p1, p2);
obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2);
所以实际上函数的调用方式就一种: func.call(context, p1, p2) 我们所说的this就是context上下文
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。
// 浏览器中
this === window // true
'use strict'
this === window // true
function func() {
console.log(this);
}
func(); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
// 等价于
func.call(undefined) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
那为什么这段代码运行在浏览器中打印出的是window对象,而不是undefined呢?
因为浏览器里有一条规则:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
var person = {
name: 'unknown',
doSomething: function () {
console.log(this.name);
},
alfred: {
name: 'alfred',
doSomething: function () {
console.log(this.name);
}
}
};
person.doSomething(); // unknown
person.alfred.doSomething(); // alfred
person.doSomething() 等价于 person.doSomething.call(person)
person.alfred.doSomething() 等价于 person.alfred.doSomething.call(person.alfred)
[]中函数的this
function fn (){ console.log(this) }
function fn2(){ console.log('fn2')}
var arr = [fn, fn2]
arr[0]() // [ƒ, ƒ]
arr[0] = arr.0
通过call形式转换成arr.0.call(arr)
所以this指向当前数组
原型链中的this
一个方法存在于一个对象的原型链上,那么this指向的是调用这个方法的对象,就像该方法在对象上一样
var o = {
a: 2,
b: 7,
f: function () {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
getter 与 setter 中的 this
用作 getter 或 setter 的函数都会把 this 绑定到设置或获取属性的对象。
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum, enumerable: true, configurable: true});
console.log(o.average, o.sum); // logs 2, 6
一个函数如果创建的目的就是希望结合new操作符来调用,那它就是构造器函数。new操作符会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到这个新对象上。
当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。
function Person (name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
const person = new Person('alfred');
console.log('the person is called ' + person.getName()); // the person is called alfred
call、apply、bind方法可以重新指定this指向
var person = {
name: 'unknown',
doSomething: function () {
console.log(this.name);
},
alfred: {
name: 'alfred',
doSomething: function () {
console.log(this.name);
}
}
};
person.doSomething(); // unknown
person.alfred.doSomething(); // alfred
// 通过call方法重新指定this,指向到person对象,apply、bind和call同理
person.alfred.doSomething.call(person); // unknown
person.alfred.doSomething.apply(person); // unknown
person.alfred.doSomething.bind(person)(); // unknown
// 需要注意!!bind只生效一次!
person.alfred.doSomething.bind(person).bind(person.alfred)(); // unknown
箭头函数没有自己的this,arguments,super或 new.target。
在箭头函数中,this与封闭词法环境的this保持一致,this 被设置为他被创建时的环境。这同样适用于在其他函数内创建的箭头函数:这些箭头函数的this被设置为封闭的词法环境的。
在全局代码中,它将被设置为全局对象:
var func = ()=>{console.log(this)};
func(); // window
var person = {
name: 'unknown',
foo: function () {
var x = () => { console.log(this); };
return x;
},
alfred: {
name: 'alfred',
doSomething: () => {
console.log(this);
}
},
jyh: {
name: 'jyh',
doSomething: function () {
console.log(this);
}
}
};
// alfred.doSomething没有被包含在某个函数内,未形成封闭词法环境,所有this指向window对象
person.alfred.doSomething(); // window
person.jyh.doSomething(); // jyh
// foo返回的函数this在foo被调用时绑定
var func1 = person.foo();
func1(); // person
var func2 = person.foo;
// func2()调用时foo的this指向window
func2()(); // window
通过 call 或 apply 调用
由于 箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。
var func = () => {
console.log(this);
};
func(); // window
var person = {
name: 'unknown'
};
// call、bind同样无法改变箭头函数this指向
func.apply(person); // window
当函数被用作事件处理函数时,它的this指向触发事件的元素(一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定)
// 被调用时,将关联的元素变成蓝色
function bluify(e){
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象时为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
e.currentTarget为绑定事件的元素,e.target为触发事件的元素,所以this一般就指向e.target
内联事件处理函数被调用时,会指向监听器所在的DOM元素, 但只有外层代码中的this是这样设置的
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
alert 'button'
<button onclick="alert((function(){return this})());">
Show inner this
</button>
没有设置内部函数的this, 所以alert 'window'