9.1面向对象编程
OOP的基础理念非常简单直观:对象是一个逻辑相关的数据和功能的集合。
它以人类对世界的自然理解为设计理念。
类指的是通用的东西(模板)。实例(或者对象实例)指具体的东西。人类==》郭靖
9.2创建类和实例
在ES6之前,JavaScript 中创建类的过程比较繁琐且不直观。ES6 新引入了一些创
建类的便捷语法:
class Car {constructor(make, model) {this.make = make;this.model = model;this.userGears = ['P', 'N', 'R', 'D'];this.userGear = this.userGears[0];}shift(gear) {if (this.userGears.indexOf(gear) < 0) {throw new Error(`Invalid gear ${gear}`);}this.userGear = gear;}}const car1 = new Car("Tesla", "ModelS");const car2 = new Car("Mazda", "3i");console.log(car1 instanceof Car);console.log(car2 instanceof Car);car1.shift('D');car2.shift('R');console.log(car1.userGear);console.log(car2.userGear);
9.3类即函数
类实际上就是函数。在ES5中,会这样编写Car类
ES6中并没有什么新东西,仅仅是多出了一些更易用的新语法,
//构造方法 始终以大写字母开头function Car(make, model) {this.make = make;this.model = model;this._userGears = ['P', 'N', 'R', 'D'];this._userGear = this._userGears[0];}//使用关键字new来创建一个新的实例对象let car = new Car("Tesla","ModelS");//验证方式class ES6Car {}function ES5Car() {}console.log(typeof ES6Car);console.log(typeof ES5Car);
9.4遍历属性(枚举属性)
对象属性枚举是无序的
const SYM = Symbol("SYM");const o = {a: 1,b: 2,c: 3,[SYM]: 4};for (let prop in o) {if (!o.hasOwnProperty(prop)) {continue;}console.log(`${prop}:${o[prop]}`);}Object.keys(o).forEach(prop => console.log(`${prop}:${o[prop]}`));let syms = Object.getOwnPropertySymbols(o);console.log(syms);for(let i=0;i<syms.length;i++){console.log(syms[i].description);console.log(o[syms[i]]);}
Symbol属性遍历
for in不能遍历到Symbol类型的属性
for of不能遍历到Symbol类型的属性
Object.keys()不能遍历到Symbol类型的属性
Object.getOwnPropertySymbols(obj)仅遍历obj的Symbol类型的属性
9.5动态属性(get/set)
大部分面向对象语言都会通过封装来阻止直接赋值对象属性赋值。
JavaScript没有访问权限机制,动态属性可以稍微有效地弥补这种不足
私有属性加上下划线作为前缀,是一种约定俗成的做法
get/set关键字 关键字将对象属性与函数进行绑定,当属性被访问时,对应函数被执行。
表示即时调用
class Car {constructor(make, model) {this.make = make;this.model = model;this._userGears = ['P', 'N', 'R', 'D'];this._userGear = this._userGears[0];}get userGear() {return this._userGear;}set userGear(value) {if (this._userGears.indexOf(value) < 0) {throw new Error(`Invalid gear ${value}`);}this._userGear = value;}shift(gear) {this.userGear = gear;}}const car1 = new Car("Tesla", "ModelS");const car2 = new Car("Mazda", "3i");console.log(car1 instanceof Car);console.log(car2 instanceof Car);car1.shift('D');car2.shift('R');console.log(car1.userGear);console.log(car2.userGear);
WeakMap
即时调用函数将WeakMap隐藏在一个闭包内,从而阻止了外界的访问。WeakMap可以安全地存储任何不想被Car类外部访问的属性。
const Car = (function() {const carProps = new WeakMap();class Car {constructor(make, model) {this.make = make;this.model = model;this._userGears = ['P', 'N', 'R', 'D'];carProps.set(this, {userGear: this._userGears[0]});}get userGear() {return carProps.get(this).userGear;}set userGear(value) {if (this._userGears.indexOf(value) < 0) {throw new Error(`Invalid gear ${value}`);}carProps.get(this).userGear = value;}shift(gear) {this.userGear = gear;}}return Car;})();const car1 = new Car("Tesla", "ModelS");const car2 = new Car("Mazda", "3i");console.log(car1 instanceof Car);console.log(car2 instanceof Car);car1.shift('D');car2.shift('R');console.log(car1.userGear);console.log(car2.userGear);
9.6原型和原型链
大多数时候,不必搞懂原型链和动态调度的机制。但在不同的阶段,可能会遇到一
些迫使大家去深入学习它们的问题。最好在学习的时候能都明白其中的细节。
function ES5Car() {this.make = "Tesla";//实例方法this.shift = function() {console.log('当前类挂挡');}}let car1 = new ES5Car();let car2 = new ES5Car();console.log(car1.shift ===car2.shift);class ES6Car {constructor(make, model) {this.make = make;this.model = model;this.userGears = ['P', 'N', 'R', 'D'];this.userGear = this.userGears[0];}//原型方法shift(gear) {if (this.userGears.indexOf(gear) < 0) {throw new Error(`Invalid gear ${gear}`);}this.userGear = gear;}}let car3 = new ES6Car("Tesla", "ModelS");let car4 = new ES6Car("Mazda", "3i");console.log(car3.shift===car4.shift);
对应名称
prototype :原型
proto :原型链
从属关系
每个函数都有一个叫作prototype(原型) 的特殊属性
每个对象都有一个叫作proto(原型链)的特殊属性
对象的proto保存着该对象的构造函数的prototype
function Car() {}//prototype -> 函数的一个属性:值是对象console.log(Car.prototype);const car1 = new Car();//__proto__ -> 对象Object的一个属性:值是对象console.log(car1.__proto__);//对象的__proto__保存着该对象的构造函数的prototypeconsole.log(car1.__proto__ === Car.prototype);//console.log(Car.prototype.__proto__);//console.log(Car.prototype.__proto__ === Object.prototype);console.log(Object.prototype.__proto__); //Object最顶层__proto__ null
关于原型,有一个重要的机制叫作动态调度
当试图访问对象的某个属性或者方法时,如果它不存在于当前对象中,JavaScritp会检查它是否存在于对象原型中。因为同一个类的所有实例共用同一个原型,如果原型中存在某个属性或者方法,则该类的所有实例都可以访问这个属性或方法。
function Car() {this.make = "Tesla";this.shift = function(){console.log('当前类挂挡');}}console.log(Car.prototype);Car.prototype.mode = "ModelS";const car1 = new Car();console.log(car1);Object.prototype.userGears = ['P', 'N', 'R', 'D'];console.log(car1.make);console.log(car1.mode);console.log(car1.userGears);// Car {// make:"Tesla",// __proto__:Car.prototype = {// mode:"ModelS",// __proto__:Object.prototype = {// userGears:['P', 'N', 'R', 'D'],// }// }// }Car.prototype.shift = function(){console.log('prototype原型挂挡');}//动态调度car1.shift();
9.7继承
在分析原型的时候,已经看到了继承的身影:当创建一一个类的实例时,它继承了类
原型中所有的功能。尽管如此,它并没有就此打住:如果一个方法没有在对象原型
中找到其定义,它会检查原型的原型。这样就建立了一个原型链。JavaScript 会沿
着原型链走下去,直到某个原型满足了需求。如果找不到这样的原型,程序最终会报错
//交通工具class Vehicle{constructor() {this.passengers = [];console.log("Vehicle Create");}addPassenger(p){this.passengers.push(p);}}//汽车 继承 交通工具class Car extends Vehicle{constructor() {super();console.log("Car created");}deployAirbags(){console.log("BWOOSH");}}const v = new Vehicle();v.addPassenger("Frank");v.addPassenger("Judy");console.log(v.passengers.join(","));const c = new Car();c.addPassenger("Alice");c.addPassenger("Cameron");console.log(c.passengers.join(","));c.deployAirbags();// 注意,可以在c上调用deployAirbags,但是不能在v上调用。换言之,继承// 是单向的。Car类的实例可以访问所有Vehicle类的方法,反之却不行。//v..deployAirbags();//报错
9.8静态方法
静态方法(也叫类方法),它不与实例绑定。在静态方法中,this绑
定的是类本身,但通常使用类名来代替this是公认的最佳实践。
class Car {static getNextVin() {return Car.nextVin++;}constructor(make, model) {this.make = make;this.model = model;this.vin = Car.getNextVin();}//判断辆车是否具有相同的品牌和型号static areSimilar(car1, car2) {return car1.make === car2.make && car1.mode === car2.model;}//判断辆车是否具有相同VINstatic areSame(car1, car2) {return car1.vin === car2.vin;}}Car.nextVin = 0;const car1 = new Car("Tesla", "s");const car2 = new Car("Mazda", "3");const car3 = new Car("Mazda", "3");console.log(car1.vin);console.log(car2.vin);console.log(car3.vin);Car.areSimilar(car1, car2);Car.areSimilar(car2, car3);Car.areSame(car2, car3);Car.areSame(car2, car3);
9.9多态
多态是面向对象的一个术语,意思是一个实例不仅是它自身类的实例,也可以被当做它的任何父类的实例来使用。
JavaScript弱类型语言,就是数据类型可以被忽略的语言,天生具有多态性。
JavaScript提供了instanceof运算符,它会指出某个对象是否属于某个给定类。
class Vehicle{}class Motorcycle extends Vehicle {}class Car extends Vehicle {}const c = new Car();const m = new Motorcycle();console.log(c instanceof Car);console.log(c instanceof Vehicle);console.log(m instanceof Car);console.log(m instanceof Motorcycle);console.log(m instanceof Vehicle);function run(obj){if(obj instanceof Car){console.log("汽车对象,执行汽车的方法");}if(obj instanceof Motorcycle){console.log("摩托车对象,执行汽车的方法");}}
9.10hasOwnProperty()和toString()方法
class Super {constructor() {this.name = 'Super';this.isSuper = true;}}//合法,但不推荐这么做Super.prototype.sneaky = 'not recommended';class Sub extends Super {constructor() {super();this.name = 'Sub';this.isSub = true;}}const obj = new Sub();// 如果obj中存在x属性,obj . hasOwnProperty(x)返回true。反之,如果属性// 没有定义或者定义在原型链中,obj .hasOwnProperty (x)结果为false。for (let p in obj) {console.log(`${p}:${obj[p]}`+(obj.hasOwnProperty(p)?'':'(inherited)'));}// name,isSuper 和isSub三个属性都被定义在实例中,而不是在原型链中,// 属性sneaky被手动添加到父类的原型中。// 使用object. keys就可以完全避免这个问题,因为object.keys只包含了原型中定义的属性。
每个对象最终都是继承自object类,所以可以在object中使用的方法在任何,其他对象中都可以用。其中的一个方法是toString,该方法是为了给对象提供一个默认的字符串表示。toString默认的方法会返回”[object object]”, 这
种表示并没有什么实际用处。
class Car {static getNextVin() {return Car.nextVin++;}constructor(make, model) {this.make = make;this.model = model;this.vin = Car.getNextVin();}toString() {return `${this.make} ${this.model}:${this.vin}`;}}Car.nextVin = 0;let car = new Car("Tesla", "s");console.log(car.toString());
9.11多继承
一些面向对象语言(C++)支持多继承,也就是说一个类可以有两个直接的父类。多继承有引发冲突的风险。
比如两个父类都有一个同名的方法,那么子类该继承谁的方法呢?所以很多编程语言选择单继承来避免这个问题。但是 当思考现实世界的问题时,多继承往往是有意义的。例如,汽车可能继承自交通工具和“可接受保险的”。不支持多继承的编程语言通常会引人接口的概念来绕过这个问题。
一个类(Car)只能有一个父类(Vehicle),但它可以有多个接口(Insurable,Container等等)。
JavaScript是一种有趣的混合。技术上,它是一一个单继承的语言,因为在原型链并
不会去寻找多个父类,但是它确实提供了一些方法,有时候这些方法比多继承或者
接口还要好用。
class InsurancePolicy {}class Car{}function makeInsurable(o) {o.addInsurancePolicy = function(p) {this.insurancePolicy = p;}o.getInsurancePolicy = function() {return this.insurancePolicy;}o.isInsured = function() {return !!this.insurancePolicy;}}const car1 = new Car();makeInsurable(car1);car1.addInsurancePolicy(new InsurancePolicy())
