系列文章:javascript
this 机制在 javascript 中是动态绑定,或称为运行期绑定的。这就致使 JS 中的 this 关键字会有多重含义,因此会给咱们形成一误解。学习 this 的第一步是明白this既不指向函数自身也不指向函数的词法做用域。java
人们容易把 this 理解成指向函数自身,看下面的代码bash
function foo(num) {
console.log("foo: " + num
this.count++;
}
// 这里为 foo 添加了一个属性 count,初始化为 0
foo.count = 0;
for(var i = 0; i < 10; i++) {
if (i > 5) {
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count); // 0
// foo 确实被调用了4次,可是 foo.count 仍是 0,
// 是由于函数内部的 this 并非指向那个函数对象,
// 因此虽然属性名相同,可是根对象却不相同
复制代码
this指向函数的做用域,这个问题比较复杂,由于有时它是正确的,有时它的错误的。但能够明确的是:this在任何状况下,都不指向函数的词法做用域。闭包
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();
复制代码
结果是 undefined ,由于这里经过 this.bar() 来引用 bar 函数,这里能调用成功是由于:app
⚠️注意:这里有人会认为结果是 Reference Error 报错,其实和 RHS(Javascript进阶1--做用域和闭包 内有解释)混淆了。函数
javascript中,this 是在运行时进行绑定的,它的上下文取决于函数调用时的各类条件。oop
先根据把绑定的优先级抛出结论,按照如下顺序进行判断:post
函数是否在 new 中调用(new 绑定)?若是是的话,this 绑定的是新建立的对象。学习
var bar = new Foo()
ui
函数是否经过 call、apply(显式绑定)或者硬绑定调用?若是是的话, this 绑定的是指定的对象。
var bar = foo.call(obj2)
函数是否在某个上下文对象中调用(隐式绑定)?若是是的话,this 绑定的是那个上下文对象。
var bar = obj1.foo()
若是都不是,使用默认绑定,严格模式下,绑定到 undefined,不然绑定到全局对象。
var bar = foo()
在传统的面向类的语言中,构造函数是类的一些特殊方法,使用 new 初始化类时,会调用类中的构造函数。
首先,咱们须要澄清的一个误解是:在JS中,是没有构造函数的,在普通的函数调用前面加 new 关键字以后,就会把这个函数调用变成一个“构造函数调用”。实际上,new 会劫持全部普通函数并用构造函数的形式来调用它。
那么在JS中,使用 new 来调用函数,var obj = new Foo()
,会执行如下操做:
首先建立一个全新的空对象。
var obj = {}
将空对象的原型 [[prototype]] 赋值为构造函数的原型.
obj.__proto__ = Foo.prototype
更改 this 的指向。
Foo.call(obj)
若 return 有值,而且值是对象,则直接返回此对象,不然,返回新建立的对象 obj。
如今看一段简单的代码检测一下学习成果
function foo1(a) {
this.a = a;
}
function foo2(name) {
this.name = name;
return {
w:1
};
}
foo2.prototype.getName = function() {
return this.name;
};
var bar = new foo1(2);
console.log(bar.a) // 2
var a = new foo2('hh');
a.getName();
// Uncaught TypeError: a.getName is not a function
// 这是由于,foo2 有返回值,而且是一个对象,因此a值是 {w: 1}
复制代码
若是想调用函数时,强制把函数的 this 绑定到 obj 上,可使用 call(...) 和 apply(...) 方法,它们的第一个参数一个对象,是给 this 准备的,这称之为显式绑定。
function foo() {
console.log(this.a)
}
var obj = {
a: 2
};
foo.call(obj) //2
复制代码
⚠️注意:若是传入了一个原始值(布尔、数字、字符串等类型)来看成 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new Boolean(...)、new Number(...)、new String(...))。
硬绑定就是显式绑定的一个变种,用于咱们将 this 被永久绑定到 obj 的 foo 函数,这样咱们就没必要每次调用 foo 都在尾巴上加上 call 那么麻烦。
function foo() {
console.log(this.a)
}
var obj = {
a: 2
};
var bindFunc = foo.bind(obj)
bindFunc(); //2
复制代码
call/apply 与 bind 的区别是:call、apply将当即执行该函数,bind 不执行函数,只返回一个可供执行的函数。
当一个函数被一个对象调用时,会把函数调用中的 this 绑定到这个上下文对象中。
fucntion foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo() //2
复制代码
⚠️注意:当咱们使用隐式绑定规则时,要注意下面两点:
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo
};
var a = 'oops, global';
// obj.foo 引用 foo 函数自己,应用默认绑定
doFoo(obj.foo); // oops, global
var bar = obj.foo;
// bar 引用 foo 函数自己,应用默认绑定
bar(); // oops, global
复制代码
当直接使用不带任何修饰的函数引用进行函数调用时,只能使用默认绑定,没法应用其余规则。在严格模式下,this 会绑定到 undefined,在非严格模式下, this会绑定到全局对象。
若是把 null 或者 undefined 做为 this 的绑定对象传入 call、apply或者 bind,会应用默认绑定规则。
箭头函数根据外层(函数或全局)做用域来决定 this。
function foo() {
return (a) => {
// 继承自 foo()
console.log(this.a)
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call(obj1);
bar.call(obj2) // 2
复制代码
⚠️注意:箭头函数的绑定没法被修改
练习题1:
function Foo() {
getName = function() {
console.log(1);
};
return this;
}
// 静态方法赋值
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(3);
};
var getName = function() {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName(); //2
// 变量声明提高,赋值语句留在原地,因此结果是4
getName(); //4
// 在Foo函数中,重写了全局做用域下的 getName 函数
Foo().getName(); //1
getName(); //1
// 等价于new (Foo.getName)(), 运算符优先级:成员访问 > new(不带括号) > 函数调用
new Foo.getName(); //2
// 等价于 (new Foo()).getName(),运算符优先级:成员访问、new(带括号)的优先级同样,因此从左到右执行。
// 使用 new 实例 Foo 后生成的实例上,只有原型方法,没有静态方法
new Foo().getName();
复制代码
练习题2:
var title = 'world';
var a = {
title: 'hello',
alias: this.title,
show() {
console.log(this.title, this.alias)
},
ashow: () => {
console.log(this.title, this.alias)
}
}
a.show(); // hello world
var b = a.show;
b(); // world undefined
a.ashow(); // world undefined
复制代码