JavaSrcipt红宝书阅读导图(三)



  • 五、面向对象

    对象是无序属性的集合,属性可以包含基本值、对象或者函数

    1. 属性类型

    1.1 数据属性

    • [[Configurable]]
      • 能否通过delete函数而重新定义属性
      • 能否修改属性特性
    • [[Enumerable]]
      • 能否for-in
    • [[Writable]]
      • 能否修改
    • [[Value]]
      • 数据值
    • 修改特性
      • Object.defineProperty(objName,propName,desciptor)
      • Configurable修改为false之后无法恢复

    1.2 访问器属性

    • [[Configurable]]
    • [[Enumerable]]
    • [[Get]]
      • 读取时调用函数
    • [[Set]]
      • 赋值时调用函数
    • 定义多个属性
      • Object.defineProperties()
    • 读取属性特性
      • Object.getOwnPropertyDescriptor()

    2. 创建对象

    2.1 工厂模式

    function createPerson(name, age, job){ 
      let o = new Object();
      //... 
      return o;​​​​
    }
    let person1 = createPerson("Bonne",12,"Writer");​​​
    

    2.2 构造函数模式

    • 构造经历步骤
      1. 创建一个对象
      1. 将构造函数的作用域赋给新对象
      1. 执行构造函数中的代码
      1. 返回新对象
    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      //...
    ​​​​​​}
    let person = new Person("Nick",11,"Doctor");​​​
    
    • 对象都拥有constructor属性,指向对象类型
    • 将构造函数作为函数
    let person = new Person(...) //构造函数
    Person(...) //挂在window对象下
    let o = new Object();Person.call(o,...); //在另一个对象的作用域中使用
    
    • 问题
      • 在构造函数中定义的函数会导致不同的作用域链和标识符解析,即是不同的函数
        需要在构造函数外部定义函数,再在构造函数中将函数指针赋值

    2.3 原型模式

    function Person(){}
    Person.prototype.name = "Google";
    Person.prototype.age = 23;
    Person.prototype.sayname = function(){
      //...
    };
    let person1 = new Person();​​​​​
    
    • prototype属性
      • 是一个指针,指向一个对象
      • 可以共享它所包含的属性和方法
    • 理解
      • 只要创建一个新函数,就会创建一个prototype属性,指向原型对象
      • 所有原型对象在默认下自动获得一个constructor属性,包括指向prototype属性所在函数的指针
      • 关系
        • 构造函数的prototype指向原型对象
        • 原型对象的constructor指回构造函数
        • 原型对象中包括后来添加的属性
        • 对象实例的__proto__指向原型对象
      • isPrototypeOf()
        • 确定实例与原型对象是否有关系
      • getPrototypeOf()
        • 返回的对象是对象的原型
      • 实例需要有与原型对象重名的属性时,就在实例创建该属性,就会屏蔽原型属性
        • delete删除实例属性后,再访问该属性会获取原型的值
      • 获取属性时,先从实例中询问是否有该属性,如果没有,就到原型中询问是否有该属性
      • hasOwnproperty()
        • 属性是否来自实例
    • in
      • 属性是否能够被访问
      • hasPrototypeProperty()
        • 访问的属性是否在原型对象中且没有在实例声明
      • for-in
        • 枚举所有属性
        • Enumerablefalse时不能枚举
      • Object.keys()
        • 返回可枚举的属性的字符串数组
    • prototype可重写为新对象
      • 没有默认声明constructor,因此需要手动赋值到对象类型
    • 动态性
      • 原型查找值是一次搜索,即代码从上往下执行,下面声明的原型不会提升
    • 问题
      • 原型中的引用会共享,导致数组公用

    2.4 组合使用构造函数模式和原型模式

    function Person(name, age, job){
      this.name = name; 
      this.age = age; 
      this.job = job; 
      this.friends = ["Shell", "Twitter"]; //不同实例的friends分配了不同的空间
    ​​​​​}
    Person.prototype = { 
      //手动赋值到对象类型​
      constructor: Person,
      sayname: function(){
        //...
      } //方法对于所有的实例都共用代码
    ​​}​
    

    2.5 动态原型模式

    2.6 寄生构造函数模式

    function Person(name, age, job){ 
      var o = new Object(); //先创建一个对象 
      o.name = name;        //对实例属性赋值 
      //... 
      return o;​​​​​
    }
    
    • 不能用instanceof,不推荐

    2.7 稳妥构造函数模式

    • 不能用instanceof

    3. 继承

    3.1 原型链

    • 一个对象类型A的原型对象是另外一个对象类型B的实例,称对象类型A继承了对象类型B
    • 一个原型是另一个类型的实例,层层递进,形成链,就是原型链
    • 搜索机制
      • 实例->原型->原型所指向的实例原型->...
    • 默认都继承原型Object.prototype
    • 子类型原型的属性可以屏蔽父类型属性
    • 问题
      • 父类型构造函数中的数组将在子类型中共用同一个空间
      • 在创建子类型时,不能向父类型的构造函数中传递参数

    3.2 借用构造函数

    • 可以向父类型的构造函数中传递参数
    • 问题
      • 无法复用方法
    function superType(){
      //...
    }
    function subType(){ 
      superType.call(this); //继承superType 
      //...
    ​​​}​​
    

    3.3 组合继承

    • 父类型构造函数解决引用问题
    • 子类型构造函数call解决传递参数问题
    • 子类型原型模式prototype继承方法和定义方法
      • 有两次调用父类型构造函数的缺点

    3.4 原型式继承

    • Object.create()
    • 引用的数组、对象会共享

    3.5 寄生式继承

    • 在调用函数中创建对象,在对象中赋予其他属性与方法,返回该对象
    • 无法复用方法的代码

    3.6 寄生组合式继承

    • 最理想的继承方式
    • 只有一次调用父类型构造函数
    function Super(){
      //...
    }
    function Sub(){ 
      Super.call(this); 
      //...​                                         //其他属性
    ​​}​
    Sub.prototype = Object.create(Super.prototype); //创建、指定对象​
    Sub.prototype.constructor = Sub;                //增强对象
    //...                                           //其他方法​​​
    

    六、函数

    1. 函数声明提升

    • 执行之前会先预读函数声明
    • 可以调用后面代码声明的函数
    • 函数表达式不会提升

    2. 递归

    • 使用argument.callee来递归,可以避免函数指针问题
    • 使用函数表达式也可以避免函数指针问题

    3. 闭包

    • 有权访问另一个函数作用域中的变量的函数
    • 作用域链本质上是一个指向变量对象的指针列表,只是引用而没有复制
    • 在函数A内部创建另一个函数B,B使用A中的变量
      • 在B中访问A的变量时会在作用域链中搜索变量
      • 当B执行完毕后,B的作用域链会销毁
    function createComparisonFunction(propertyName){ 
     return function(obj1, obj2){ 
        var v1 = obj1[propertyName]; 
        var v2 = obj2[propertyName]; 
        //...​​ 
      ​​};​​
    }​
    var compareNames = ​createComparisonFunction('name');    //执行完createComparisonFunction函数后,活动对象没有被销毁,作用域链保留,arguments并没有消除
    var res = ​compareNames({name: 'Nick'}, {name: 'Greg'}); //因为变量propertyName依然为'name',所以可以执行比较
    ​compareNames = null;                                    //解除引用,消除活动对象
    
    • 闭包与变量
      • 闭包只能取得包含函数中任何变量的最后一个值
    • this
      • 匿名函数的执行环境具有全局性,因此this对象通常指向window
    • 内存泄漏
      • 如果闭包中变量是HTML元素,将导致作用域链无法消除
      • 在闭包函数末尾将HTML变量置为null,可以正常回收内存
    • 模仿块级作用域
      • JavaScript没有块级作用域的概念
      • 在函数的语句块中声明变量,都会定义在该函数的活动对象中,导致在语句块外也可以访问这个变量
      • 语法
        • (function(){ ...​​})();
    • 私有变量
      • 在构造函数中定义变量与函数,但是没有赋值到属性当中,这些变量与函数就成为私有变量、私有函数
      • arguments也可以作为私有变量
      • 静态私有变量
      • 模块模式
      • 增强模块模式

 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待