对象的继承

1、原型对象概述
  • 构造函数的缺点
  1. 通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
  • prototype属性的作用
  1. JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享,不仅节省了内存,还体现了实例对象之间的联系。
  2. JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。
  3. 对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
    原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
    如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
    总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。
  • 什么是原型链
  1. JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
  2. 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。
  3. Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。
  • 原型对象的constructor属性。
  1. prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
  2. constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。
function Person(name) {
  this.name = name;
}

Person.prototype.constructor === Person // true

Person.prototype = {
  method: function () {}
};

Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
2、instanceof运算符的作用和原理
  • instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
  • instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。
3、构造函数的继承
  • 整体继承
  1. 第一步是在子类的构造函数中,调用父类的构造函数。
function Sub(value) {
  Super.call(this);
  this.prop = value;
}

// 上面代码中,Sub是子类的构造函数,this是子类的实例。在子类实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性。

2. 第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = ‘...‘;

// 举例来说,下面是一个Shape构造函数。
function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype.move = function (x, y) {
  this.x += x;
  this.y += y;
  console.info(‘Shape moved.‘);
};

// 我们需要让Rectangle构造函数继承Shape。
// 第一步,子类继承父类的实例
function Rectangle() {
  Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {
  this.base = Shape;
  this.base();
}

// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
  • 单个方法继承
// 有时只需要单个方法的继承,这时可以采用下面的写法。
ClassB.prototype.print = function() {
  ClassA.prototype.print.call(this);
  // some code
}
// 上面代码中,子类B的print方法先调用父类A的print方法,再部署自己的代码。这就等于继承了父类A的print方法。
4、怎么用构造函数实现一个多重继承
  • JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。
function M1() {
  this.hello = ‘hello‘;
}

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

function S() {
  M1.call(this);
  M2.call(this);
}

// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 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(混入)。
5、谈一谈模块化编程
  • 什么是模块
  1. 模块是实现特定功能的一组属性和方法的封装。
  2. JavaScript 不是一种模块化编程语言,ES6 才开始支持“类”和“模块”。下面介绍传统的做法,如何利用对象实现模块的效果。
  • 基本实现方法及缺点
  1. 简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面。
  2. 这样的写法会暴露所有模块成员,内部状态可以被外部改写。
var module1 = new Object({
 _count : 0,
 m1 : function (){
  //...
 },
 m2 : function (){
   //...
 }
});

module1.m1();

module1._count = 5;
  • 用构造函数封装私有变量及缺点
  1. 我们可以利用构造函数,封装私有变量。
  2. 这种方法将私有变量封装在构造函数中,导致构造函数与实例对象是一体的,总是存在于内存之中,无法在使用完成后清除。这意味着,构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据,违背了构造函数与实例对象在数据上相分离的原则。
function StringBuilder() {
  var buffer = [];

  this.add = function (str) {
     buffer.push(str);
  };

  this.toString = function () {
    return buffer.join(‘‘);
  };

}
  • IIFE方式封装私有变量,JavaScript模块的基本写法
// 使用“立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的。
var module1 = (function () {
 var _count = 0;
 var m1 = function () {
   //...
 };
 var m2 = function () {
  //...
 };
 return {
  m1 : m1,
  m2 : m2
 };
})();
// 使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
// 上面的module1就是 JavaScript 模块的基本写法。
  1. 模块的放大模式和宽放大模式
    • 放大模式
var module1 = (function (mod){
 mod.m3 = function () {
  //...
 };
 return mod;
})(module1);
// 上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。
    • 宽放大模式
var module1 = (function (mod) {
 //...
 return mod;
})(window.module1 || {});
// 与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象。

2. 输入全局变量

    • 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
    • 为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) {
 //...
})(jQuery, YAHOO);
// 上面的module1模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

3. 命名空间的作用

(function($, window, document) {

  function go(num) {
  }

  function handleEvents() {
  }

  function initialize() {
  }

  function dieCarouselDie() {
  }

  //attach to the global scope
  window.finalCarousel = {
    init : initialize,
    destroy : dieCarouselDie
  }

})( jQuery, window, document );
// 上面代码中,finalCarousel对象输出到全局,对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的。

对象的继承

上一篇:presto找不到jdk环境


下一篇:使用zlog实现日志记录