Skip to content

Latest commit

 

History

History
600 lines (454 loc) · 14.3 KB

面向对象编程.md

File metadata and controls

600 lines (454 loc) · 14.3 KB

返回目录

JavaScript Object Oriented Programming

面向对象编程思想核心是在软件工程领域解决代码重用与组织的问题,面向对象编程包含类、对、属性与方法这些关键组成。而类具有以下特征 :

Encapsulation: 封装性

Inheritence: 继承

ES5 Class Inheritence

原型链继承

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  console.log("hello, my name is " + this.name);
};
function Man() {}
Man.prototype = new Person("pursue");
var man1 = new Man();
man1.say(); //hello, my name is pursue
var man2 = new Man();
console.log(man1.say === man2.say); //true
console.log(man1.name === man2.name); //true

这种继承方式很直接,为了获取 Person 的所有属性方法 ( 实例上的和原型上的 ),直接将父类的实例 new Person('pursue') 赋给了子类的原型,其实子类的实例 man1,man2 本身是一个完全空的对象,所有的属性和方法都得去原型链上去找,因而找到的属性方法都是同一个。所以直接利用原型链继承是不现实的。

利用构造函数继承

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
man1.say(); //say is not a function

这里子类的在构造函数里利用了 apply 去调用父类的构造函数,从而达到继承父类属性的效果,比直接利用原型链要好的多,至少每个实例都有自己那一份资源,但是这种办法只能继承父类的实例属性,因而找不到 say 方法,为了继承父类所有的属性和方法,则就要修改原型链,从而引入了组合继承方式。

组合继承

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    Person.apply(this, arguments);
}
Man.prototype = new Person();
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
console.log(man1.say === man2.say);//true
man1.say(); //hello, my name is joe

需要注意的是 man1 和 man2 的实例属性其实是覆盖了原型属性,但是并没要覆盖掉原型上的 say 方法(因为它们没有),所以这里 man1.say === man2.say 依然返回 true,因而需要十分小心没有覆盖掉的原型属性,因为它是所有实例共有的。

寄生组合继承

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function () {
  console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
  Person.apply(this, arguments);
}
Man.prototype = Object.create(Person.prototype);//a.
Man.prototype.constructor = Man;//b.
var man1 = new Man('pursue');
var man2 = new Man('joe');
console.log(man1.say == man2.say); //true
console.log(man1.name == man2.name); //false

其实寄生组合继承和上面的组合继承区别仅在于构造子类原型对象的方式上(a. 和 b.),这里用到了 Object.creat(obj) 方法,该方法会对传入的 obj 对象进行浅拷贝,类似于:

function create(obj){
    function T(){};
    T.prototype = obj;
    return new T();
}

因此,a. 会将子类的原型对象与父类的原型对象进行很好的连接,而并不像一般的组合继承那样直接对子类的原型进行复制(如 Man.prototype = new Person();), 这样只是很暴力的在对属性进行覆盖。而寄生组合继承方式则对实例属性和原型属性分别进行了继承,在实现上更加合理。注意 : 代码 b. 并不会改变 instanceof 的结果,但是对于需要用到 construcor 的场景,这么做更加严谨。

function A(a){
  this.varA = a;
}

// 以上函数 A 的定义中,既然 A.prototype.varA 总是会被 this.varA 遮蔽,
// 那么将 varA 加入到原型(prototype)中的目的是什么?
A.prototype = {
  varA : null,  // 既然它没有任何作用,干嘛不将 varA 从原型(prototype)去掉?
      // 也许作为一种在隐藏类中优化分配空间的考虑?
      // https://developers.google.com/speed/articles/optimizing-javascript#Initializing instance variables
      // 将会验证如果 varA 在每个实例不被特别初始化会是什么情况。
  doSomething : function(){
    // ...
  }
}

function B(a, b){
  A.call(this, a);
  this.varB = b;
}
B.prototype = Object.create(A.prototype, {
  varB : {
    value: null,
    enumerable: true,
    configurable: true,
    writable: true
  },
  doSomething : {
    value: function(){ // override
      A.prototype.doSomething.apply(this, arguments); // call super
      // ...
    },
    enumerable: true,
    configurable: true,
    writable: true
  }
});
B.prototype.constructor = B;

var b = new B();
b.doSomething();

ES6

Babel

class Parent {

  constructor() {
    this.a = 1;
  }

}

class Child extends Parent {

}
"use strict";

var _getPrototypeOf = require("babel-runtime/core-js/object/get-prototype-of");

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn");

var _possibleConstructorReturn3 = _interopRequireDefault(
  _possibleConstructorReturn2
);

var _inherits2 = require("babel-runtime/helpers/inherits");

var _inherits3 = _interopRequireDefault(_inherits2);

var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

var Parent = function Parent() {
  (0, _classCallCheck3.default)(this, Parent);

  this.a = 1;
};

var Child = (function(_Parent) {
  (0, _inherits3.default)(Child, _Parent);

  function Child() {
    (0, _classCallCheck3.default)(this, Child);
    return (0, _possibleConstructorReturn3.default)(
      this,
      (Child.__proto__ || (0, _getPrototypeOf2.default)(Child)).apply(
        this,
        arguments
      )
    );
  }

  return Child;
})(Parent);

JavaScript 中类的声明与实例化

JavaScript is a powerful object-oriented programming (OOP) language, however, unlike many traditional programming languages, it uses a prototype-based OOP model which makes its syntax foreign to most developers. In addition, JavaScript also treats functions as first-class objects which may cause further confusion amongst developers who are not familiar with these concepts.

14.5.14 Runtime Semantics: ClassDefinitionEvaluation

class C {
  constructor() {
    // Use inner name C to refer to class
    console.log(`constructor: ${C.prop}`);
  }
  logProp() {
    // Use inner name C to refer to class
    console.log(`logProp: ${C.prop}`);
  }
}
C.prop = "Hi!";

const D = C;
C = null;

// C is not a class, anymore:
new C().logProp(); // TypeError: C is not a function
// But inside the class, the identifier C
// still works
new D().logProp(); // constructor: Hi! // logProp: Hi!
class A {
  say() {
    console.log(A);
  }
}

var B = A;
A = null;

var a = new B();
a.say();

let C = function() {};

C.prototype.say = function() {
  console.log(C);
};

var D = C;
C = null;

var c = new D();
c.say();

// [Function: A]
// null

静态方法

class Obj {

  static myMethod(msg) {

    console.log('static', msg);

  }

  myMethod(msg) {

    console.log('instance', msg);

  }

}

Obj.myMethod(1); // static 1

var instance = new Obj();

instance.myMethod(2); // instance 2

Access Self Method

class A {
  constructor(input) {
    this.tight = A.getResult(input); //this.tight = this.constructor.getResult( input )
  }

  static getResult(input) {
    return input * 2;
  }
}

let instanceA = new A(4);

console.log("A.tight", instanceA.tight); //A.tight 8

Remark

this.tight = this.constructor.getResult( input )

is as same as

this.tight = A.getResult( input )

Class

Reference

实例化

构造函数

单例模式

单例模式的根本目的在于不需要重复初始化一个类,并且在某些情况下能够使用某个对象来进行全局的状态管理,在 ES6 中要实现一个单例类也比较方便:

/*
  * Setting up block level variable to store class state
  * , set's to null by default.
*/
let instance = null;

class Cache{
    constructor() {
        if(!instance){
              instance = this;
        }

        // to test whether we have singleton or not
        this.time = new Date()

        return instance;
      }
}

要进行测试的话可以用如下方式:

 let cache = new Cache()
 console.log(cache.time);
 setTimeout(function(){
   let cache = new Cache();
   console.log(cache.time);
 },4000);

Definition

从 ECMAScript 6 开始,JavaScript 中有了类(class )这个概念。但需要注意的是,这并不是说:JavaScript 从此变得像其它基于类的面向对象语言一样,有了一种全新的继承模型。JavaScript 中的类只是 JavaScript 现有的、基于原型的继承模型的一种语法包装(语法糖),它能让我们用更简洁明了的语法实现继承。ES6 的类是一个基本的基于 Prototype 的 OO 模式,它依旧支持原型继承,并且支持父类调用、实例化、静态方法以及构造器,首先看下基本的类表达式定义:

// 匿名类表达式
var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// 命名类表达式
var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

下面看一个更全面一点的类的定义:

class SkinnedMesh extends THREE.Mesh {
  constructor(geometry, materials) {
    super(geometry, materials);

    this.idMatrix = SkinnedMesh.defaultMatrix();
    this.bones = [];
    this.boneMatrices = [];
    //...
  }
  update(camera) {
    //...
    super.update();
  }
  get boneCount() {
    return this.bones.length;
  }
  set matrixType(matrixType) {
    this.idMatrix = SkinnedMesh[matrixType]();
  }
  static defaultMatrix() {
    return new THREE.Matrix4();
  }
}

变量提升

类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会。也就是说,你必须先声明类,然后才能使用它,否则代码会抛出 ReferenceError 异常,像下面这样:

var p = new Polygon(); // ReferenceError
class Polygon {}

可见域

私有数据

如何在 ES6 中管理类的私有数据

属性

方法

构造器

constructor 方法是一个特殊的类方法,它既不是静态方法也不是实例方法,它仅在实例化一个类的时候被调用。一个类只能拥有一个名为 constructor 的方法,否则会抛出 SyntaxError 异常。在子类的构造器中可以使用 super 关键字调用父类的构造器。

原型方法

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  get area() {
    return this.calcArea();
  }

  calcArea() {
    return this.height * this.width;
  }
}

const square = new Polygon(10, 10);

console.log(square.area);

将 JSON 对象映射到 Object

constructor(data) {Object.assign(this, data);}var data = JSON.parse(req.responseText);new User(data);

静态方法

static 关键字用来定义类的静态方法。 静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法。静态方法经常用来作为工具函数。

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));

继承

/**
 * Classes and Inheritance
 * Code Example from http://www.es6fiddle.net/
 */
class Polygon {
  constructor(height, width) {
    //class constructor
    this.name = "Polygon";
    this.height = height;
    this.width = width;
  }

  sayName() {
    //class method
    console.log("Hi, I am a", this.name + ".");
  }
}

class Square extends Polygon {
  constructor(length = 10) {
    // ES6 features Default Parameters
    super(length, length); //call the parent method with super
    this.name = "Square";
  }

  get area() {
    //calculated attribute getter
    return this.height * this.width;
  }
}

let s = new Square(5);

s.sayName(); // => Hi, I am a Square.
console.log(s.area); // => 25

console.log(new Square().area); // => 100

使用 super 关键字引用父类

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

通过子类工厂实现简单的合成器

当 ES6 类继承另一个类,被继承的类可以是通过任意表达式创建的动态类:

// Function id() simply returns its parameter
const id = x => x;

class Foo extends id(Object) {}

这个特性可以允许你实现一种合成器模式,用一个函数来将一个类 C 映射到一个新的继承了C的类。例如,下面的两个函数 Storage 和 Validation 是合成器:

const Storage = Sup => class extends Sup {
    save(database) { ··· }
};
const Validation = Sup => class extends Sup {
    validate(schema) { ··· }
};

你可以使用它们去组合生成一个如下的 Employee 类:

class Person { ··· }
class Employee extends Storage(Validation(Person)) { ··· }