- Oh no....我的This又丢失了???
- 为什么我用Class'实例化'出来的对象会相互影响???
这些问题都是因为JS的运行机制造成的。在JS中所有的一切都是对象,而this是对象的一个属性。在对象被调用时,this动态的根据上下文环境进行绑定,因此this和词法作用域没有关系。
一、何谓this
- this是函数执行上下文对象的一个属性,存在于运行时,而不是定义时,所以它和词法作用域没有必然的关系。
- 要使用this,首先需要考虑的是函数的调用环境而不是编写定义环境。它不指向函数本身,也不指向词法作用域。它由函数调用时发生在上下文环境的绑定而决定。
- 使用this要找函数的调用位置,而不是函数的声明位置。调用栈记录了函数的调用位置和顺序。通过调试工具可以查看调用栈,找出函数的调用位置。调用时使用了谁的上下文,this就指向谁。特别要注意的是回调函数传递的是引用,所以会丢失this。真正执行回调函数的位置决定了this的指向。
- bind把用来硬绑定函数执行的上下文,用于硬绑定this。(在React组件构造函数中显示的绑定This,防止丢失上下文环境)。除此之外,赋值表达式会传递函数的引用,它的调用位置会变成函数声明的位置。
- JS中Class对象的构造函数不是其它面向对象语言的构造函数,它是对已有函数的构造调用,本质上也是This的绑定。new关键字看上去像实例化对象,其实只是根据已有函数生成新的函数对象,并通过new关键字将this绑定到新创建的对象的调用上下文环境中。比如,var test =new foo(),用 new 来 调用 foo(..) 时,我们会构造一个新对象并把它绑定到foo(..) 调用中的 this 上。
- 箭头函数本身没有this,它在执行时会捕获调用所在词法作用域父函数的this。捕获绑定之后无法再次被修改。()=>的this是和词法作用域有关系的。而function定义的函数是和词法作用域没有关系的,只和调用环境有关系。http://stackoverflow.com/questions/35813344/do-es6-arrow-functions-still-close-over-this-even-if-they-dont-use-it
- this和JS中的Class可以关联使用,通过this的重新绑定可以实现混入 '父类'属相和方法的功能,也就实现了隐士的类似Class的伪多态。
根据下面这四条规则来判断this的绑定对象。
- 由 new 调用? 绑 定 到 新 创建 的 对象。
- 由 call 或者 apply( 或者 bind) 调用? 绑 定 到 指定 的 对象。
- 由上下文对象调用? 绑定到那个下文对象。
- 默认: 在严格模式下绑定到undefined,否则 绑 定 到 全局 对象。
二、何谓对象
- 从数据结构的角度来看,对象就是键/ 值对的集合。不论是String、Number、Function还是Array、JSON,它们构成对象后都是键/值对的集合。
- 函数就是对象的 一个 子 类型( 从 技术 角度 来说 就是“ 可调 用的 对象”)。
- 对象和变量一样作为二进制编码存储。
- 对象的内容是由 一些 存储 在特定命名位置 的( 任意 类型 的) 值 组成 的, 我们 称之为属性。对象存储在内存中,组成它的属性存储在其它位置。
- 直接声明的字符串类型是字面量类型。只是引擎会自动将字面量转化为 String对象。
- 对象的每个属性都有属性描述符“writable( 可 写)、 enumerable( 可 枚举) 和 configurable( 可 配置)”等。实际上'.属性名'这样的操作是触发了Get操作。
getter/setter:用来自定义对象属性的取值和赋值操作,会覆盖掉对象默认的[[Get]]和[[PUT]]算法。
- 不论直接访问函数,还是通过属性访问函数,都是访问的函数引用。
- 遍历对象属性的每个迭代器都可以接收一个回调函数,这个回调函数会应用到对象的每个属性上。forEach会遍历数组中的每一个元素。every会一直遍历直到回调函数返回false,some会一直遍历直到回调函数返回true。for...of...,直接遍历对象的属性或者数组的值,而不是遍历数组下标。
三、Class的本质
- 首先特别注意,JS没有类(对象的抽象模式/蓝图),只有对象,因此继承只能通过原型链来实现。JS中只有对象,可以粗俗的理解为所有的一切都是对象,它是真正面向对象。new关键词的作用就是将新产生的对象的原型链指向定义的函数对象本身。继承的含义是其它语言的复制机制,而在JS中不存在真正的继承机制,而是关联机制。在JS中是创建两个对象的关联。constructor并不是构造,只是指向了一个函数,把自己要做的事情委托给了自己原型链上关联的对象的方法。
- 类/ 继承描述 了一种代码的组织结构形式—— 一种 在 软件 中 对 真实 世界 中 问题 领域 的 建模 方法。面向 对象 编程 强调 的 是 数据 和 操作 数据 的 行为 本质上 是 互相 关联 的( 当然, 不同 的 数据 有 不同 的 行为), 因此 好的 设计 就是 把 数据 以及 和 它 相关 的 行为 打包( 或者说 封装) 起来。“类"也是一种设计模式,设计思想。面向 对象 设计 模式, 比如 迭代 器 模式、 观察者 模式、 工厂 模式、 单例模式。建筑师提前设计好房屋,但是不关心建在哪里和建造多少个。建筑 和 蓝图 之间 的 关系 是 间接 的。 你 可以 通过 蓝图 了解 建筑 的 结构, 只 观察 建筑 本身 是 无法 获得 这些 信息 的。 但是 如果 你想 打开 一 扇 门, 那就 必须 接触 真实 的 建筑 才行—— 蓝图 只能 表示 门 应该 在哪, 但 并不是 真正 的 门。一个 类 就是 一张 蓝图。 为了 获得 真正 可以 交互 的 对象, 我们 必须 按照 类 来 建造( 也 可以说 实例 化) 一个 东西,这个东西就是对象,所以对象就是 类 中 描述 的 所有 特性 的 一份 副本。JS中的类的继承和实例化并不会发生复制行为,所以它不是真正的类。它不会创建副本,而只是对象之间被关联起来了。
- 标准面向对象语言中构造函数是属于类的,而在JS中类是属于构造函数的。JS中父类和子类的关系只存在于构造函数对应的.prototype中,所以构造函数只是一个委托关系函数,不能生成对象的副本。继承:实质上复制父类的副本,不会改变父类的方法,但在JS中不是这样的。所以说JS中是伪多态,js本身不提供多重继承机制。JS中的类继承是通过mixin或者prototype来实现的。
- 每一个JS对象都有一个内置的Prototype属性,它保存了对其它对象的引用。所有普通的原型链的尽头都是Object.prototype,它有很多通用的功能。对函数原型链的修改有时候会产生隐形属性屏蔽,并没有修改到原型链上。ES6的Class是显示伪多态的语法糖,是通过原型链来实现的,原型链委托的一种语法糖。
- 因为JS的原型链机制,所以JS可以使用两种设计思想,面向对象和委托关联(不同于.Net中的委托,不要误解)。
- 面向对象:是通过new来调用对象函数,并把新生成的对象的行为封装在new 调用对象中。对象关联(委托):根据父对象创建子对象对齐的关联,然后逐步调用父对象的中的方法。
- 这是两种设计思想。Class只是把动态语法降低难度写上去像静态语法,虽然书写难度降低了,但是对理解底部真是的引擎运行机制形成了误导。委托调用的设计思想比面向对象的设计思想更加接近JS的真实运行机制。