JS:面向对象(进阶篇)
组合使用构造函数和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享这对方法的引用,最大限度的节省了内存。
function Person (name,age) {
this.name = name;
this.age = age;
this.friends = ['cjh','csb'];
}
Person.prototype = {
constructor:Person,
sayName:function () {
console.log(this.name);
}
}
let person1 = new Person('aaa',22);
let person2 = new Person('bbb',26);
person1.friends.push('zxf');
// [ 'cjh', 'csb', 'zxf' ]
console.log(person1.friends);
// [ 'cjh', 'csb' ]
console.log(person2.friends);
// false
console.log(person1.friends === person2.friends);
//true
console.log(person1.sayName === person2.sayName);
我们已经达到目的了,这种构造函数与原型混成的模式,是目前用的比较广泛的。
稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其他方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境会禁止使用this和new),看个例子:
function Person (name) {
let o = new Object();
o.sayName=function(){
console.log(name);
}
return o;
}
let f = Person('cjh');
// cjh
f.sayName();
// undefined
console.log(f.name);
这种其实很少用,就是把函数的参数当作Person的属性,只有函数内部才能访问。
原型链
终于讲到最终BOSS了,原型链可谓难到一大部分刚入门JS的同学(我也是), 原型链主要作用之一就是继承,我们来看个例子:
function SuperType () {
console.log('Super函数被执行')
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
console.log('getSuperValue函数被执行了');
return this.property;
}
function SubType () {
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subProperty;
}
let instance = new SubType();
console.log(instance.getSuperValue());
运行结果:
Super函数被执行
getSuperValue函数被执行了
true
这是实现继承的一种基本模式,定义两个类型SuperType和SubType,每个类型都有一个属性和一个方法,要实现subType继承SuperType,通过创建SuperType实例并赋值给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
上图来自高级JS程序设计
这里我想说的是,还有种继承的方法,还更安全点,Object.create
上面的运行结果也看出来了,SuperType里面的代码被执行了,这就是new的能力,new的作用其实就是:
//new的时候做了什么
//会执行Base里面的代码
var o1 = new Base();
o1.[[Prototype]] = Base.prototype;
Base.call(o1);
而Object.create做了些什么:
/Object.create = function (o) {
var F = function () {};
//没有执行o函数里面的代码
F.prototype = o;
return new F();
};
所以上面的继承我们可以改为:
function SuperType () {
console.log('Super函数被执行了');
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
console.log('getSuperValue函数被执行了');
return this.property;
}
function SubType () {
this.subProperty = false;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getSubValue = function () {
return this.subProperty;
}
let instance = new SubType();
console.log(instance.getSuperValue());
运行结果:
getSuperValue函数被执行了
undefined
因为没有执行SuperType里面的函数,当这个函数里面要是创建对象和返回对象就会造成内存泄漏,所有this.property没有被声明和赋值,返回undefined,但是继承了所有方法和没有在函数里面声明的属性(在外面声明的):
//这个函数没有被执行到
function SuperType () {
console.log('Super函数被执行了');
this.property = true;
}
//这个函数有被执行到
SuperType.prototype.getSuperValue = function () {
console.log('getSuperValue函数被执行了');
return this.property;
}
SuperType.prototype.name = 'cjh';
function SubType () {
this.subProperty = false;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getSubValue = function () {
return this.subProperty;
}
let instance = new SubType();
//undefined
console.log(instance.property)
//cjh
console.log(instance.name);
//[Function: SuperType]
console.log(SuperType.prototype.constructor);
//[Function: SuperType]
console.log(SubType.prototype.constructor);
我们看最后两个结果:都是[Function: SuperType],实际上,我们没有改变SubType.prototype的constructor的指向,还记得在JS面向对象(基础篇)里面讲过,默认情况下,所以原型对象都会自动获得一个constructor(构造函数)属性,并且constructor包含一个指向prototype属性所在函数的指针,我们改变了SubType.prototype=SuperType.prototype,所有construtor就自动指向了SuperType。
原型链的问题
function SuperType () {
this.colors = ['red','green'];
}
function SubType () { }
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
// [ 'red', 'green', 'black' ]
console.log(instance1.colors);
let instance2 = new SubType();
// [ 'red', 'green', 'black' ]
console.log(instance2.colors);
在基本篇的时候讲过用原型模式来创建对象有个很大的问题:就是会共享属性,这个是我们不想看到的,因为每个对象都应该有它自己的一块内存,所有那时我们用组合模式来解决那个问题(就是在构造函数里面定一个各个对象的属性),一样的,现在也可以用组合继承来解决这个问题:
//原型链的组合继承
function SuperType (name) {
console.log('调用了Supertype');
this.name = name;
this.colors = ['red','green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name) {
//继承属性
//给每个实例分配自己的属性地址
SuperType.call(this, name);
}
//第一次调用SuperType();
SubType.prototype = new SuperType();
//第二次调用SuperType(); 因为SubType函数里面的SuperType.call();
let instance1 = new SubType('cjh',22);
//第三次调用SuperType();因为SubType函数里面的SuperType.call();
let instance2 = new SubType('csb',24);
instance1.colors.push('black');
// [ 'red', 'green', 'black' ]
console.log(instance1.colors);
// [ 'red', 'green' ]
console.log(instance2.colors);
// cjh
instance1.sayName();
// csb
instance2.sayName();
这样是可以解决我们的问题,但是再看下结果:
调用了Supertype
调用了Supertype
调用了Supertype
[ 'red', 'green', 'black' ]
cjh
csb
上面的SuperType被执行了三次,但我们就创建了两个对象,第一次纯属多余,要想帮法去掉,在上面讲过我们用new关键字时,JS到时做了什么:
//new的时候做了什么
//会执行Base里面的代码
var o1 = new Base();
o1.[[Prototype]] = Base.prototype;
Base.call(o1);
看到了call这个函数,就是调用Base()并且把o1传进去,但是这里的call没有任何作用,因为我们还没有创建对象时,它就call了。有个终极蛇皮版:
//subType:子类 superType:父类 => subType继承于superType
function inheritPrototype (subType, superType) {
let prototype = Object.create(superType.prototype);
//因为重写了subType的原型而失去的默认的constructor,所以指回subType
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType (name) {
console.log('调用了Supertype构造函数');
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name,age) {
console.log('调用了SupType构造函数');
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
}
let instance1 = new SubType('cjh',22);
let instance2 = new SubType('csb',25);
instance1.colors.push('black');
// [ 'red', 'blue', 'green', 'black' ]
console.log(instance1.colors);
// [ 'red', 'blue', 'green' ]
console.log(instance2.colors);
// true
console.log(instance1.sayName === instance2.sayName);
// true
console.log(instance1.sayAge === instance2.sayAge);
//false
console.log(instance1.name === instance2.name);
//
instance1.sayAge();
//
instance2.sayAge();
// cjh
instance1.sayName();
// csb
instance2.sayName();
// true
console.log(instance1 instanceof SubType);
//[Function: SubType],要是没写prototype.constructor = subType;,
//结果就是:[Function: SubType],亲测
// [Function: SubType]
console.log(instance1.constructor);
运行结果:
调用了SupType构造函数
调用了Supertype构造函数
调用了SupType构造函数
调用了Supertype构造函数
现在创建一个对象分别调用一次SuperType和SubType,看上面的结果,不管是父类的方法还是子类的方法是共享的,然后属性却都是自己的内存里面的,这很符合我们的要求,即方便又省内存,到此,就差不多了,有错的话欢迎指正
JS:面向对象(进阶篇)的更多相关文章
- Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)
Python开发[第七篇]:面向对象 详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)> ...
- Python 面向对象 (进阶篇)
<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使用(可 ...
- Java面向对象进阶篇(包装类,不可变类)
一. Java 8的包装类 Java中的8种基本数据类型不支持面向对象的变成机制,也不具备对象的特性:没有成员变量,方法可以调用.为此,Java为这8 种基本数据类型分别提供了对应的 包装类(Byte ...
- Java面向对象进阶篇(抽象类和接口)
一.抽象类 在某些情况下,父类知道其子类应该包含哪些方法,但是无法确定这些子类如何实现这些方法.这种有方法签名但是没有具体实现细节的方法就是抽象方法.有抽象方法的类只能被定义成抽象类,抽象方法和抽象类 ...
- Java面向对象进阶篇(内部类)
一. 概念 大部分时候,类被定义成一个独立的程序单元.有时候把一个类放在另一个类内部定义,这个类被称为内部类,包含内部类的类也被称为外部类. 内部类的主要作用: 内部类提供良好的封装,可以把内部类隐藏 ...
- python之路 面向对象进阶篇
一.字段 字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同, 普通字段属于对象 静态字段属于类 class Province: # 静态字段 countr ...
- python 面向对象(进阶篇)
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- 【转】python 面向对象(进阶篇)
[转]python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 ...
- python 面向对象(进阶篇)转载武沛齐
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- Python基础—面向对象(进阶篇)
通过上一篇博客我们已经对面向对象有所了解,下面我们先回顾一下上篇文章介绍的内容: 上篇博客地址:http://www.cnblogs.com/phennry/p/5606718.html 面向对象是一 ...
随机推荐
- 01java基础笔记
计算机组成:运算器,控制器,存储器,输入输出设备(外部设备I/O设备) 机器语言:机器语言,汇编语言,高级语言 人机交互:命令行方式,图形化界面交互方式 JAVA语言平台分为:J2SE,J2ME,J2 ...
- 引入iframe, 头部跳转并点亮效果
<script> /** * @Author: zhangcs * @Date: 2018-09-20 * @cnblogs: https://www.cnblogs.com/zhangc ...
- C++ STL rope 可持久化平衡树 (可持久化数组)
官方文档好像 GG 了. rope 不属于标准 STL,属于扩展 STL,来自 pb_ds 库 (Policy-Based Data Structures). 基本操作: #include <e ...
- apiDoc部署搭建
apiDoc介绍: 目前,越来越多的项目进行前后端分离,那么就有需要比较规范的来管理API文档.而apiDoc这个API文档管理器,可以根据你项目代码的注释来进行自动生成API文档.同时支持C#.Ja ...
- 在Ubuntu中安装配置java后运行java -version时提示二进制文件不能执行
因为jdk安装包有问题,试试32位的
- postgresql 取出分组中最大的几条数据
WITH Name AS ( SELECT * FROM ( SELECT xzqdm, , ) xzdm, COUNT (*) sl FROM sddltb_qc WHERE xzqdm ') GR ...
- ubuntu18.4 搭建lamp环境
一.Apache2 web服务器的安装: 可以先更新一下服务器(可选) 1.sudo apt update # 获取最新资源包 2.sudo apt upgrade ...
- Sed的查,删,增,改
sed的查,删,增,改 1.sed的查找 2.sed的删除 3.sed的上下左右增加文件内容 4.sed的改
- 前后台 工作切换---------------Linux 任务管理器(一)
继续下一章... 发现了一个好东东.就是前后台的切换.例如我们现在要vim一个文件.然后又要查找一些命令的时候,以前不知道,都是退出后,查完了,在vim进入.现在我们可以将该vim拿到后台,然后查完了 ...
- java中的final关键字的用法
一. 什么是final关键字? final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变 ...