关于this
this提供一种更优雅的方式来隐式传递一个对象引用
记录函数的调用次数,第一种错误的理解,将this理解为指向函数自身
function foo(num) {
console.log("foo: "+num);
this.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
if (i>5){
foo(i);
}
}
console.log(foo.count);
console.log(count);
通过词法作用域改正
function foo(num) {
console.log("foo: "+num);
data.count++;
}
let data={
count:0
};
for (let i=0;i<10;i++){
if (i>5){
foo(i);
}
}
console.log(data.count);
通过函数名来引用函数对象(匿名函数只有通过arguments.callee,但已经弃用),但也回避了this,也依赖了foo的词法作用域
function foo(num) {
console.log("foo: "+num);
foo.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
if (i>5){
foo(i);
}
}
console.log(foo.count);
强制this指向foo对象
function foo(num) {
console.log("foo: "+num);
this.count++;
}
foo.count=0;
for (let i=0;i<10;i++){
if (i>5){
foo.call(foo,i);
}
}
console.log(foo.count);
第二种错误的理解,this指向函数的作用域
function foo() {
let a=2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();
this是在运行时绑定的,它的上下文取决于函数调用时的条件,this的绑定也函数声明的位置没有任何关系,只取决于函数的调用方式
this全面解析
调用栈和调用位置
调用栈:全局作用域-->baz-->bar-->foo
function baz() {
console.log('baz');
bar();
}
function bar() {
console.log('bar');
foo();
}
function foo() {
console.log('foo');
}
baz();
绑定规则
- 默认绑定,不带修饰的函数调用,会将this绑定到全局对象上,严格模式(strict mode)绑定到undefined
function foo() {
console.log(this.a);
}
let a=2;
foo();
function foo() {
"use strict";
console.log(this.a);
}
let a=2;
foo();
严格模式下与函数调用位置无关
function foo() {
console.log(this.a);
}
let a=2;
(function () {
"use strict";
foo()
})();
- 隐式绑定,调用位置是否有上下文对象,或者说是否被某个对象拥有或包含
function foo() {
console.log(this.a);
}
let obj={
a:2,
foo:foo
};
obj.foo();
对象属性引用链中只有最顶层或者说最后一层会影响调用位置
function foo() {
console.log(this.a);
}
let obj2={
a:42,
foo:foo
};
let obj1={
a:2,
obj2:obj2
};
obj1.obj2.foo();
隐式丢失,即隐式绑定会丢失绑定对象,然后应用默认绑定
function foo() {
console.log(this.a);
}
let obj={
a:2,
foo:foo
};
let bar=obj.foo;
let a="oops";
bar();
function foo() {
console.log(this.a);
}
let obj={
a:2,
foo:foo
};
let a="oops";
setTimeout(obj.foo,100);
- 显示绑定,使用call()和apply()强制把第一个参数(原始值会转换为对象,如string,number)对象绑定到this上
function foo() {
console.log(this.a);
}
let obj={
a:2
};
foo.call(obj);
硬绑定
function foo() {
console.log(this.a);
}
let obj={
a:2
};
let bar=function () {
foo.call(obj);
};
bar();
setTimeout(bar,100);
bar.call(window);
包裹函数
function foo(something) {
console.log(this.a,something);
return this.a+something;
}
let obj={
a:2
};
let bar=function () {
return foo.apply(obj,arguments);
};
let b=bar(3);
console.log(b);
可重复使用的辅助函数
function foo(something) {
console.log(this.a,something);
return this.a+something;
}
function bind(fn,obj) {
return function () {
return fn.apply(obj,arguments);
};
}
let obj={
a:2
};
let bar=bind(foo,obj);
let b=bar(3);
console.log(b);
ES5后提供内置的Function.prototype.bind方法
function foo(something) {
console.log(this.a,something);
return this.a+something;
}
let obj={
a:2
};
let bar=foo.bind(obj);
let b=bar(3);
console.log(b);
内置函数中的一个可选参数context(上下文),其作用和bind一样,确保回调使用指定的this
function foo(el) {
console.log(el,this.id);
}
let obj={
id:"awesome"
};
[1,2,3].forEach(foo,obj);
- new绑定
JavaScript中实际不存在构造函数,只有对函数的构造调用,使用new来调用函数时会自动执行以下操作
- 创建一个全新的对象
- 这个对象会被执行[[原型]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么就会自动返回新对象
function foo(a) {
this.a=a;
}
let bar=new foo(2);
console.log(bar.a);
优先级
显示绑定高于隐式绑定
function foo(a) {
console.log(this.a);
}
let obj1= {
a: 2,
foo: foo
};
let obj2={
a:3,
foo:foo
};
obj1.foo();
obj2.foo();
obj1.foo.call(obj2);
obj2.foo.call(obj1);
new绑定高于隐式绑定
function foo(something) {
this.a=something;
}
let obj1={
foo:foo
};
let obj2={};
obj1.foo(2);
console.log(obj1.a);
obj1.foo.call(obj2,3);
console.log(obj2.a);
let bar=new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);
new绑定高于显示绑定(如果硬绑定函数被new调用,新创建的this就会替换硬绑定this)
function foo(something) {
this.a=something;
}
let obj1={};
let bar=foo.bind(obj1);
bar(2);
console.log(obj1.a);
let baz=new bar(3);
console.log(obj1.a);
console.log(baz.a);
bind可以把除第一个参数外的其他参数传递给下层函数
function foo(p1, p2) {
this.val=p1+p2;
}
let bar=foo.bind(null,"p1");
let baz=new bar("p2");
console.log(baz.val);
判断this
- 函数是否在new中调用,如果是this是新创建的对象
- 函数是否通过call.apply,或者bind调用,如果是this为指定的对象
- 函数是否在某个上下文对象中调用如果是,则this是上下文对象
- 如果都不是,则默认绑定
绑定例外
把null或undefined作为this绑定对象传入call,apply,bind,这些值会被忽略,实际应用默认绑定
function foo() {
console.log(this.a);
}
let a=2;
foo.call(null);
会污染全局作用域
function foo(a,b) {
console.log("a: "+a+",b: "+b);
}
foo.apply(null,[2,3]);
let bar=foo.bind(null,2);
bar(3);
更安全的this,将null换成空对象,Object.create(null)比{},少了prototype委托
function foo(a,b) {
console.log("a: "+a+",b: "+b);
}
let o=Object.create(null);
foo.apply(o,[2,3]);
let bar=foo.bind(0,2);
bar(3);
function foo(a,b) {
console.log(a,b);
}
foo(...[1,2]);
间接引用,函数的间接引用,在这种情况下会默认绑定((p.foo=o.foo)的返回值是foo)
function foo() {
console.log(this.a);
}
let a=2;
let o={
a:3,
foo:foo
};
let p={
a:4
};
o.foo();
(p.foo=o.foo)();
软绑定
if (!Function.prototype.softBind){
Function.prototype.softBind=function (obj) {
let fn=this;
let curried=[].slice.call(arguments,1);
let bound=function () {
return fn.apply(
(!this||this===(window||global))?obj:this,
curried.concat.apply(curried,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
function foo() {
console.log("name: "+this.name);
}
let obj={name:"obj"},
obj2={name:"obj2"},
obj3={name:"obj3"};
let fooOBJ=foo.softBind(obj);
fooOBJ();
obj2.foo=foo.softBind(obj);
obj2.foo();
fooOBJ.call(obj3);
setTimeout(obj2.foo,10);
this词法(=>胖箭头),根据外层作用域来决定this
function foo() {
return (a)=>{
console.log(this.a);
}
}
let obj1={
a:2
};
let obj2={
a:3
};
let bar=foo.call(obj1);
bar.call(obj2);
箭头函数的绑定无法被修改
function foo() {
setTimeout(()=>{
console.log(this.a);
},100);
}
let obj={
a:2
};
foo.call(obj);