JavaScript 本身就是面向对象的,这些“模拟面向对象”,实际上做的事情就是“模拟
基于类的面向对象”。JavaScript 创始人 Brendan Eich 在“原型运行时”的基础上引入了 new、this 等语言特性,使之“看起来语法更像 Java”,而 Java 正是基于类的面向对象的代表语言之一。
在不同的编程语言中,设计者也利用各种不同的语言特性来抽象描述对象。
1、最为成功的流派是使用“类”的方式来描述对象,这诞生了诸如 C++、Java 等流行的编程
语言。这个流派叫做基于类的编程语言。2、还有一种就是基于原型的编程语言,它们利用原型来描述对象。我们的 JavaScript 就是其中代表。
“基于类”的编程:
“基于类”的编程提倡使用一个关注分类和类之间关系开发模型。在这类语言中,总是先有
类,再从类去实例化一个对象。类与类之间又可能会形成继承、组合等关系。类又往往与语
言的类型系统整合,形成一定编译时的能力。
“基于原型”的编程:
“基于原型”的编程看起来更为提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象,划分到最近的使用方式相似的原型对象,而不是将它们分成类。
基于原型的面向对象系统通过“复制”的方式来创建新对象。
一些语言的实现中,还允许复制一个空对象。这实际上就是创建一个全新的对象。
原型系统的“复制操作”有两种实现思路:
1、一个是并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用;
2、另一个是切实地复制对象,从此两个对象再无关联。
历史上的基于原型语言因此产生了两个流派,显然,JavaScript 显然选择了前一种方式。
JavaScript 的原型:
原型系统可以说相当简单,我可以用两条概括:
1、如果所有对象都有私有字段 [[prototype]],就是对象的原型;
2、读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。
从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:
Object.create 根据指定的原型创建新对象,原型可以是 null;
Object.getPrototypeOf 获得一个对象的原型;
Object.setPrototypeOf 设置一个对象的原型。//下面的代码展示了用原型来抽象猫和虎的例子。 //创建一个“猫”对象 var cat = { say(){ console.log("meow~"); }, jump(){ console.log("jump"); } } //根据猫做了一些修改创建了虎 var tiger = Object.create(cat, { say:{ writable:true, configurable:true, enumerable:true, value:function(){ console.log("roar!"); } } }) //可以用Object.create 来创建另外的猫和虎对象 //我们可以通过“原始猫对象”和“原始虎对象”来控制所有猫和虎的行为。 var anotherCat = Object.create(cat); anotherCat.say(); var anotherTiger = Object.create(tiger); anotherTiger.say();
new操作具体做了哪些事情:
new 运算接受一个构造器和一组调用参数,实际上做了几件事:
1、以构造器的 prototype 属性(注意与私有字段 [[prototype]] 的区分)为原型,创建新
对象;
2、将 this 和调用参数传给构造器,执行;
3、如果构造器返回的是对象,则返回,否则返回第一步创建的对象。new 这样的行为,它客观上提供了两种方式,
一是在构造器中添加属性,
二是在构造器的 prototype 属性上添加属性。
下面代码展示了用构造器模拟类的两种方法:
//第一种方法是直接在构造器中修改 this,给 this 添加属性。 function c1(){ this.p1 = 1; this.p2 = function(){ console.log(this.p1); } } var o1 = new c1; o1.p2(); //第二种方法是修改构造器的 prototype 属性指向的对象 //它是从这个构造器构造出来的所有对象的原型。 function c2(){ } c2.prototype.p1 = 1; c2.prototype.p2 = function(){ console.log(this.p1); } var o2 = new c2; o2.p2();
ES6 中的类——class:
类的基本写法:
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } // Getter get area() { return this.calcArea(); } // Method calcArea() { return this.height * this.width; } }
我们通过 get/set 关键字来创建 getter,通过括号和大括号来创建方法,数据型成员最好
写在构造器里面。
类提供了继承能力。
//创造了 Animal 类 class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } //通过 extends 关键字让 Dog 继承 Animal class Dog extends Animal { constructor(name) { super(name); // call the super class constructor and pass in the name parameter } speak() { console.log(this.name + ' barks.'); } } //调用子类的 speak 方法获取了父类的 name。 let d = new Dog('Mitzie'); d.speak(); // Mitzie barks.
所以当我们使用类的思想来设计代码时,应该尽量使用 class 来声明类,而不是用旧语法,
拿函数来模拟对象。