what is this
When a function is called, a execution context . This execution context will contain information such as where the function is called (execution stack), how the function is called, and the parameters passed in. this is an attribute of the execution context , which will be used during the execution of the function.
Call location
Before understanding this
, we must first understand the call location: the call location is the location (not the declared location) of the
The call location we care about is in the call before of the currently executing function.
function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar的调用位置
}
function bar() {
// 当前调用栈是:baz --> bar
// 因此,当前调用位置在baz中
console.log( "bar" );
foo(); // <-- foo的调用位置
}
function foo() {
// 当前调用栈是:baz --> bar --> foo
// 因此,当前调用位置在bar中
console.log( "foo" );
}
baz(); // <-- baz的调用位置
Binding rules
this
are a total of 5 binding rules for 060883c1228f1d:
- Default binding
- Implicit binding
- Show binding
new
binding- Arrow function binding
1. Default binding
independent function call : This rule can be regarded as the default rule when other rules cannot be applied.
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
strict mode (strict mode) , global objects cannot be used for default binding, this will be bound to undefined
.
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: Cannot read property 'a' of undefined
2. Implicit binding
When the function has a reference context object time, implicit binding rules will function in this
bound to the context object.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
Only the upper or last layer in the object attribute reference chain plays a role in the calling position.
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1={
a:2,
obj2:obj2
}
obj1.obj2.foo() // 42
Implicitly missing
Is implicit binding lose binding object function under certain circumstances, it will apply default binding , the this
bound to the global object or undefined
on (depending on whether strict mode).
// 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
// 因此此时的bar()是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global"
A more subtle, common and unexpected situation occurs when a callback function is passed in:
function foo() {
console.log( this.a );
}
function doFoo(fn){
//fn其实引用的是foo
fn();// <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a是全局对象的属性
doFoo(obj.foo); // "oops, global"
Parameter passing is actually a kind of implicit assignment , we will also be implicitly assigned when we pass in the function.
If you pass the function to the built-in function of the language instead of the function declared by yourself, the result is the same.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a是全局对象的属性
setTimeout(obj.foo,100); // "oops, global"
// JS环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn, delay) {
// 等待delay毫秒
fn(); // <-- 调用位置!
}
3. Explicit binding
You can use the call(...)
and apply(...)
methods of the function. Their first parameter is an object, which is this
, and then bind it to this
when calling the function. Because you can directly specify the binding object of this
explicit binding .
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
Through foo.call(...)
we can force this
bound to obj
foo
.
Unfortunately, explicitly bound still does not solve the problem of the loss of binding before we put forward.
hard binding
But a variant of explicit binding can solve this problem.
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的bar不可能再修改它的this
bar.call( window ); // 2
We created the function bar()
, and manually called foo.call(obj)
inside it, so it is mandatory to bind foo
of this
obj
.
No matter how after calling the function bar
, it will always manually obj
call on foo
. We call it hard binding .
A typical application scenario is to create a wrapper function, which is responsible for receiving parameters and returning values:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
Another way to use it is to create a helper function that can be reused:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
}
}
var obj = {
a: 2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
ES5 provides a built-in method Function.prototype.bind
:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
bind(...)
will return a hard-coded new function, which will set the parameters you specify to this
and call the original function.
API call "context"
Third-party libraries, as well as many new built-in functions in the JavaScript language and host environment, provide an optional parameter, usually called "context" (context), its role is the bind(...)
, make sure your callback function uses the specified this
.
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
}
let myArr = [1,2,3]
// 调用foo(..)时把this绑定到obj
myArr.forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome
The function is actually through call(...)
or apply(...)
achieved explicit binding .
4.new binding
In Javascript, the constructor is just some functions that are called when the new
They do not belong to a certain class, nor will they instantiate a class.
All functions including built-in object functions (such as Number(...)
new
. This kind of function call is called a constructor call.
In fact, there is no so-called "constructor", only the "construction call" to the function.
Use new
to call a function, or when a constructor call occurs, the following operations will be performed automatically.
- Create (or construct) a brand new object.
- This new object will be executed [[Prototype]] connection.
- This new object will be bound to the function call
this
. - If the function does not return other objects,
new
expression will automatically return this new object.
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
When using new
to call foo(...)
, we will construct a new object and bind it to foo(...)
in the this
call.
new
is another method that can affect the this
when a function is called. We call it new
binding.
priority
function foo1() {
console.log(this.a)
}
function foo2(something) {
this.a = something
}
var obj1 = {
a: 2,
foo: foo1
}
var obj2 = {
a: 3,
foo: foo1
}
var obj3 = {
foo: foo2
}
var obj4 = {}
obj1.foo(); //2
obj2.foo(); //3
obj1.foo.call(obj2); //3
obj2.foo.call(obj1); //2
//可见,显式绑定比隐式绑定优先级高
obj3.foo(4);
console.log(obj3.a); //4
obj3.foo.call(obj4, 5);
console.log(obj4.a); //5
var bar = new obj3.foo(6);
console.log(obj3.a); //4
console.log(bar.a); //6
//可见,new绑定比隐式绑定优先级高
var qux = foo2.bind(obj4);
qux(7);
console.log(obj4.a); //7
var quux = new qux(8);
console.log(obj4.a); //7
console.log(quux.a); //8
//new绑定修改了硬绑定(到obj4的)调用qux(...)中的this。
Now, we can determine which rule is applied to a function at a call location based on the priority.
- Whether the function is called
new
new
binding), if so,this
bound to the newly created object. - Whether the function is
call
by 060883c12294ab,apply
(explicit binding) or hard binding, if so,this
bound to the specified object. - Whether the function is called in a certain context object (implicit binding), if so,
this
bound to that context object. If neither is true, use the default binding.
- If it is in strict mode, it is bound to
undefined
. - Otherwise, it is bound to the global object.
- If it is in strict mode, it is bound to
Binding exception
ignored this
If you null
or undefined
as this
bound objects passed call
, apply
or bind
, these values are ignored when you call, the practical application of the default rule.
function foo(){
console.log(this.a);
}
var a = 2;
foo.call(null); //2
In two cases, null
- Use
apply(...)
to "expand" an array and pass it as a parameter to a function. bind(...)
can curry the parameters (set some parameters in advance).
function foo(a, b) {
console.log( "a:" + a + ",b:" + b );
}
// 把数组”展开“成参数
foo.apply( null, [2, 3] ); // a:2,b:3
// 使用bind(..)进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2,b:3
Always use null
to ignore the this
binding may have some side effects.
If a function does use this
(for example, a function in a third-party library), the default binding rule will this
to the global object, which will lead to unpredictable consequences (such as modifying the global object).
safer this
A "safer" approach is to pass in a special object, and this
to this object will not have any side effects on your program.
The easiest way to create an empty object in Javascript is Object.create(null) . It is {}
much like 060883c1229674, but it will not create the Object.prototype
commission.
function foo(a, b) {
console.log( "a:" + a + ",b:" + b );
}
// 我们的空对象
var ø = Object.create( null );
// 把数组”展开“成参数
foo.apply( ø, [2, 3] ); // a:2,b:3
// 使用bind(..)进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2,b:3
Indirect reference
Another thing to note is that you may create an "indirect reference" to a function. Calling this function will apply the default binding rule.
indirect references to most likely to occur during assignment:
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4};
o.foo(); // 3
(p.foo = o.foo)(); // 2
The return value of the assignment expression p.foo = o.foo
is a reference to the target function, so the calling location is foo()
instead of p.foo()
or o.foo()
.
Soft binding
This way of hard binding can this
to be bound to the specified object (except when new
is used) to prevent function calls from applying the default binding rules.
The disadvantage is that hard binding will greatly reduce the flexibility of the function. After using hard binding , you cannot use implicit binding or explicit binding to modify this
.
If you can default binding and a value other than undefined , then you can achieve the same effect as hard binding, while retaining implicit binding or explicit binding to modify this
.
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有curried参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
In addition to soft binding, softBind(...)
of other principles and ES5 built bind(...)
similar.
It will encapsulate the specified function, first check this
when calling, if this
bound to a global object or undefined
, then bind the specified default object obj
this
, otherwise it will not modify this
.
function foo() {
console.log("name:" + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind( obj );
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!!!
setTimeout( obj2.foo, 10 ); // name: obj
As you can see, the soft binding version of foo()
can manually bind this
obj2
or obj3
, but if the default binding is applied, this
will be bound to obj
.
Arrow function
The four rules we introduced earlier can include all normal functions. But ES6
introduced a special function type that cannot use these rules: arrow functions.
The arrow function does not use this
this
according to the outer (function or global) scope.
function foo() {
//返回一个箭头函数
return (a) => {
//this继承自foo()
console.log(this.a);
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar =foo.call(obj1);
bar.call(obj2); //2,不是3!
The arrow function created inside foo()
foo()
of this
when it is called. Because foo()
of this
bound to obj1
, bar
(reference arrow function) of this
also bound to obj1
, binding function of the arrow that can not be revised. ( new
also does not work!)
Arrow functions are often used in callback functions, such as event handlers or timers:
function foo(){
setTimeout(()=>{
//这里的this在词法上继承自foo()
console.log(this.a);
},100);
}
var obj = {
a:2
};
foo.call(obj) //2
This in arrow function
1. The arrow function does not have prototype
(prototype), so the arrow function itself does not have this
.
let a = () => {}
console.log(a.prototype) //undefined
this
in arrow functions is inherited from the context in which they are defined (Javascript Authoritative Guide 7th Edition P206), and inherited from this
first ordinary function in the outer layer.
let foo
let barObj = {
msg: 'bar的this指向'
}
let bazObj = {
msg: 'baz的this指向'
}
bar.call(barObj) //bar的this指向barObj
baz.call(bazObj) //baz的this指向bazObj
function bar() {
foo = () => {
console.log(this, 'this指向定义它们的上下文,外层的第一个普通函数')
}
}
function baz() {
foo()
}
//msg: "bar的this指向" "this指向定义它们的上下文,外层的第一个普通函数"
3. arrow function this
not pass bind
, call
, apply
to direct modified.
let quxObj = {
msg: '尝试直接修改箭头函数的this指向'
}
function baz() {
foo.call(quxObj)
}
//{msg: "bar的this指向"} "this指向定义它们的上下文,外层的第一个普通函数"
Indirectly modify the direction of the arrow function:
bar.call(bazObj) //普通函数bar的this指向bazObj,内部的箭头函数也会指向bazObj
It inherited an ordinary function of this
point to change the function of the arrow this
point will also change.
4. If the arrow function has no outer function, this
points to window
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b()//undefined, window
obj.c()//10, {i: 10, b: ƒ, c: ƒ}
Exercise
/**
* Question 1
* 非严格模式下
*/
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1()
person1.show1.call(person2)
person1.show2()
person1.show2.call(person2)
person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()
person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()
The correct answer is as follows:
person1.show1() // person1,隐式绑定
person1.show1.call(person2) // person2,显式绑定
person1.show2() // window,箭头函数绑定,没有外层函数,指向window
person1.show2.call(person2) // window,箭头函数绑定,不能直接修改,还是指向window
person1.show3()() // window,高阶函数,person1.show3()返回一个函数ƒ(){ console.log(this.name)}到全局
//从而导致最终函数执行环境是window,所以此时this 指向 var name = 'window'
person1.show3().call(person2) // person2 ,返回函数以后,显式绑定person2,this指向person2对象
person1.show3.call(person2)() // window,高阶函数ƒ(){return function(){console.log(this.name)}} 显式绑定person2,
//也就是高阶函数this指向person2,它的返回值ƒ(){console.log(this.name)}执行环境是window,同上
person1.show4()() // person1,箭头函数绑定,this是从定义函数的上下文继承的,也就是外层函数所在的上下文,外层函数的this指向person1
person1.show4().call(person2) // person1,无法通过call直接修改箭头函数绑定
person1.show4.call(person2)() // person2,高阶函数,外层函数this显式绑定person2,修改箭头函数的外层函数this指向,可以改变箭头函数this指向
/**
* Question 2
*/
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()
personA.show1.call(personB)
personA.show2()
personA.show2.call(personB)
personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()
personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()
The correct answer is as follows:
personA.show1() // personA,new绑定以后,构造函数Person中的this绑定到personA,Person传入的参数personA,所以结果是personA
personA.show1.call(personB) // personB,new绑定以后,构造函数Person中的this绑定到personA,personA.show1就是ƒ(){console.log(this.name)}
// 再显式绑定personB,personA.show1的this指向personB实例对象,所以结果是personB
personA.show2() // personA,new绑定以后,构造函数Person中的this绑定到personA,personA.show2就是()=>console.log(this.name)
//然后箭头函数绑定,调用箭头函数,this指向外层函数的this.name,也就是personA
personA.show2.call(personB) // personA,new绑定以后,构造函数Person中的this绑定到personA,personA.show2就是()=>console.log(this.name)
//箭头函数不能直接修改,所以还是personA
personA.show3()() // window,new绑定以后,构造函数Person中的this绑定到personA,personA.show3()返回一个函数ƒ(){console.log(this.name)}到全局
// 执行环境是window,所以执行以后的结果是var name = 'window',也就是window
personA.show3().call(personB) // personB,new绑定以后,构造函数Person中的this绑定到personA,
// personA.show3()返回一个函数ƒ(){console.log(this.name)}到全局,
// 再显式绑定personB,所以最终结果是personB
personA.show3.call(personB)() // window,new绑定以后,构造函数Person中的this绑定到personA,
//高阶函数ƒ(){return function(){console.log(this.name)}}显式绑定personB,
//返回一个函数ƒ(){console.log(this.name)}到全局
//执行环境是window,所以执行以后的结果是var name = 'window',也就是window
personA.show4()() // personA,new绑定以后,构造函数Person中的this绑定到personA,
// 高阶函数ƒ(){return ()=>console.log(this.name)}执行后返回箭头函数()=>console.log(this.name),执行箭头函数
// 箭头函数绑定,继承外层普通函数this,所以结果是personA
personA.show4().call(personB) // personA,new绑定以后,构造函数Person中的this绑定到personA,
// 高阶函数ƒ(){return ()=>console.log(this.name)}执行后返回箭头函数()=>console.log(this.name),
// 箭头函数不能直接修改,所以结果还是personA
personA.show4.call(personB)() // personB,new绑定以后,构造函数Person中的this绑定到personA,
// 显式绑定 外层函数 ,所以箭头函数也被修改为 personB
reference
[The JavaScript You Don’t Know About Volume]
[Javascript Definitive Guide Seventh Edition]
From these two sets of questions, re-understand the this, scope, closure, and object of
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。