JavaScript里的类和继承

JavaScript与大部分客户端语言有几点明显的不同:

JS是 动态解释性语言,没有编译过程,它在程序运行过程中被逐行解释执行

JS是 弱类型语言,它的变量没有严格类型限制

JS是面向对象语言,但 没有明确的类的概念(虽然有class关键字,然而目前并没有什么卵用)

JS虽然没有类,但可以通过一些方法来模拟类以及实现类的继承。

一切皆对象,还先从对象说起。

1、对象(Object)

ECMA-262对对象的定义是:无序属性的集合,其属性可以包含基本值、对象或者函数。

直观点描述,就是由多个键值对组成的散列表。

JS创建对象的方法和其它语言大同小异:

// 通过构造函数创建

var zhangsan = new Object();

zhangsan.name = "张三";

zhangsan.age = 20;

zhangsan.sayHi = function() {

alert("Hi, I‘m " + this.name);

};

// 通过对象字面量创建

var lisi = {

name: "李四",

age: 21,

sayHi: function() {

alert("Hi, I‘m " + this.name);

}

};

当需要大量创建相同结构的对象时,可以使用 对象工厂(Object Factory):

// 对象工厂

function createPerson(name, age) {

return {

name: name,

age: age,

sayHi: function() {

alert("Hi, I‘m " + this.name);

}

};

}

var zhangsan = createPerson("张三", 20);

var lisi = createPerson("李四", 21);

但通过这种方式创建出来的实例,不能解决类型识别问题,只知道它是一个对象,但具体什么?无法判断:

zhangsan instanceof ?

lisi.constructor = ?

这时,“类”就登场了。

2、类(Class)

2.1、构造函数模式

事实上,JS中每个函数(function)本身就是一个构造函数(constructor),就是一个类:

// 构造函数模式

function Person(name, age) {

this.name = name;

this.age = age;

this.sayHi = function() {

alert("Hi, I‘m " + this.name);

};

}

var zhangsan = new Person("张三", 20);

var lisi = new Person("李四", 21);

alert(zhangsan instanceof Person); // true

alert(lisi.constructor === Person); // true

这里面其实有个问题:

alert(zhangsan.sayHi === lisi.sayHi); // false

多个实例中的同名方法并不相等,也就是说存在多个副本。而这些行为是相同的,应该指向同一个引用才对。

为了解决这个问题,JS为每个函数分配了一个 prototype(原型)属性,该属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

2.2、原型模式

原型(Prototype):指向一个对象,作为所有实例的基引用(base reference)。

// 构造函数+原型组合模式

function Person(name, age) {

this.name = name;

this.age = age;

}

Person.prototype.sayHi = function() {

alert("Hi, I‘m " + this.name);

};

var zhangsan = new Person("张三", 20);

var lisi = new Person("李四", 21);

alert(zhangsan.sayHi === lisi.sayHi); // true

在Person中,sayHi 是 原型成员(prototype),name 和 age 是 特权成员(privileged),它们都是 公共成员(public)。

注:“特权”是道格拉斯提出的名词。道格拉斯·克罗克福德(Douglas Crockford),Web界人称道爷,JSON创立者,《JavaScript语言精粹》作者,JSLint、JSMin、ADsafe开发者。

类的原型带有一个 constructor 属性,指向该类的构造函数(如果重新分配了原型指针,需要手动添加 constructor 属性);类的实例上会自动生成一个属性指向该类原型(在Chrome上可以通过“__proto__”访问到该对象,而IE上该属性则是不可见的)。

Person、Person的原型、Person的实例间的关系如下:

需要注意的是,原型成员保存引用类型值时需谨慎:

Person.prototype.friends = [];

zhangsan.friends.push("王五");

alert(lisi.friends); // ["王五"]

张三的基友莫名其妙就变成李四的基友了,所以 friends 应该添加为特权成员,而不是原型成员。

2.3、类的结构

综上所述,JS中的类的结构大致如下:

类由构造函数和原型组成

构造函数中可以声明私有成员和添加特权成员

原型中可以添加原型成员

私有成员可以被特权成员访问而对原型成员不可见

特权成员和原型成员都是公共成员

3、继承(Inherit)

在JS中继承是如何实现的呢?

3.1、拷贝继承

最简单直接的方式莫过于 属性拷贝:

// 拷贝继承

function extend(destination, source) {

for (var property in source) {

destination[property] = source[property];

}

}

extend(SubClass.prototype, SuperClass.prototype);

这种方式虽然实现了原型属性的继承,但有一个非常明显的缺陷:子类实例无法通过父类的 instanceof 验证,换句话说,子类的实例不是父类的实例。

3.2、原型继承

在 Chrome 的控制台中查看 HTMLElement 的原型,大致如下:

可以清晰看到,HTMLElement 的原型是 Element 的实例,而 Element 的原型又是 Node 的实例,从而形成了一条 原型链(Prototype-chain),JS的原生对象就是通过原型链来实现继承。

这里顺道说下解释器对实例属性的查找过程:

在特权属性中查找

特权属性中没找到,再到原型属性中查找

原型属性中没找到,再到原型的原型属性中查找

直到根原型还没找到,返回 undefined

这就说明为什么我们自定义的类明明没有声明 toString() 方法,但仍然可以访问到,因为所有对象都继承自 Object。

因此,我们也可以通过原型链来实现继承:

// 原型链继承

function User(name, age, password) {

// 继承特权成员

Person.call(this, name, age);

this.password = password;

}

// 继承原型

User.prototype = new Person();

// 修改了原型指针,需重新设置 constructor 属性

User.prototype.constructor = User;

var zhangsan = new User("张三", 20, "123456");

zhangsan.sayHi(); // Hi, I‘m 张三

运行正常,貌似没什么问题,但其实里面还是有些坑:

父类的构造函数被执行了 2 次:继承特权成员时 1 次,继承原型时又 1 次。

父类初始化两次,这有时会导致一些问题,举个例子,父类构造函数中有个alert,那么创建子类实例时,会发现有两次弹框。

不仅如此,还导致了下面的问题。从控制台中查看子类的实例,结构如下:

可以看到子类的原型中也包含了父类的特权成员(直接创建了一个父类实例,当然会有特权成员),只不过因为解释器的属性查找机制,被子类的特权成员所覆盖,只要子类的特权成员被删除,原型中相应的成员就会暴露出来:

delete zhangsan.name;

alert(zhangsan.name); // 此时访问到的就是原型中的name

那怎么办呢?对此道爷提供了一个很实用的解决方案—— 原型式寄生组合继承。

3.3、原型式寄生组合继承

我们的目的是子类原型只继承父类的原型,而不要特权成员,原理其实很简单:创建一个临时的类,让其原型指向父类原型,然后将子类原型指向该临时类的实例即可。实现如下:

function inheritPrototype(subClass, superClass) {

function Temp() {}

Temp.prototype = superClass.prototype;

subClass.prototype = new Temp();

subClass.prototype.constructor = subClass;

}

inheritPrototype(User, Person);

因为临时类的构造函数是空实现,子类在继承原型时自然不会执行到父类的初始化操作,也不会继承到一堆乱七八糟的特权成员。

再看下 zhangsan 的结构:

此时,子类实例的原型链大致如下:

总结

修改后的代码整理如下:

// 用于子类继承父类原型的工具函数

function inheritPrototype(subClass, superClass) {

function Temp() {}

Temp.prototype = superClass.prototype;

subClass.prototype = new Temp();

subClass.prototype.constructor = subClass;

}

// 父类

function Person(name, age) {

// 特权成员(每个实例都有一份副本)

this.name = name;

this.age = age;

}

// 原型成员(所有实例共享)

Person.prototype.sayHi = function() {

alert("Hi, I‘m " + this.name);

};

// 子类

function User(name, age, password) {

// 继承父类特权成员(在子类中执行父类的初始化操作)

Person.call(this, name, age);

// 添加新的特权成员

this.password = password;

}

// 继承父类原型

inheritPrototype(User, Person);

// 重写父类原型方法

User.prototype.sayHi = function() {

alert("Hi, my name is " + this.name);

};

// 扩展子类原型

User.prototype.getPassword = function() {

return this.password;

};

到此为止,我们已经比较完美地实现了类和类的继承。

但每个类、每个子类、每个子类的子类,都要这么分几步写,也是很蛋疼的。对象有对象工厂,类当然也可以搞个 类工厂(Class Factory),江湖上已有不少现成的类工厂,让我们可以从统一规范的入口来生成自定义类。

时间: 2024-12-17 05:09:42

JavaScript里的类和继承的相关文章

javaScript闭包实现类与继承(非ES6)

首先我们都知道js的一个函数定义是这样的 function func(){ //声明一个普通的函数 //省略代码 } 而没有名字的函数叫匿名函数,是长这样的 function(){ //声明一个匿名函数,一般这样声明方式是用于回调函数 //省略代码 } 或者我们习惯用一个变量来保存匿名函数,那么这个变量成为函数本身 var func = function(){ //匿名函数保存到func变量内 //省略代码 } 同样,在调用函数的时候,使用函数名或者变量名后面加上一个括号,像以下这样 func(

javascript里的继承

js里面继承的方式, 1. 类式继承,通过构造函数的继承 1 function extend(subClass, superClass){ 2 var F = function(){}; 3 F.prototype = superClass.prototype; 4 subClass.prototype = new F(); 5 subClass.prototype.constructor = subClass; 6 7 subClass.superclass = superClass.prot

详谈Javascript类与继承

本文将从以下几方面介绍类与继承 类的声明与实例化 如何实现继承 继承的几种方式 类的声明与实例化 类的声明一般有两种方式 //类的声明 var Animal = function () { this.name = 'Animal'; }; //ES6中类的声明 class Animal2 { constructor () { this.name = 'Animal2'; } } 实例化就比较简单,直接用new运算符 new Animall() new Animal2() 这些比较简单,简单介绍一

Java编程里类的继承

今天,我们将要讨论的内容是Java里面类的继承的相关概念. 说到继承,我相信大家都不陌生.生活中,子承父业,子女继承父母的财产,这就是继承.实际上,Java里的继承也是如此.对于一个类来说,它的数据成员和方法就是它的财产,而申明另一个类接收了这个类的财产,这就是Java里的继承. 接下来我将就几个方面,谈一谈继承的相关知识. 一.继承的基本格式与意义 在上文,我们了解了什么叫做继承.那么,如何使用继承? 继承的关键词是extends.继承的一般格式为:public class 类名 extend

javascript类式继承

javascript中是没有类这个概念的,但是javascript有它自己的原型机制,我们可以通过原型机制(prototype,constructor)来伪一个类出来,俗称“伪类”. 新函数对象被创建时,会被赋予一个prototype属性,它的值是一个包括constructor属性,且属性值为该新函数的对象,用代码来解释就是: Cat.prototype.constructor = Cat 介绍javascript原型机制的文章博客园有很多,可以自己找来看看哦. 构建伪类的第一步是定义一个构造器

How Javascript works (Javascript工作原理) (十五) 类和继承及 Babel 和 TypeScript 代码转换探秘

个人总结:读完这篇文章需要15分钟,文章主要讲解了Babel和TypeScript的工作原理,(例如对es6 类的转换,是将原始es6代码转换为es5代码,这些代码中包含着类似于 _classCallCheck 和 _createClass这样的函数,而这些函数已经在Babel和TypeScript的标准库中预先定义好了,然后进行处理). 顺便温习了Object.create这个方法,  比如有一个obj:{name:'是ho',f:function(){alert(1)}} var a = O

JavaScript是如何工作的:深入类和继承内部原理 + Babel和TypeScript之间转换

现在构建任何类型的软件项目最流行的方法这是使用类.在这篇文章中,探讨用 JavaScript 实现类的不同方法,以及如何构建类的结构.首先从深入研究原型工作原理,并分析在流行库中模拟基于类的继承的方法. 接下来是讲如何将新的语法转制为浏览器识别的语法,以及在 Babel 和 TypeScript 中使用它来引入ECMAScript 2015类的支持.最后,将以一些在 V8 中如何本机实现类的示例来结束本文. 概述 在 JavaScript 中,没有基本类型,创建的所有东西都是对象.例如,创建一个

javascript“类”与继承总结和回顾-韩烨

Javascipt语法不支持"类"(class)[es6已经支持],但是有模拟类的方法.今天我主要谈谈Javascipt中模拟"类"的方法及js中继承的总结和回顾. js中实现"类"与继承,既是重点,又是难点.很多同学可能都对js中"类"与继承都有所了解,但是深入剖析的时候,感觉力不从心.模棱两可. 下面我们一起来总结一下,巩固提高一下js的基础知识.关于js的基础知识,我在之前写过一个关于js老生常谈之this,constr

精读JavaScript模式(八),JS类式继承

一.前言 这篇开始主要介绍代码复用模式(原书中的第六章),任何一位有理想的开发者都不愿意将同样的逻辑代码重写多次,复用也是提升自己开发能力中重要的一环,所以本篇也将从“继承”开始,聊聊开发中的各种代码复用模式. 其实在上一章,我感觉这本书后面很多东西是我不太理解的,但我还是想坚持读完,在以后知识逐渐积累,我会回头来完善这些概念,算是给以前的自己答疑解惑. 二.类式继承VS现代继承模式 1.什么是类式继承 谈到类式继承或者类classical,大家都有所耳闻,例如在java中,每个对象都是一个指定