在this Or That?中提到了许多对於this
的误解,并且也对於这些误解做了一些解释,我们了解到this
是对每个函式调用的绑定,是基於被调用的位置而不是宣告的位置。
为了了解this
,我们必须要先了解一个重要的观念call-site
,它代表着函式在程序中被调用的位置
,通常要找到call-site是要定位从何处调用此函式
,但通常这个行为并不是这麽容易,因为某些模式下会掩盖掉真正的call-site,这个状态下我们需要考虑的是call-stack
,call-stack代表着呼叫function的堆叠,比如说
function c(){
console.log('c');
}
function b(){
c();
console.log('b');
}
function a(){
b();
console.log('a');
}
a();
上面的程序中呼叫a(...),而a(...)中又呼叫b(...),最後b(...)中又呼叫c(...),而call-stack -> a() -> b() -> c()。
而call-site来说,他是在他call-stack父层中呼叫自己的位置
,看其来很绕舌不过我们一样拿上面的程序码来做举例,对於c(...)
而言,他的call-site
就是在call-stack父层(b(...))
所呼叫的位置,以此类推。
function c(){
console.log('c');
}
function b(){
c(); // function c(...) => call-site
console.log('b');
}
function a(){
b(); // function b(...) => call-site
console.log('a');
}
a();
介绍完call-site与call-stack,接下来我们将重点移到call-site是如何确定函数执行期间this
的指向,对於这个指向我们有4条规则
,我们先一一介绍是哪些规则。
第一种规则是来自函数最常见的情况函数独立调用(换句话说就是只呼叫自己而没有在内部嵌入其他函数)
,若没有其他规则适用,可以将这个规则是为万用规则。
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
以上面的程序中,default binding
代表着this
是绑定全域物件
,由於我们的var a = 2
是宣告在全域,所以这里的this才会指向到全域的a,我们要如何知道default binding规则适用於这个例子?
我们通过call-site来观察foo(...)是在哪里被调用的,在我们的程序中foo(...)是一个直白且无修饰的
函数呼叫,意味着他没有在内部嵌入其他函数,所以default binding规则在这里适用。
但是default binding对於使用严格模式来说就不适用
function foo ( ) {
"use strict" ;
console . log ( this . a ) ;
}
var a = 2 ;
foo ( ) ; // TypeError: `this` is `undefined`
但是有一个特别的点,对於严格模式来说只要foo(...)的作用域内
不是严格模式,那麽this
一样也可以绑定到全域物件。
function foo ( ) {
console . log ( this . a ) ;
}
var a = 2 ;
( function ( ) {
"use strict" ;
foo ( ) ; // 2
} ) ( ) ;
第二个规则是需要考虑call-site是否有一个环境物件(context object)。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
我们宣告了一个function foo(...)之後将他加入到obj物件中成为他的property,无论foo()是否一开始就在obj
上被宣告或是後来才加入到obj
中(上面的例子),这个函数
都不被obj所真正的拥有或包含
,但是由於对於call-site来说obj环境来Reference
foo(...),所以可以说obj在函数被调用的时间点
拥有或包含这个funciton reference
。
当一个context object中有一个function reference则implicit binding规则会将这个fucntion中的this绑定这个object,所以以上面的例子来说foo(...)中的this所指向的就是obj
。
对於嵌套的物件来说,只有最後一层/最上层
物件才会对call-site起作用
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo // call-site
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
当一个implicitly bound的函数丢失了绑定,则会退回default binding
,至於指向的是全域物件还是undefined则取决於是否使用严格模式。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // loses that binding!
var a = "oops, global"; // `a` is property on global object
bar(); // "oops, global"
虽然bar
似乎是obj.foo的reference,但是实际上他只是对foo(...)本体的另一个reference,换句话说虽然bar与obj.foo都是对foo(...)本体的reference,但是实际上是两个不一样的地方
,而且对於call-site而言呼叫bar(...)是一个直白且无修饰的
函数呼叫,所以他适用於default binding
。
还有一个更加微妙更常见更出乎意料的方式,当我们传递一个callback function时
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// `fn` is just another reference to `foo`
fn(); // <-- call-site!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"
对於参数的传递来说他是一个隐性赋值
,而且如果要传递的参数是函数的话则是一个隐性的reference赋值
,所以结果会与上一个程序码相同。
function doFoo(var fn = obj.foo){
// 隐性function reference 赋值代表fn与obj.foo的reference是不同的。
}
对於传递callback function作为参数会丢失binding这件事,除了自己定义的function之外对於原生的funciton也是一样的情况。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"
可以将他看为
function setTimeout(var fn = obj.foo, delay){
//隐性function reference 赋值
}
我们介绍了Implicit Binding如果需要间接地将函数中的this绑定到这个物件上,会需要对这个物件做一些改变(将function reference引入到物件属性中),但是有没有方法是可以不更改物件的型态却又可以使function的this绑定着这个物件的呢?
我们可以使用JS所提供function的prototype(後面会介绍)call(...)
或apply(...)
method,他们的第一个参数都是一个物件,他代表着我这个fucntion的this所指向的目标,因为明确的指出this要指向什麽所以我们称这种方式为Explicit Binding。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var a = 5; // declaration in global
foo.call( obj ); // 2
通过foo.call(...)的方式将this明确的指向obj,注意的是如果对於call(...)或apply(...)的第一个参数传递的不是一个物件(string,boolean,number...)那麽传递的这个参数的类性会被包装在物件(new String(...), new Boolean(...), new Number(...))这种行为称为boxing
。
虽然可以对单独的function进行显性绑定,但是依然无法解决上面提到的赋值导致绑定丢失的问题,但是可以有一种明确绑定的变种可以解决这个问题。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var a = 4; // declaration in global
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2
我们在bar内部强制绑定了foo(...)的this指向obj,所以无论之後怎麽调用bar(...)在他的内部都会自动的强制绑定obj,这种行为我们称为hard binding
。
对於hard binding来说,ES5中提供了funciton.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
在许多现在JS的内建函数中都有提供一个可选的参数通常称为context,这种设计可以让你直接填入你需要绑定的object而不必一定要使用bind(...)
。
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
arr.forEach(function callback(currentValue[, index[, array]]) {
//your iterator
}[, thisArg]);
/*
callback : 把 Array 中的每一个元素作为参数,带进本 callback function中
currentValue : 当前被处理的Array元素
index(可选):当前被处理的Array元素的index
array(可选):forEach()本身的Array -> arr
thisArg(context)(可选):callback function的this (需要绑定的物件)
*/
在传统拥有class的语言中,constructor是一个特殊的method,当一个class被new
实体化後这个constructor就会被调用以用来初始化这个class。
something = new MyClass(...);
虽然JavaScript中也有new
但是他与其他语言的new是没有关系的,对於JavaScript来说constructor就只是个函数
他们偶然的与new一起被调用,但他却不依附於也不会初始化一个class。
当一个函数前面加上new调用,也就是constructor调用时,会自动完成以下的事情:
自动return
这个新创建的物件。function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
使用new来调用foo(...)等於我们建立了一个新的物件并将function中的this指向这个新创出来的物件,这种绑定新建出物件的方法称为new binding。
参考文献:You Don't Know JavaScript
串链的表示法 基本介绍 1.矩阵表示法: 若G(V,E)是含n个顶点的图,表示图G的矩阵为mat[n...
本节将进行继续完成虚拟订单的功能 首先每组订单需要一组订单编号,为了方便,笔者这边采UUID的方式,...
今天先来点轻松的,先来看看各种 sendMessage 的功能,最後来转换使用者发送的 Markd...
终於到了最後一天了,不知道把这三十天看完的人有多少呢?希望看到最後一天的人,有感受到我对於这系列文章...
如果有错误,欢迎留言指教~ Q_Q 没写完啦 子元件通常会接收父元件的 state 或 event...