[JS] You Don't Know JavaScript [this & Object Proto

前言

在this Or That?中提到了许多对于this的误解,并且也对于这些误解做了一些解释,我们了解到this是对每个函式调用的绑定,是基于被调用的位置而不是宣告的位置。

Call-site

为了了解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()。
http://img2.58codes.com/2024/20124767MJrJJDrECS.png
而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();

Nothing But Rules

介绍完call-site与call-stack,接下来我们将重点移到call-site是如何确定函数执行期间this的指向,对于这个指向我们有4条规则,我们先一一介绍是哪些规则。

Default Binding

第一种规则是来自函数最常见的情况函数独立调用(换句话说就是只呼叫自己而没有在内部嵌入其他函数),若没有其他规则适用,可以将这个规则是为万用规则。

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 } ) ( ) ;

Implicit Binding

第二个规则是需要考虑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环境来Referencefoo(...),所以可以说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 Lost

当一个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 objectbar(); // "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 objectdoFoo( 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 objectsetTimeout( obj.foo, 100 ); // "oops, global"

可以将他看为

function setTimeout(var fn = obj.foo, delay){    //隐性function reference 赋值}

Explicit Binding

我们介绍了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

Hard Binding

虽然可以对单独的function进行显性绑定,但是依然无法解决上面提到的赋值导致绑定丢失的问题,但是可以有一种明确绑定的变种可以解决这个问题。

function foo() {console.log( this.a );}var obj = {a: 2};var a = 4; // declaration in globalvar bar = function() {foo.call( obj );};bar(); // 2setTimeout( bar, 100 ); // 2// `bar` hard binds `foo`'s `this` to `obj`// so that it cannot be overridenbar.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 3console.log( b ); // 5

API Call "Contexts

在许多现在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 (需要绑定的物件)*/

New Binding

在传统拥有class的语言中,constructor是一个特殊的method,当一个class被new实体化后这个constructor就会被调用以用来初始化这个class。

something = new MyClass(...);

虽然JavaScript中也有new但是他与其他语言的new是没有关係的,对于JavaScript来说constructor就只是个函数他们偶然的与new一起被调用,但他却不依附于也不会初始化一个class。

当一个函数前面加上new调用,也就是constructor调用时,会自动完成以下的事情:

凭空创造一个全新的物件。被创建的物件会接入原形鍊([[prototype]]-Link)。被调用funciton中的this被设定为指向新的物件。除非function return属于自身的物件,否则这个new调用的function会自动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


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章