这是我参与8月更文挑战的第11天,活动详情查看: 8月更文挑战”
前言
由浅入深,深入浅出。梳理一下JavaScript类与继承。
什么是原型
在不同的编程语言中,设计者也利用各种不同的语言特性来抽象描述对象。最为成功的流派是使用“类”的方式来描述对象,这诞生了诸如 C++、Java 等流行的编程语言。这个流派叫做基于类的编程语言。
JavaScript是基于原型的编程语言,它们利用原型来描述对象。
基于类:
在这类语言中,总是先有类,再从类去实例化一个对象。类与类之间又可能会形成继承、组合等关系。类又往往与语言的类型系统整合,形成一定编译时的能力。
基于原型:
关注对象实例的行为,划分到最近的使用方式相似的原型对象。
基于原型的面向对象系统,通过“复制”的方式来创建新对象。
( 原型:一系列对象行为的集合。原型更强调的是行为。)
在 JavaScript 之前,原型系统就更多与高动态性语言配合,并且多数基于原型的语言提倡运行时的原型修改。
原型系统的“复制操作”有两种实现思路:
一个是并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用;
另一个是切实地复制对象,从此两个对象再无关联。
JavaScript采用的是第一个。
JavaScirpt原型
整体上JavaScript1.1提出了原型相关概念,1.2引入了__proto__伪属性,ES3规范中删除了伪属性之后就没有改动了。
JavaScript 1.1 原型的实例
// 定义出作为方法被使用的函数
function ptSum(pt2) {
return new Point(this.x + pt2.x, this.y + pt2.y);
}
function ptDistance(pt2) {
return Math.sqrt(Math.pow(pt2.x - this.x, 2) + Math.pow(pt2.y - this.y, 2));
}
// 定义 Point 构造函数
function Point(x, y) {
// 创建并初始化新对象的数据属性
this.x = x;
this.y = y;
}
// 添加方法到共享的原型对象
Point.prototype.sum = ptSum;
Point.prototype.distance = ptDistance;
var origin = new Point(0, 0); // 创建 Point 对象
复制代码
原型的特点:
- 访问对象属性时,如果这个属性的名称在「与对象构造函数相关联的原型」上已被定义,那么将返回原型对象的属性值。
- 对原型对象属性的添加或修改,对于通过「与原型相关联的构造函数」创建的现有对象,是立即可见的。
- 为对象属性赋值时,会遮盖在「与对象构造函数相关联的原型」上定义的同名属性值。
————————————————————————————————
ES6中,为我们提供了一系列内置函数,以便更为直接地访问操纵原型:
-
Object.create 根据指定的原型创建新对象,原型可以是 null;
-
Object.getPrototypeOf 获得一个对象的原型;
-
Object.setPrototypeOf 设置一个对象的原型。
// ES6中 原型API相关案例
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();
// 我们可以通过“原始猫对象”和“原始虎对象”来控制所有猫和虎的行为。console.log(Object.getPrototypeOf(anotherCat) === cat);
console.log(Object.getPrototypeOf(anotherTiger) === cat);
console.log(Object.getPrototypeOf(anotherTiger) === tiger);
PS:关于__proto__伪属性,参考MDN文档,不推荐使用,逐步被淘汰。这里就不花过多的精力去探讨。可能大多数博客资料都还有这个__proto__伪属性的身影。根据自身需要学习吧。
类与继承
通过new 构造函数创建对象,通过prototype共享方法和属性。
ES6之前
在没有ES6中的Object.create、Object.setPrototypeOf时,我们只能通过new 和 构造函数 去指定对象的原型。
// 通过new实现的类与继承
function c1(){
// 直接在构造器中修改 this,给 this 添加属性
this.p1 = 1;
this.p2 = function(){
console.log(this.p1);
}
}
var o1 = new c1;
o1.p2();// 1
function c2(){
}
// 修改构造器的 prototype 属性指向的对象,它是从这个构造器构造出来的所有对象的原型
c2.prototype.p1 = 1;
c2.prototype.p2 = function(){
console.log(this.p1);
}
var o2 = new c2;
o2.p2(); // 1
复制代码
ES6中的类
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
复制代码
ES6中的继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
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.');
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
复制代码
PS:
怀着忐忑的心理,梳理类与集成。因为这一块并没有多少实战经验作为基础,所以必然要举一些实例,并且围绕这些实例展开梳理。一方面是知识上的梳理,一方面也是案例上的梳理。
在这之前,还是得有思路上的梳理,以免造成内容上的堆砌。JavaScript1.0是没有继承这个概念的,是基于对象的一门语言,之后1.1函数对象有了prototype属性,再之后1.2才引入了__proto__伪属性。
ES3并没有相关的修改,ES5函数prototype属性的bind方法更新了,对象的Setter,getter,原操作,ES6(ECMAScript2015)正式引入了类,把JavaScript类与继承进行了封装,并且之后再没有较大的更新。
总体来看,核心内容还是JavaScirpt对象、函数、原型链、以及类与继承这些内容。单从年限来看,学习资料应该是很多的。重点还是放在原型链和类与集成的实现上,找到合适的案例,加深理解。
本文挑选了一些简单的案例,做一个简单的梳理,主要是熟悉JavaScirpt中类与继承的核心概念。稳扎稳打把,连续查阅资料,整个人都有些许浮躁,先把这些最简单的基建打好,再往深处探寻。