JavaScript 继承小记
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class(ES6 引入了class 语法),而是通过“原型对象”(prototype)实现。那么在JS中常见的继承方式有几种呢?
首先我们先来通过 es5 里的方法做一个类,即构造方法,如下:
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
// 通过类添加属性和静态方法
Person.city = "北京";
Person.eat = function () {
console.log(this.name + "在吃")
};
// 实例化 Person
var p = new Person();
p.run(); // 张三在运动
p.work(); // 张三在工作
// p.eat(); // 报错 p.eat is not a function
Person.eat(); // Person在吃 // this 指向发生改变,为全局,该 name 不是 Person 类中定义的 name
在上面的代码中,我们根据构造函数方法创建了一个 Person 类,并通过内部定义参数方法,原型链扩展属性和方法,通过类添加属性和方法。接下来我们就根据这个 Person 类来实现集中继承的方法。
1、对象冒充实现继承
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
function Student() {
Person.call(this);
}
var s = new Student();
console.log(s.name); // 张三
s.run(); // 张三在运动
/**
* 对象冒充继承不能实现
* 父类原型链上的属性和方法
*/
console.log(s.sex); // undefined
s.work(); // 报错 s.work is not a function
在上面的代码中,我们可以通过对象冒充的方法进行继承,但是原型链上的属性和方法是不能被继承的。
2、原型链继承
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
// 通过类添加属性和静态方法
Person.city = "北京";
Person.eat = function () {
console.log(this.name + "在吃")
};
function Student() {
}
Student.prototype = new Person();
var s = new Student();
console.log(s.name); // 张三
s.run(); // 张三在运动
console.log(s.sex); // 男
s.work(); // 张三在工作
在上面的代码中,我们通过原型链的方法进行继承,这样构造方法内的方法和原型链上的方法我们都能继承了,看似很不错,但是其实存在着一些问题,如下:
function Person(name,age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三",18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name,age) {
}
Student.prototype = new Person();
var s = new Student("李四", '20');
console.log(s.name); // undefined
s.run(); // undefined在运动
console.log(s.sex); // 男
s.work(); // undefined在工作
在上面的代码中,我们可以看出,通过原型链继承,在实例化子类的时候没法给父类进行传参。
3、原型链+对象冒充组合继承
function Person(name, age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三", 18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name, age) {
Person.call(this, name, age); // 对象冒充
}
Student.prototype = new Person();
var s = new Student("李四", '20');
console.log(s.name); // 李四
s.run(); // 李四在运动
console.log(s.sex); // 男
s.work(); // 李四在工作
上面的代码中我们通过原型链继承+对象冒充的组合方式实现了继承,不过也存在缺点就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。
4、组合继承优化
function Person(name, age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三", 18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name, age) {
Person.call(this, name, age); // 对象冒充
}
/**
* 可以只继承 Person 的原型链
* 因为上面已经通过对象冒充继承了 Person 构造方法
*/
Student.prototype = Person.prototype;
var s = new Student("李四", '20');
console.log(s.name); // 李四
s.run(); // 李四在运动
console.log(s.sex); // 男
s.work(); // 李四在工作
console.log(s instanceof Student, s instanceof Person); //true true
console.log(s.constructor); //Person
这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。
但是这种方法没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。
5、组合继承继续优化
function Person(name, age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三", 18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name, age) {
Person.call(this, name, age); // 对象冒充
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
var s = new Student("李四", '20');
console.log(s.name); // 李四
s.run(); // 李四在运动
console.log(s.sex); // 男
s.work(); // 李四在工作
console.log(s instanceof Student, s instanceof Person); // true true
console.log(s.constructor); // Student
在上面的代码中,我们通过借助原型可以基于已有的对象来创建对象, var B = Object.create(A) 以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
同样的,Student 继承了所有的 Person 原型对象的属性和方法。目前来说,最完美的继承方法!
上面的代码我们都是基于 ES5 的特性进行的继承,在 ES6 中为我们提供了 class 类来帮助我们更快更好的实现继承。
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.call(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
class Person {
//调用类的构造方法
constructor(name, age) {
this.name = name;
this.age = age;
}
//定义方法
run() {
console.log(this.name + "在运动");
}
}
let p = new Person('张三', 18);
console.log(p); // Person { name: '张三', age: 18 }
//定义一个子类
class Student extends Person {
constructor(name, age) {
//通过super调用父类的构造方法
super(name, age)
}
//在子类自身定义方法
work() {
console.log(this.name + "在工作");
}
}
let s = new Student('李四', 18);
console.log(s); // Student { name: '李四', age: 18 }
s.run(); // 李四在运动
s.work(); // 李四在工作
上面通过 class 关键字所创建的类的继承简单易懂,是未来 JS 的发展方向。
JavaScript 继承小记的更多相关文章
- javascript继承的三种模式
javascript继承一般有三种模式:组合继承,原型式继承和寄生式继承: 1组合继承:javascript最为广泛的继承方式通过原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承,同 ...
- javascript继承机制的设计思想(ryf)
我一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例" ...
- 【读书笔记】javascript 继承
在JavaScript中继承不像C#那么直接,C#中子类继承父类之后马上获得了父类的属性和方法,但JavaScript需要分步进行. 让Brid 继承 Animal,并扩展自己fly的方法. func ...
- 图解JavaScript 继承
JavaScript作为一个面向对象语言,可以实现继承是必不可少的,但是由于本身并没有类的概念(不知道这样说是否严谨,但在js中一切都类皆是对象模拟)所以在JavaScript中的继承也区别于其他的面 ...
- JavaScript强化教程——Cocos2d-JS中JavaScript继承
javaScript语言本身没有提供类,没有其它语言的类继承机制,它的继承是通过对象的原型实现的,但这不能满足Cocos2d-JS引擎的要求.由于Cocos2d-JS引擎是从Cocos2d-x演变而来 ...
- [原创]JavaScript继承详解
原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...
- javascript继承(六)—实现多继承
在上一篇javascript继承—prototype最优两种继承(空函数和循环拷贝)(3) ,介绍了js较完美继承的两种实现方案,那么下面来探讨一下js里是否有多继承,如何实现多继承.在这里可以看看j ...
- javascript继承(五)—prototype最优两种继承(空函数和循环拷贝)
一.利用空函数实现继承 参考了文章javascript继承—prototype属性介绍(2) 中叶小钗的评论,对这篇文章中的方案二利用一个空函数进行修改,可以解决创建子类对象时,父类实例化的过程中特权 ...
- javascript继承(四)—prototype属性介绍
js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向 ...
随机推荐
- 2018-6-24-WPF-使用RPC调用其他进程
title author date CreateTime categories WPF 使用RPC调用其他进程 lindexi 2018-06-24 14:41:29 +0800 2018-2-13 ...
- Python--day22--面向对象的交互
Python里面自带的类和对象: 类名的作用: 类里面的与属性相关的对象self的运用: 实例化:就是创建一个对象 调用方法,类名.方法名(对象名) 执行步骤: 简写:alex.walk()等价于Pe ...
- 多线程:“对象当前正在其他地方使用”如何解决 system.drawing
使用这个委托,在拥有此控件的基础窗口句柄的线程上执行指定委托 this.Invoke(new Action(() => { node.SetValues(values); }));
- java基本数据类型和包装类相互转换
把基本数据类型 → 包装类: 通过对应包装类的构造方法实现 除了Character外,其他包装类都可以传入一个字符串参数构建包装类对象. 包装类 → 基本数据类型 包装类的实例方法xxxValue() ...
- laravel-admin新手的使用
1.添加页面 配置好laravel-admin的模板后 点击管理员管理里的菜单列表,输入如下信息即可 提交之后刷新页面,左侧菜单就会显示新增的广告管理的标签 2.定义路由 配置好前端的页面显示之后就要 ...
- 【js】vue 2.5.1 源码学习 (十) $mount 挂载函数的实现
大体思路(九) 本节内容: 1. $mount 挂载函数的实现. // 将Vue.prototype.$mount 缓存下来 ==>mountComponet(this,el) { // 组建挂 ...
- 12563 - Jin Ge Jin Qu hao——[DP递推]
(If you smiled when you see the title, this problem is for you ^_^) For those who don’t know KTV, se ...
- P1034 台阶问题一
题目描述 有 \(N\) 级的台阶,你一开始在底部,每次可以向上迈最多2级台阶(最少1级),问到达第 \(N\) 级台阶有多少种不同方式. 输入格式 一个正整数 \(N(\le 20)\) . 输出格 ...
- H3C IP地址与子网掩码
- H3C 端口接入控制方式