Why use OO
减少重複的程式码减少记忆体的使用使实体有关联性类别、物件
类别: 是一个蓝图,未有实体(instance) Ex:车子设计图物件: 透过类别建立出来的实体 Ex:车子原型链特性
JS prototype Base 继承方式最上层的原型为物件向上寻找属性、方法的特性继承特性 (不同实体,继承同一属性、方法)类别使用 new关键字 建造出实体JS 原型链
若要建立两个汽车的实体,该如何建立?
Level 0 (直接建立物件)
直接建立两个物件其缺点: 繁琐、无法统一管理、扩展性差、占用记忆体 const carA = { wheels: 4, pressHorn() { console.log('AAA') }, } const carB = { wheels: 4, pressHorn() { console.log('BBB') }, }
Level 1 (prototype建立蓝图)
利用 JS prototype 建立蓝图使用 new 关键字建立实体缺点: function功能相同,但每次创造实体时都複製一份,占用了不必要的记忆体 const Car = function (brand, sound = '888') { this.brand = brand this.wheels = 4 this.pressHorn = function () { console.log(sound) } } const carA = new Car('AAA') const carB = new Car('BBB', 'BBB')
Level 2 (在原型上建立共用方法)
将 Function 挂载到蓝图的 prototype利用原型链 向上寻找的特性,实体无该属性、方法即往上寻找可用的属性、方法写于原型仅佔一份记忆体 Car.prototype.pressHorn = function () { console.log(`${this.brand} ${this.sound}`) } // true 表 Function 来自同一个记忆体位置 console.log(carA).pressHorn() === carB.pressHorn())
Level 3 (ES5 Reveal Pattern)
封装内部资料,公开想公开的介面ES5 範例 const Car = function (brand,sound) { this.brand = brand this.wheel = 4 this.pressHorn = function (){ console.log(sound) } return { brand, pressHorn: this.pressHorn } } const test = new Car('A','AAA') console.log(test.wheels) // undefined 不可取得 console.log(test.brand) // 可取得
Level 4 (ES6 Class)
JS 为 prototype base,因此 Class 仅为 prototype的语法糖使用 Class 使语法更精简 class Car { // 写于 constructor的内容,皆会在记忆体创一份新的 // 因此 方法避免写于 constructor 内部 constructor(brand = 'default') { // constructor内容会于实体建立时执行 this.brand = brand this.init() } init() { console.log('init') } pressHorn(){ console.log(`${this.brand} ${this.sound}`) } }
Level 5 (ES6 static/get/set)
ES6 Static 用法
静态方法不须实例化,即可被呼叫但被实例化后,则静态方法不能被呼叫 (私有方法)常用于工具函式 (资料处理) class Calculator { constructor(str) { this.str = str } static add(a, b) { console.log(a + b) } hey() { return this.str } } const calculatorA = new Calculator('hey') // 建立实体后,无法透过实体使用该函式 console.log(calculatorA.add) // undefined console.log(calculatorA.hey()) // 'hey' // 没有建立实体即可使用 Calculator.add(1, 4) // 3
ES6 get/set 用法 (仅用于私有属性)
可控制 资料 写入 & 读出的过程可于中间作一些格式处理、错误侦测getter不会有传入参数,setter只会有一个传入参数看不是很懂... 範例参考至此 class Option { constructor(key, value) { if (typeof key !== 'undefined') { this[`_${key}`] = value } } get color() { if (this._color !== undefined) { return this._color } return 'no color prop' } set color(value) { this._color = value } } const op1 = new Option('color', 'red') op1.color = 'yellow' const op2 = new Option('action', 'run') op2.color = 'yellow'
ES5 getter / setter 写法
使用 Object.defineProperty若要使物件的属性无法被更动 writable: false, Object.defineProperty(obj, 'key', { get() { // 取得值前 可于此作format....etc return this.name }, set(val) { // 设定值前可以作一些 例外判断 (falsy...etc) this.name = val console.log(val) }, })
Bonus: 预设参数 & 解构
const User = function ({ name = 'default', age }) { this.name = name // 可预设但可传入修改 this.age = age this.gender = 'male' // 可预设不给用传入的 } // 传入顺序无差,以 Key为基準 let test = new User({ age: 30 }) console.log(test.name, test.gender) // 'default' 'male'
取得原型语法
取得原型1: 双下底线proto (不推荐使用 效能差)取得原型2: Object.getPrototypeOf(obj) (建议使用、IE9+)// ESLint/MDN 不建议使用 __proto__ 取得原型const CarProto = Car.__proto__// 推荐使用 ES5const CarProto = Object.getPrototypeOf(Car)
instanceof 原理 (判断是否有该原型)
判断目标是否在其原型链之下 // 判断 'str' 是否在 String 之下 console.log('str' instanceof String) // false,因为此种表示方法为原始型别,其原型链为 undefined // 使用 new 关键字建立物件 const newStr = new String('newStr') console.log(newStr instanceof String ) console.log(newStr instanceof Object ) // 皆true,String & Object 皆于 newStr 原型链上
术语
literal不使用 constructor(构造函数 new) 建立 dataconstructor 构造函式
一个指定对象实例的类型的类或函数new 关键字
创建一个用 自定义的对象类型 的 instance(实例)
或具有构造函数的内置对象的实例
// Object.literal const obj = { test(){ console.log('hi') } } const mark = new Person() const str = new String()
小建议
类别 使用大写作为开头 Car不公开方法 使用_作为开头 static _add()使用 Object.getPrototypeOf(obj) 取得原型使用 ES6 Class使用 预设参数 & 解构参考资料
保哥 物件导向基础