You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
红宝书面向对象这一章节看了不下两三遍去理解书中提到不同的继承方式和思考为何代码需要这样编写,这篇文章也算是第六章的阅读笔记。回忆了一下自己写过的代码非常非常少用面向对象方式去写。JavaScript 是类面向对象的语言,理解原型继承等等非常有必要,而 OOP 优点在于封装、代码重用。如果JS代码仅有100行左右的规模,多个纯 function 调用可行,可读性也许会比 OOP 更高,但如果代码量达到上千行,密密麻麻的都使用大量 function 调用的话那么将会极其难以维护。将松散的JS代码进行整合,便于后期的维护并让代码适应更多的业务逻辑。
构造函数
单独使用构造函数的问题
在构造函数中定义函数与声明函数在逻辑上是等价的,像下面这个例子,person 函数实例都会包含一个不同的 function 实例,这样导致每个实例不同的作用域链和标识符解析。且实例化两次函数没太大必要。单独使用构造函数情况十分少,相同的方法应该放在原型中。
functionPerson(name,age,job){this.name=name;this.age=age;this.job=job;this.sayName=function(){alert(this.name)}//this.sayName = new Function("alert(this.name)");}
functionPerson(){}Person.prototype.name='Kevin';varperson=newPerson();person.name='Daisy';console.log(person.name)// Daisydeleteperson.name;console.log(person.name)// Kevin
ES6 Class
第一次接触 Class 语法的时候,就被这个基于原型继承的语法糖便利之处给吸引到。我非常推荐用 Class 来封装函数但前提是对原本继承方法有基本的了解。这里不对 Class 基本使用写太多,可在这里阅读ECMAScript 6 入门 Class篇。只写写需要注意的点。首先是静态方法,在 Class 中如果在函数前加 static 那么表示该方法不会被实例继承但可被子类继承调用,而是直接通过类来调用。静态方法包含的 this 指向类而不是实例。关于 Class 中的 prototype 属性和 proto 属性,建议阅读链接中文章。
super 作为函数调用时代表父类的构造函数。子类 constructor 构造函数必须执行一次 super 函数。值得注意的是,super 虽然代表了父类 Point 的构造函数,但返回的是子类 ColorPoint 的实例,即 super 内部的 this 指的是 ColorPoint,因此 super 在这里相当于 Point.prototype.constructor.call(this)。super 作为函数时只能用在子类的构造函数之中调用。
super 作为对象时,在普通方法中指向父类的原型对象,既可直接调用父类方法例如 super.toString() 相当于 Point.prototype.toString(),由于 Super 指向父类原型所以在父类构造函数中定义的方法属性无法调用。如果在 static 静态方法中 super 作为对象调用则指向父类而不是父类原型。
The text was updated successfully, but these errors were encountered:
红宝书面向对象这一章节看了不下两三遍去理解书中提到不同的继承方式和思考为何代码需要这样编写,这篇文章也算是第六章的阅读笔记。回忆了一下自己写过的代码非常非常少用面向对象方式去写。JavaScript 是类面向对象的语言,理解原型继承等等非常有必要,而 OOP 优点在于封装、代码重用。如果JS代码仅有100行左右的规模,多个纯 function 调用可行,可读性也许会比 OOP 更高,但如果代码量达到上千行,密密麻麻的都使用大量 function 调用的话那么将会极其难以维护。将松散的JS代码进行整合,便于后期的维护并让代码适应更多的业务逻辑。
构造函数
单独使用构造函数的问题
在构造函数中定义函数与声明函数在逻辑上是等价的,像下面这个例子,person 函数实例都会包含一个不同的 function 实例,这样导致每个实例不同的作用域链和标识符解析。且实例化两次函数没太大必要。单独使用构造函数情况十分少,相同的方法应该放在原型中。
动态原型模式
动态原型是最优化的方式,将相同方法属性定义放在原型中,并用判断动态添加原型。
继承
ES5 继承共分六种继承方式
原型和实例的关系
第一种方式是使用 instanceof 操作符:
第二种方式是使用isPrototypeOf()方法:
原型链继承
下面的 Demo 代码,Subfn 继承 Superfn,原型链等于 Superfn 的实例,Subfn 原型 constructor 指针指向了另一个对象 Superfn 的原型。Superfn 原型指向 Superfn 构造函数。所以 Subfn 继承了 Superfn 原型与构造函数的方法与值。
原型链存在的问题,一是例如当有引用类型值 colors 数组存在于被继承函数 Superfn 中,那么继承函数 Subfn 的每一个实例都共用 colors 。因为继承后 Subfn 的 colors 存在于 Subfn 原型中。二是不能向被继承函数 Superfn 传参数。
借用构造函数
实际上是 Subfn 利用 call 方法(或apply)在 Subfn 实例的环境下调用 Superfn 函数,就会在实例环境上执行 Superfn() 所有定义值对象或方法的初始化代码。这样 Subfn 每个实例就会具有自己的引用类型值副本。像上面这个例子,其中一个实例向 colors 中 push 一个值,另一个实例不会受到影响。
组合继承
构造函数 + 原型链的组合继承方式,是为解决各自本身的问题而组合使用,如下:
借用构造函数
原型链
原型式继承
原型式继承是借助原型可以基于已有的对象创建新对象,没有必要创建继承函数的情况。只想让一个对象与另一个对象保持类似。
一个 Demo 如下,基础信息一致的对象 info,需要在 info 基础上添加不同的变量。
输出 example1 与 example2,发现原型中存在 info 的属性,并且包含各自的属性值。本质上是通过将 info 信息赋给了一个新的原型,所以如果基础对象info中存在引用类型值的话,多个 example 对象是会被共享引用类型值的。
寄生式继承
寄生式是原型式的一种扩展,创建一个用于封装继承的构造函数,在函数在内部以所需的方式增强对象。
例如原型式的这个例子,一次次的为实例创建新属性 name 步骤重复,如需要有更多操作就会显得代码臃肿,所以用一个函数 createInfo 将这些操作包装起来。
主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
寄生组合式继承
还记得组合继承吗?通过借用构造函数与原型链继承实现 superfn 实例属性与原型属性的继承。
虽然说组合继承是最常用的继承方式,但这种继承方式会存在着一个问题是两次调用了被继承 superfn 函数,例子代码已经清晰的注释调用位置与顺序,下面详细说明。
所以需要寄生组合式继承来解决这个函数被多次调用的问题。以下利用寄生组合式继承修改后完整的代码。
首先
supfn.prototype = new superfn()
原型链继承语句替换成了 inheritPrototype 方法。原来的代码是将 superfn 的实例属性与原型属性赋给 supfn 的原型,这个方法所要做是只将 superfn 原型属性赋给 supfn 的原型,而不是一同将实例属性也赋给了 supfn 的原型,这样就解决组合继承多次调用 superfn 的问题。再来看 inheritPrototype 这个函数,其中还调用了原型式继承的方法:
将 supfn 与 superfn 传到函数后执行步骤:
最后附上一张其他例子的关系图(来自mqyqingfeng blog):
ES6 Class
第一次接触 Class 语法的时候,就被这个基于原型继承的语法糖便利之处给吸引到。我非常推荐用 Class 来封装函数但前提是对原本继承方法有基本的了解。这里不对 Class 基本使用写太多,可在这里阅读ECMAScript 6 入门 Class篇。只写写需要注意的点。首先是静态方法,在 Class 中如果在函数前加
static
那么表示该方法不会被实例继承但可被子类继承调用,而是直接通过类来调用。静态方法包含的 this 指向类而不是实例。关于 Class 中的 prototype 属性和 proto 属性,建议阅读链接中文章。继承
Class 继承方式则需要用到
extends
关键字。在下面的例子代码中,constructor 是这个类的构造函数。this 代表实例对象与 function 方式写构造函数一致。在子类的构造函数中出现了super
这个关键字。super
可当作函数或者对象使用。super
作为函数调用时代表父类的构造函数。子类 constructor 构造函数必须执行一次super
函数。值得注意的是,super
虽然代表了父类 Point 的构造函数,但返回的是子类 ColorPoint 的实例,即super
内部的 this 指的是 ColorPoint,因此super
在这里相当于Point.prototype.constructor.call(this)
。super
作为函数时只能用在子类的构造函数之中调用。super
作为对象时,在普通方法中指向父类的原型对象,既可直接调用父类方法例如super.toString()
相当于Point.prototype.toString()
,由于 Super 指向父类原型所以在父类构造函数中定义的方法属性无法调用。如果在 static 静态方法中super
作为对象调用则指向父类而不是父类原型。The text was updated successfully, but these errors were encountered: