读书摘要 -《JavaScript 面向对象精要》

『22年09月29日』

banner
图:书本封面

分享最近读的一本书 -《JavaScript 面向对象精要》,作者是尼古拉斯(Nicholas C. Zakas),著名的 JavaScript 开发者。你可能会因为他的另外一本书而熟悉-《JavaScript 高级程序设计》,熟称红宝书。作者文笔干练,每次都能用简洁、优雅的语言直击核心要点,另读者心领神会,就像这本书一样,短短 90 页内容,却贯穿了整个 JavaScript 最核心的部分。

这本书主要讲了,“如何在没有类概念的 JavaScript 中实现面向对象编程”,因为这本书的写作背景是在 2013 年,那时 JS 版本处于 ES5 阶段,还没有类的概念,书本的目标是指在帮助那些习惯使用带有类概念编程语言如 Java,C#的开发者快速上手 JS,同时也说明了 JS 是一门非常灵活的语言。

本书共有六个章节:

  1. 原始类型和引用类型
  2. 函数
  3. 理解对象
  4. 构造函数和原型对象
  5. 继承
  6. 对象模式

原始类型和引用类型

这章讲了 JS 的数据类型分为两类 - 原始类型和引用类型。
两者的区别:

原始类型 引用类型
存储方式 直接存储 只存储指针
鉴别方法 typeof + “===null” typeof function + instanceof

另外,每个页面有自己的全局上下文-Array、Object 以及其他内建类型的版本,当你把一个数组从一个框架传到另一个框架的时候,instanceof 不能正常识别,因为那个数组是来自不同框架的 array 的实例,可以用 Array.isArray 解决这个问题

函数

函数在 JS 中是一等公民,因为它本质上也是一个对象,所以可以被当作对象使用,也可赋值给变量,也可作为函数的参数,或者作为函数的返回值,威力无穷大。

概要:

  • 函数也是对象,和其它对象的区别就是内部具有[[Call]]属性
  • 使用 typeof 操作符鉴别函数的时候,会检查是否具有内部属性[[Call]],如果找到,则返回”function”
  • 函数声明会被提升到上下文的顶部,而函数表达式不会
  • length 属性保存了期望的参数数量
  • 函数重载的概念是一个函数可以具有多个签名,也就是可以声明多个同名函数,只是他们的参数数量,参数类型不同,然后调用的时候语言会自动根据提供的参数数量,参数类型调用相应的函数。由于 JS 同名函数会被覆盖,所以也就不能实现多个签名,也就没有重载的概念,JS 可以在函数内部通过判断参数的数量来实现伪重载。
  • 所有的函数作用域内都有一个 this 对象代表调用该函数的对象
  • 可以使用 this 对象来解耦合

理解对象

基于类的语言会根据类的定义锁定对象,JavaScript 的对象则没有这种限制,它是动态的,可在代码执行的任意时刻进行改变。

概要:

  • 创建对象属性的时候,实际上会调用对象的内部属性[[Put]],修改的时候调用[[Set]],删除调用[[Delete]]

  • 由于使用 if(obj.property){//do something} 判断属性是否存在的时候,当值为 null,undefined,NaN,false 或空字符串时评估为假,而对象的属性可能包含这些值,所以使用 in 操作符来判断属性是否存在更加靠谱。
    in 操作符会检查实例属性和原型属性,可以配合 hasOwnProperty 来检查是否时自有属性

  • for-in 和 Object.keys 都可以用来遍历对象的可枚举属性,后者只会在实例上遍历

  • Object.getOwnPropertyNames 可以用来获取对象的所有自身属性的属性名,包括不可枚举属性

  • 对象的属性有两种类型,数据属性和访问器属性
    两者的一些区别:
数据属性 访问器属性
存储 可以直接保存值 只定义 getter 和 setter 的行为
属性特征 [[Enumerable]] [[Configurable]] [[Writable]] [[Value]] [[Enumerable]] [[Configurable]] [[Get]] [[Set]]

因为这些不同的特性,区别开了数据属性和防问器属性的工作模式

  • 可以用 Object.defineProperty 或 Object.defineProperties 来更改属性的特性
  • 可以用 Object.getOwnPropertyDescriptor()获取属性特性

对象和属性一样具有指导其行为的内部特征,其中,用[[Extensible]]来表示对象是否可以被修改,默认是可扩展的,也就是可以添加新属性。

禁止修改对象的一些方法:

  • 禁止扩展。使用 Object.preventExtensions()创建不可扩展的对象
  • 对象封印。使用 Object.seal() 创建的对象其[[Extensible]][[Configurable]]都为 false
  • 对象冻结。使用 Object.freeze() 创建的对象,其数据属性都为只读[[Extensible]][[Configurable]][[Writable]]都为 false

应在严格模式下使用冻结对象,这样修改就会报错

继承

Object.create 可以用来创建一个对象并为其指定[[Prototype]]的值

创建一个对象字面量的时候,会自动去继承 Object.Prototype


  const obj = { title: 'hi~'};
  // auto done by engine
  Object.create(Object.prototype,{ title: {
    value: 'hi~',
    configurable:true,
    enumerable:true,
    writable:true,
  }})

这章讲了 JS 实现继承的几种办法:

对象继承:因为当访问对象属性时,会有原型链查找机制,所以可以利用 Object.create 来指定[[Prototype]]的值,从而实现继承

原型对象链继承:定义在原型对象(x.Prototype)上的属性和方法会被其实例共享

构造函数继承:改写子类的 prototype,令其[[Prototype]]属性指向父类的原型对象,从而实现原型链继承;
有以下两种实现方式:

1. ChildClass.prototype = new ParentClass(); ChildClass.prototype.constructor = ChildClass;
2. ChildClass.prototype = Object.create(ParentClass.prototype,{constructor: { value:ChildClass }})

构造函数窃取:利用 call,apply 方法在子类中调用父类构造函数从而继承到父类的自有属性,再通过修改子类 prototype 中的[[Prototype]]属性指向父类的 prototype 来实现继承父类原型对象上的方法,这也叫伪类继承

混入:不改变原型对象链,而是直接复制另一对象的属性,如果对象的属性也是一个对象,那就是浅拷贝

另外,JS 不提供 super 来调用父类的方法(ES5),可以用 call,apply 代替
如:


  ParentClass.prototype.sayName.call(childClassThis);

对象模式

私有成员和特权成员 也就是怎么实现 OO 中的 private。JavaScript 对象的所有属性都是公开的,可以用下面两种方式实现私有属性:

  1. 模块模式

    利用 IIFE 产生一个可以访问一个已销毁函数作用域的闭包,然后在返回的闭包里包含访问私有成员的“特权方法”。比如,

    
       const obj = (function(){
         let age = 18;
         return {
           getAge() { return age; }, 
           setAge(val) { age = val; }
         }
       }())
     
  2. 构造函数的私有成员

    在构造函数中使用本地变量,然后暴露更改变量的接口函数。比如,

    
      function Person() {
        let age = 18;
        this.getAge = function() {
          return age;
        }
        this.setAge = function(val) {
          age = val;
        }
      }
    

作用域安全的构造函数当你没有使用 new 操作符去调用的时候,会在内部自己添加 new。比如,内置的 Array,Object,RegExp 和 Error



function Person(name){
  if(this instanceof Person) {
    // called with new
    this.name = name;
  } else {
    // called without new
    return new Person(name);
  }
}

以上,尼古拉斯使用 JS 实现了 OOP 中类的:封装,聚合,继承和多态的特性。