this和对象原型第一二章

This与对象原型

第一章:this是什么?


-任何足够先进的技术都跟魔法没有区别。---ArthurC.Clarke

-JavaScript的this机制实际上没有那么先进

-this代词/关键词标识符

 

为什么要用this


-函数对多个环境对象进行复用,而不是针对每个对象定义函数的分离版本

-this机制提供了更优雅的方式来隐含地“传递”一个对象引用

-导致更加干净的API设计和更容易的复用

 

困惑


-它自己:this指向函数自己

       1.函数内部引用自己(递归、第一次被调用时会解除自己绑定的事件处理器)

       2.将函数作为一个对象,在方法调用之间储存状态(属性中的值)

       3.对this的含义和其工作方式上的理解(以上理解不对)

-它的作用域

       1.this不会以任何方式指向函数的词法作用域

       2.JavaScript作用域是引擎的内部实现

       3.不能使用this引用在词法作用域中查找东西

-this既不是函数自身的引用,也不是函数词法作用域的引用

 

什么是this?


-this不是编写时绑定而是运行时绑定,依赖于函数调用的上下文条件

-this绑定与函数声明位置无关,与函数被调用方式紧密相连

-一个函数被调用时,会建立一个称为执行环境的活动记录,这个记录包含函数是从何处(调用栈---call-stack)被调用的,函数是如何被调用的,被传递了什么参数等信息

-这个记录的属性之一,就是在函数执行期间将被使用的this引用

-寻找函数的调用点(call-site)来判定它的执行如何绑定this

 

第二章:this豁然开朗!

-this是一个完全根据调用点(函数是如何被调用的)而为每次函数调用建立的绑定

 

调用点(Call-Site)


-调用点:函数在代码中被调用的位置

       -某些特定的编码模式会使真正的调用点变得不那么明确

       -调用栈(call-stack):使我们到达当前执行位置而被调用的所有方法的堆栈

              -调用点就位于当前执行中的函数之前的调用

       -在分析代码来寻找(从调用栈中)真正的调用点要小心,因为它是影响this绑定的唯一因素

 

仅仅是规则


-调用点如何决定函数执行期间this指向哪里

-考察调用点并判定4中规则中哪一种适用

 
默认绑定(Default Binding)


-独立函数调用:没有其他规则适用时的默认规则

functionfoo() {

        console.log( this.a );

}

var a =2;

foo(); // 2

-foo()是被一个直白的毫无修饰的函数引用调用的,没有其他规则

-this默认绑定,指向了全局对象a

functionfoo() {
        "use strict";
        console.log( this.a );
}
var a =2;
foo(); // TypeError: `this` is `undefined`
functionfoo() {
        console.log( this.a );
}
var a =2;
(function(){
        "use strict";//与调用点的strict mode无关
        foo(); // 2
})();

-foo()内容在strict mode下执行,对于默认绑定全局对象不合法

 

隐含绑定(Implicit Binding)


-调用点是否有一个环境对象(contextobject),也称为拥有者(owning)或容器(containing)

functionfoo() {
        console.log( this.a );
}
var obj = {
        a:2,
        foo: foo
};
obj.foo(); // 2

-foo()被声明后作为引用属性添加到obj,并不被obj所真正“拥有”或“包含”

-调用点使用obj环境来引用函数,可以说在被调用时间点上拥有这个函数引用

-foo()在调用点指向obj对象引用(方法引用存在环境对象)

       -隐含绑定规则:这个函数调用的this绑定这个对象this.a—obj.a

-只有对象属性引用链最后一层影响调用点

functionfoo() {
        console.log( this.a );
}
var obj2 = {
        a:42,
        foo: foo
};
var obj1 = {
        a:2,
        obj2: obj2
};
obj1.obj2.foo(); // 42

-隐含丢失(ImplicitlyLost)

functionfoo() {
        console.log( this.a );
}
var obj = {
        a:2,
        foo: foo
};
var bar = obj.foo; // 函数引用!
var a ="oops, global"; // `a` 也是一个全局对象的属性
bar(); // "oops, global"

-bar似乎是obj.foo的引用,实际上只是另一个foo本身的引用,起作用调用点是bar(),适用默认绑定

functionfoo() {
        console.log( this.a );
}
 
functiondoFoo(fn) {
        // `fn` 只不过 `foo` 的另一个引用
        fn(); // <-- 调用点!
}
var obj = {
        a:2,
        foo: foo
};
var a ="oops, global"; // `a` 也是一个全局对象的属性
doFoo( obj.foo ); // "oops, global"

-回调函数也是如此,我们在传递一个函数,它是一个隐含的引用赋值

functionfoo() {
        console.log( this.a );
}
var obj = {
        a:2,
        foo: foo
};
var a ="oops, global"; // `a` 也是一个全局对象的属性
setTimeout( obj.foo, 100 ); // "oops, global"

-语言内建也是如此,假想为JavaScript环境内建的实现

functionsetTimeout(fn,delay) {
  // (通过某种方法)等待 `delay` 毫秒
        fn(); // <-- 调用点!
}

-回调函数丢掉this绑定十分常见(通过固定this解决问题)

 

明确绑定(Explicit Binding)


-强制一个函数调用使用某个特定对象作为this绑定

-函数工具call()、apply()通过他们的[Prototype]

       1.接收的第一个参数都是一个用于this的对象

       2.直接指明想让this是什么

functionfoo() {

        console.log( this.a );

}

var obj = {

        a:2

};

foo.call( obj ); // 2,强制函数的this指向obj

-如果是简单基本类型值作为this绑定,会包装在对应对象类型中(封箱boxing)

-从this绑定的角度讲call()和apply()完全一样

-硬绑定(Hard Binding):为所有传入的参数和传出的返回值创建一个通道

-硬绑定已作为ES5的内建工具提供:Function.prototype.bind

functionfoo(something) {

        console.log( this.a, something );

        returnthis.a + something;

}

var obj = {

        a:2

};

var bar = foo.bind( obj );

var b =bar( 3 ); // 2 3

console.log( b ); // 5

-bind()返回一个硬编码的新函数,使用你指定的this环境来调用原本的函数

-ES6中bind()生成的硬绑定函数有一个.name的属性,源自原始的目标函数,显示在调用栈轨迹的函数调用名称中

-API调用的“环境”

       -内建函数提供的可选参数也可以起到bind()的作用

       -内部也是通过call()或apply()

 

New绑定(new Binding)


-实际上JavaScript机制和new在JS中的用法暗示面向类的功能没有任何联系

-在JS中,构造器仅仅是一个函数,在被使用new来调用时改变了行为

-引用ES5.1的语言规范

15.7.2 Number 构造器

Number 作为 new 表达式的一部分被调用时,它是一个构造器:它初始化这个新创建的对象。

-实际上不存在“构造器函数”,只有函数的构造器调用(constructor call)

-函数前加new完成构造器调用时,会自动完成:

       1.一个全新的对象会凭空创建(被构建)

       2.这个新构建的对象会被接入原型链([[Prototype]]-linked)

       3.这个新构建的对象被设置为函数调用的this绑定

       4.除非函数返回一个它自己的其他对象,否则这个被new调用的函数将自动返回这个新构建的对象

 

一切皆有顺序


-new绑定>明确绑定>隐含绑定>默认绑定

-bind()的一个能力:任何在第一个this绑定参数之后被传入的参数。默认地作为当前函数的标准参数(技术上这称为”局部应用(partialapplication)”,是一种”柯里化(currying)”)

-判定this

       1.函数是通过new被调用的嘛

       2.函数是通过call或apply被调用或隐藏在bind硬绑定之中嘛

       3.函数是通过环境对象(拥有着或容器对象)被调用的嘛

       4.否则使用默认地this(strict mode为undefined否则是global对象)

 

绑定的特例


-被忽略的this

       1.硬绑定传递null或undefined作为绑定参数

       2.值会被忽略,适用默认绑定规则适用

-更安全的this

       1.为this传递一个特殊创建好的对象

       2.创建完全为空没有委托的对象

       3.建立一个”DMZ (非军事区)”对象:符号可以自己创建比如ø

functionfoo(a,b) {

        console.log( "a:"+ a +", b:"+ b );

}

// 我们的 DMZ 空对象

var ø =Object.create( null );

// 将数组散开作为参数

foo.apply( ø, [2, 3] ); // a:2, b:3

// `bind(..)` 进行 currying

var bar = foo.bind( ø, 2 );

bar( 3 ); // a:2, b:3

-间接

       -通过赋值产生的简介引用

functionfoo() {

        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

       -赋值表达式p.foo=o.foo结果值是一个刚好指向底层函数对象的引用,调用点为foo()

      

软化绑定(Softening Binding)

-硬绑定极大地降低了函数的灵活性,阻止我们手动使用隐含绑定或后续的明确绑定附带this

-构建一个所谓软绑定工具模拟我们期望的行为

if (!Function.prototype.softBind) {

        Function.prototype.softBind=function(obj) {

                 var fn =this,

                         curried = [].slice.call( arguments, 1 ),

                         bound=function bound() {

                                  return fn.apply(

                                          (!this||

                                                   (typeof window!=="undefined"&&

                                                           this===window) ||

                                                   (typeof global!=="undefined"&&

                                                           this===global)

                                          )? obj :this,

                                          curried.concat.apply( curried, arguments )

                                  );

                         };

                 bound.prototype=Object.create( fn.prototype );

                 return bound;

        };

}

-softBind()函数在调用时检查this,是global或undefined使用预先指定的默认值(obj),否则保持this不变

functionfoo() {

   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   <---- 退回到软绑定

 

词法this


-ES6引入了一种不适用这些规则特殊的函数:箭头函数(arrow function)

functionfoo() {

  // 返回一个箭头函数

        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!

-箭头函数词法绑定是不能被覆盖的,foo()被this绑定到obj1就不会被覆盖

-常用于回调,如事件处理器或计时器

functionfoo() {

        setTimeout(() => {

                 // 这里的 `this` 是词法上从 `foo()` 采用

                 console.log( this.a );

        },100);

}

var obj = {

        a:2

};

foo.call( obj ); // 2

 

选择


-仅使用词法作用域并忘掉虚伪的this风格代码

-完全接受this风格机制,包括在必要的时候使用bind(),并尝试避开self=this和箭头函数的“词法this”技巧

-词法和this不要混合使用

 

复习


为执行中的函数判定 this 绑定需要找到这个函数的直接调用点。找到之后,四种规则将会以这种优先顺序施用于调用点:

1.通过 new 调用?使用新构建的对象。

2.通过 call 或 apply(或 bind)调用?使用指定的对象。

3.通过持有调用的环境对象调用?使用那个环境对象。

4.默认:strict mode 下是 undefined,否则就是全局对象。

小心偶然或不经意的 默认绑定 规则调用。如果你想“安全”地忽略 this 绑定,一个像 ø = Object.create(null) 这样的“DMZ”对象是一个很好的占位值,以保护 global 对象不受意外的副作用影响。

与这四种绑定规则不同,ES6 的箭头方法使用词法作用域来决定 this 绑定,这意味着它们采用封闭他们的函数调用作为 this 绑定(无论它是什么)。它们实质上是 ES6 之前的 self = this 代码的语法替代品。






 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值