(79)Wangdao.com第十五天_JavaScript 对象的继承_prototype原型对象_封装_函数式编程

javascript 内置了许多 function 函数(){...}javascript

js 执行首先就会执行本身内置的函数定义 (function Function、function Object)html

对象的继承java

大部分面向对象的编程语言,都是经过“类”(class)实现对象的继承。c++

传统上,JavaScript 语言的继承不经过 class,而是经过“原型对象”(prototype)实现,称之为 JavaScript 的原型链继承面试

JavaScript 继承机制的设计思想就是,原型对象 prototype 的全部属性和方法,都能被实例对象共享编程

ES6 引入了 class 语法数组

  • 构造函数的缺点
    • 同一个构造函数的多个实例之间,没法共享属性,从而形成对系统资源的浪费
      • function Cat(name, color) {
            this.name = name;
            this.color = color;
            this.bar = function () {
                console.log('喵喵');
            };
        }
        
        var cat1 = new Cat('大毛', '白色');
        var cat2 = new Cat('二毛', '黑色');
        
        cat1.meow === cat2.meow    // false

        cat1 和 cat2 是同一个构造函数的两个实例,它们都具备 bar 方法。因为 bar 方法是生成在每一个实例对象上面,因此两个实例就生成了两次。浏览器

    • 也就是说,每新建一个实例,就会新建一个 bar方法。这既没有必要,又浪费系统资源,由于全部 bar 方法都是一样的行为,彻底应该共享
    • 这个问题的解决方法,就是 JavaScript 的原型对象(prototype)

 

  • JavaScript 的原型对象
    • JavaScript 继承机制的设计思想就是,原型对象的全部属性和方法,都能被实例对象共享
    • 属性和方法定义在原型上,那么全部实例对象就能共享,不只节省了内存,还体现了实例对象之间的联系
    • JavaScript 规定,每一个函数都有一个 prototype 属性,指向一个对象。
      • 对于普通函数来讲,该属性基本无用。
      • 可是,对于构造函数来讲,生成实例的时候,该属性会自动成为实例对象的原型
        • function Animal(name) {
              this.name = name;
          }
          Animal.prototype.color = 'white';
          
          var cat1 = new Animal('大毛');
          var cat2 = new Animal('二毛');
          
          cat1.color    // 'white'
          cat2.color    // 'white'
      • 原型对象的做用,就是定义全部实例对象共享的属性和方法。
  • 在函数建立时,都会默认建立 显示原型对象

 

读取属性/方法,沿着原型链找安全

设置属性/ 方法,只会查看和影响自身编程语言

全部函数都具备 prototype 显式原型属性,指向一个对象____原型对象 

  •  function Dog(){};

全部对象都是某个构造函数的实例,都拥有 __proto__隐式原型属性

  •  var wangCai = new Dog("旺财", "2");

注意:

  • 因为 函数 也是一个对象,因此 函数 也拥有一个 __proto__隐式原型属性,由原生底层语言实现

constructor    构造函数____等于函数自己

__proto__    隐式原型属性____指向 该对象的构造函数的原型对象 prototype 隐式原型属性 指向 上一级对象的原型对象) 

  • 同一构造函数的 全部实例对象 都有一个 隐式原型 指向同一个原型对象

  • 也就是说,能够在 构造函数 定义时,存放一些实例对象 公共方法 公共属性

 

  • 底层 native code 用 c/c++ 实现
  • 全部 函数 有一个 prototype 显示原型属性

全部函数都是 function Function(){...} 的实例

  • 全部 实例对象 都有一个 __proto__ 隐式原型属性 (包括原型对象都有一个 __proto__ )
  • 全部 原型对象 都有 constructor 属性指向 构造函数

 

    • 原型链
      • 当调用某对象的属性或者方法时,沿着 __proto__ 这条链向上查找,
          • 首先在自身做用域中寻找,
          • 而后到原型对象中寻找,
          • 再到原型对象的原型对象中寻找,直到找到 Object 。
          • 若是始终没找到就返回 undefined。
      • 若是对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性____这叫作 “覆盖”(overriding)
      • JavaScript 规定,全部对象都有本身的 隐式原型属性 __proto__        指向 new 构造函数的 原型对象
        • 一方面,任何一个对象,均可以充当其余对象的原型;
        • 另外一方面,因为原型对象也是对象,因此它也有本身的原型。
        • 所以,就会造成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
      • Object.prototype 的原型是 null ,所以,原型链的尽头就是 null 
      • 所寻找的属性在越上层的原型对象,对性能的影响越大。若是寻找某个不存在的属性,将会遍历整个原型链。
          • /* 面试题 */
            var A = function() {
            
            }
            
            A.prototype.n = 1
            
            var b = new A()
            
            A.prototype = {    // 改变的 只是一个地址值,而不会改变 真正对象 的存在(b 始终指向那个 原始的 prototype 对象)
                n: 2,
                m: 3
            }
            
            var c = new A()
            console.log(b.n, b.m, c.n, c.m)
      • 举例来讲,若是让构造函数的 prototype 属性指向一个数组,就意味着实例对象能够调用数组方法
        • prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数
        • constructor 属性的做用
          • 能够得知某个实例对象,究竟是哪个构造函数产生的
          • 有了 constructor 属性,就能够从一个实例对象新建另外一个实例
            • function Constr() {}
              var x = new Constr();
              
              var y = new x.constructor();
              y instanceof Constr;    // true

              这使得在实例方法中,调用自身的构造函数成为可能

              • Constr.prototype.createCopy = function () {
                    return new this.constructor();
                };
      • constructor 属性表示原型对象与构造函数之间的关联关系,若是修改了原型对象,通常会同时修改 constructor 属性,防止引用的时候出错
      • 要么将 constructor 属性从新指向原来的构造函数,要么只在原型对象上添加方法,这样能够保证 instanceof 运算符不会失真
      • 若是不能肯定 constructor 属性是什么函数,还有一个办法:经过 name 属性,从实例获得构造函数的名称。
        • function Foo() {}
          var f = new Foo();
          f.constructor.name    // "Foo"
    • 全部 函数 都是 function Function(){} 的实例对象,

        • 包括 Object 的构造函数        的 __proto__ 都指向 Function 的原型对象

        • 甚至 Function 本身的构造函数        的 __proto__ 都指向 Function 的原型对象
            • console.log(Object instanceof Function);    // true
              console.log(Object instanceof Object);    // true
              console.log(Function instanceof Function);    // true
              console.log(Function instanceof Object);    // true
    • instanceOf 运算符
          • A instanceof B            若是 B 函数的显示原型对象 A 对象的 __proto__ 原型链上,则返回 true,不然返回 false
      • 返回一个布尔值,表示对象是否为某个构造函数的实例
        • var v = new Vehicle();
          v instanceof Vehicle;    // true

          实例对象 instanceOf 构造函数

          • 它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上
            v instanceof Vehicle
            // 等同于
            Vehicle.prototype.isPrototypeOf(v)
      • 有一种特殊状况,就是左边对象的原型链上,只有null对象。这时,instanceof 判断会失真
        • var obj = Object.create(null);
          typeof obj;    // "object"
          Object.create(null) instanceof Object;    // false

          所以,只要一个对象的原型不是null,instanceof运算符的判断就不会失真。

      • 使用 instanceOf 判断一个变量的类型
        • var x = [1, 2, 3];
          var y = {};
          x instanceof Array    // true
          y instanceof Object    // true

          注意,instanceof 运算符只能用于对象,不适用原始类型的值。

      • 对于undefined和null,instanceOf运算符老是返回false
      • 利用 instanceof 运算符,还能够巧妙地解决,调用构造函数时,忘了加 new 命令的问题
        • function Fubar (foo, bar) {
              if (this instanceof Fubar) {
                  this._foo = foo;
                  this._bar = bar;
              } else {
                  return new Fubar(foo, bar);
              }
          }
    • 构造函数的继承

让一个构造函数继承另外一个构造函数,是很是常见的需求。这能够分红两步实现

  • 在子类的构造函数中,调用父类的构造函数
  • 让子类的原型指向父类的原型,这样子类就能够继承父类原型
    • function Sub(value) {
          Super.call(this);
          Sub.prototype = Object.create(Super.prototype);
          Sub.prototype.constructor = Sub;
      
          this.name = value;
          Sub.prototype.method = '...';
      }

      另一种写法是 Sub.prototype 等于一个父类实例,可是子类会具备父类实例的方法。有时,这可能不是咱们须要的,因此不推荐使用这种写法

      • Sub.prototype = new Super();

         

  • 有时只须要单个方法的继承,这时能够采用下面的写法
    • ClassB.prototype.print = function() {
          ClassA.prototype.print.call(this);
          // some code
      }

      这就等于继承了父类A的 print 方法

 

  • 封装——函数式编程
  • 在 ES6 加入 class 关键字以前的编程方式
  • /**** 旨在实现封装的前提下,最少占用内存 ****/
    // 封装 父类
    function Parent(name, age){
        this.name = name;
        this.age = age;
    };
    
    Parent.prototype.setName = function(name){
        this.name = name;
    };
    
    Parent.prototype.setAge = function(age){
        this.age = age;
    };
    
    // 封装 子类
    function Child(name, age){
        Parent.call(this, name, age);    // 继承父类的属性
        this.isCrying = false;
    };
    
    Child.prototype = new Parent();    // 继承父类的方法
    Child.prototype.constructor = Child;    // 修正构造器指向

 

    • 多重继承
      • JavaScript 不提供多重继承功能,即不容许一个对象同时继承多个对象。可是,能够经过变通方法,实现这个功能
        • function M1() {
              this.hello = 'hello';
          }

          function M2() {
              this.world = 'world';
          }

          function S() {
              M1.call(this);
              // 继承 M1
              S.prototype = Object.create(M1.prototype);

              M2.call(this);
              // 继承链上加入 M2
              Object.assign(S.prototype, M2.prototype);

              // 指定构造函数
              S.prototype.constructor = S;
          }

          var s = new S();
          s.hello    // 'hello'
          s.world    // 'world'

          子类 S 同时继承了父类 M1 和 M2 。这种模式又称为 Mixin(混入)

 

    • 模块
      • JavaScript 不是一种模块化编程语言,ES6 才开始支持 “类” 和 “模块”
      • ES5 中传统方法实现模块
        • 模块是实现特定功能的一组属性和方法的封装
          • 简单的作法是把模块写成一个对象,全部的模块成员都放到这个对象里面
          • 可是,这样的写法会暴露全部模块成员,内部状态能够被外部改写。好比,外部代码能够直接改变内部计数器的值。
            • var module1 = new Object({
                 _count : 0,
                 m1 : function (){
                    //...
                 },
                 m2 : function (){
                     //...
                 }
              });

               

        • 能够利用构造函数,封装私有变量
          • function StringBuilder() {
                var buffer = [];
            
                this.add = function (str) {
                     buffer.push(str);
                };
            
                this.toString = function () {
                    return buffer.join('');
                };
            }
          • 构造函数有双重做用,既用来塑造实例对象,又用来保存实例对象的数据
          • 背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不该该保存在实例对象之外。同时,很是耗费内存)
          • 解决:
            • function StringBuilder() {
                  this._buffer = [];
                  StringBuilder.prototype = {
                      constructor: StringBuilder,
                      add: function (str) {
                          this._buffer.push(str);
                      },
                      toString: function () {
                          return this._buffer.join('');
                      }
                  };
              }

              以上代码将私有变量放入实例对象中,好处是看上去更天然,可是它的私有变量能够从外部读写,不是很安全

 

        • 使用 “当即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数做用域里面,能够达到不暴露私有成员的目的
            • var module1 = (function () {  var age = 0;  var getAge = function () {   return this.age;
                };   var setAge = function (age) {    this.age = age;
                };   return {     getAge : getAge,    setAge : setAge  }; })();

              使用上面的写法,外部代码没法直接获取内部的_count变量。

 

        • 模块的 放大模式 (argumentation)
          • 若是一个模块很大,必须分红几个部分,或者一个模块须要继承另外一个模块,这时就有必要采用“放大模式”。
            • var module1 = (function (mod){
                 mod.m3 = function () {
                    //...
                 };
                 return mod;
              })(module1);

              上面的代码为 module1 模块添加了一个新方法 m3(),而后返回新的 module1 模块。

          • 在浏览器环境中,模块的各个部分一般都是从网上获取的,有时没法知道哪一个部分会先加载。
          • 若是采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用 "宽放大模式"(Loose augmentation)
            • var module1 = (function (mod) {
                  //...
                  return mod;
              })(window.module1 || {});

              与"放大模式"相比,“宽放大模式”就是“当即执行函数”的参数能够是空对象

 

        • 输入全局变量
          • 独立性是模块的重要特色,模块内部最好不与程序的其余部分直接交互
          • 为了在模块内部调用全局变量,必须显式地将其余变量输入模块
            • var module1 = (function ($, YAHOO) {
                 //...
              })(jQuery, YAHOO);

               

            • 上面的module1模块须要使用 jQuery 库和 YUI 库,就把这两个库(实际上是两个模块)看成参数输入module1

            • 这样作除了保证模块的独立性,还使得模块之间的依赖关系变得明显

          • 当即执行函数还能够起到命名空间的做用
            • (function($, window, document) {
              
                  function go(num) {
                  }
              
                  function handleEvents() {
                  }
              
                  function initialize() {
                  }
              
                  function dieCarouselDie() {
                  }
              
                  //attach to the global scope
                  window.finalCarousel = {
                      init : initialize,
                      destroy : dieCouraselDie
                  }
              
              })( jQuery, window, document );

               

            • 上面代码中,finalCarousel 对象输出到全局,对外暴露 init() 和 destroy() 接口,

            • 内部方法 go()、handleEvents()、initialize()、dieCarouselDie() 都是外部没法调用的。

相关文章
相关标签/搜索