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
functionPerson(name,sex){this.name=namethis.sex=sex}Person.prototype.getInfo=function(){return"I am "+this.name+" and sex is "+this.sex}functionEmployee(name,sex,age){this.name=namethis.sex=sexthis.age=age}Employee.prototype=newPerson()Employee.prototype.getInfo=function(){return"I am "+this.name+" and sex is "+this.sex+" and age is "+this.age}varperson=newPerson('xiaoming','male')varemployee=newEmployee('xiaoming','male',12)console.log(person.getInfo())console.log(employee.getInfo())
前言
最近学了 JavaScript 相关的设计模式,书名《JavaScript设计模式与开发实践》,为加深对 OOP 和设计模式的理解,分成三个部分来理解 JavaScript 的面向对象设计。
面向对象的 JavaScript (OOJS)
面向对象三大特性:
封装
「封装」 把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
私有属性和方法
只能在构造函数内访问不能被外部所访问(在构造函数内使用 var 声明的属性)
公有属性和方法(或实例方法)
对象外可以访问到对象内的属性和方法(在构造函数内使用 this 设置,或者设置在构造函数原型对象上比如 Person.prototype.xxx)
静态属性和方法
定义在构造函数上的方法(比如 Person.xxx),不需要实例就可以调用;
ES6 之前的封装(非 Class)
函数(function)
首先来看零散的语句
缺点很明显:
接下来将零散的的语句写进函数的花括号内,成为函数体。
优点也很明显
提高了代码的复用性;
按需执行——解析器读到此处,函数并不会立即执行,只有当被调用的时候才会执行;
避免全局变量——因为存在函数作用的问题。
原始模式
优点:一把梭
缺点:生成几个实例,相似的对象,代码重复;而且实例与原型之间没有什么联系;
工厂模式
优点:解决代码重复的问题
缺点:p1 和 p2 没有内在联系,不能反映出是同一个原型的实例
构造函数模式
构造函数其实就是一个普通函数,但是内部有 this 指向,对构造函数使用 new 运算符就可以生成构造函数的实例。并且 this 会指向新生成的实例。
比如工厂模式中的 Person 改造成构造函数
优点:解决代码重复的问题,反映出 p1 和 p2 是同一个原型的实例。
缺点:多个实例有重复的属性和方法,占用内存。(所有的实例都会有 getName 方法)
Prototype 模式
每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。这意味着那些不变的属性和方法,可以直接定义在 prototype 对象上。
优点:所有实例的 getName 方法指向都是同一个内存地址,指向 prototype 对象,减少内存占用,提高效率。
一个完整的例子
ES6 之后的封装
在 ES6 之后,新增了
class
这个关键字,代表传统面向对象语言的类的概念。但是并不是真的在 JavaScript 中实现了类的概念,还是一个构造函数的语法糖。存在只是为了让对象的原型功能更加清晰,更加符合面向对象语言的特点。类的公有属性和方法
类的私有属性和方法
原型上的属性和方法
静态属性和静态方法(static)
类的实例属性
注意事项
new Foo();class Foo{}
会报错;继承
继承是面向对象语言中最有意思的概念。
许多面向对象语言都支持两种继承方式,继承通常包括"实现继承"和"接口继承"。
由于 JS 中没有签名,所以无法实现接口继承,只支持实现继承,依赖原型链实现。
原型和实例的关系
看一个简单的原型链实现继承的例子
原型链实现继承存在的问题
实践中很少会单独使用原型链,有几种办法尝试弥补原型链的不足
借助构造函数 / 经典继承
在子类构造函数的内部调用超类构造函数
优点:
缺点:
组合继承 / 伪经典继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,
instanceof
和isPrototypeOf()
也能用于识别基于组合继承创建的对象。同时我们还注意到组合继承其实调用了两次父类构造函数,造成了不必要的消耗。规范化原型继承
在 es5 中,通过
Object.create()
方法规范化了上面的原型式继承,接收了两个参数,一个用作新对象原型的对象、一个为新对象定义额外属性的对象(可选的)。提醒:因为对传入的对象使用的是浅拷贝,所以包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
寄生式继承
构造函数+工厂模式:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
输出
使用寄生式继承来为对象添加函数,无法做到函数复用而降低效率。
寄生组合继承
组合继承是最常用的,但是会调用两次父类构造函数。
一是在创建子类型原型的时候,另一次是在子类型的构造函数内部。
寄生组合式继承就是为了降低父类构造函数的开销而出现的。
集寄生式继承和组合式继承的优点于一身,是最有效的实现类型继承的方法。
多态
看完了上面的封装和继承,到了多态这一步就没那么多复杂的代码了。
概念
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
在强类型语言中,如 C++、C#、Java
通常采用
抽象类
或者接口,进行更高一层的抽象,从而可以直接使用该抽象,即”向上转型“。本质上是为了弱化具体类型带来的限制。在弱类型语言中,如 JavaScript
在 JavaScript 中,万物皆对象,对象的多态性是与生俱来的。多态可以把过程化的条件分支语句转化为对象的多态性,从而消除条件分支语句。
常见的两种实现方式
JavaScript 中的两种多态
通过子类重写父类方法的方式实现多态。
鸭子类型
一个 JavaScript 对象,既可以表示 Duck 类型的对象,又可以表示 Chicken 类型的对象,这意味着JavaScript 对象的多态性是与生俱来的。
总结
在 JavaScript 中,会很难看到多态性的影响。因为 JavaScript 具有动态类型系统,因此在编译时没有函数重载或自动类型强制。由于语言的动态特性,我们甚至都不需要 JavaScript 中的参数多态性。
但是 JavaScript 仍具有两种多态的形式:
总而言之,多态的设计是为了面向对象编程时共享对象的行为。
附录:
new 运算符里做了什么?
遍历实例对象属性的三种方法
for...in...
能获取到实例对象自身的属性和原型链上的属性;Object.keys()
和Object.getOwnPropertyNames()
只能获取实例对象自身的属性;hasOwnProperty()
方法传入属性名来判断一个属性是不是实例自身的属性;属性查找
instance.hasOwnProperty('property')
检查属性是否在指定实例对象上Father.prototype.isPrototypeOf(instance)
判断调用该方法的对象是不是参数实例对象的原型对象模拟 Object.create()
先创建一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个构造函数的一个新实例。
参考文章
The text was updated successfully, but these errors were encountered: