【JavaScript 16】对象继承 原型对象属性 原型链 构造函数属性 instanceof运算符 继承 多重继承 模块

对象继承

      • 原型对象概述
      • instanceof运算符
      • 构造函数的继承
      • 多重继承
      • 模块

A 对象通过继承 B 对象,就能 直接拥有 B 对象的所有属性和方法(利于代码复用)
大部分面向对象的编程语言都是通过类(class)实现对象的继承
传统上JavaScript 语言的继承不通过 class,而是通过**原型对象(prototype)**实现
ES6引入了基于class的继承

下面是JS原型链继承相关笔记

原型对象概述

1 构造函数缺点
JS通过构造函数生成新的对象
因此构造函数可以视为对象的模版(实例对象的属性和方法可以定义在构造函数内部)

function Cat (name, color) {this.name = name;this.color = color;
}var cat1 = new Cat('LH', 'White');
cat1.name // 'LH'
cat1.color // 'White' 

上面代码中Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象(上例是cat1)都会生成这两个属性,即这两个属性会定义在实例对象上面

通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点是:
同一个构造函数的多个实例之间无法共享属性从而造成对系统资源的浪费

function Cat(name, color) {this.name = name;this.color = color;this.meow = function () {console.log('miao');};
}var cat1 = new Cat('LH', 'White');
var cat2 = new Cat('EH', 'Black');cat1.meow === cat2.meow
// false

上面代码中,cat1和cat2是同一个构造函数的两个实例都具有meow方法
由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次
也即每新建一个实例就会新建一个meow方法
这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享
这种缺点的解决方法即是JS的原型对象(prototype)

2 prototype 属性作用

JS继承机制的设计思想是原型对象的所有属性和方法,都能被实例对象共享

也即若属性和方法定义在原型上则所有的实例对象都可以共享,从而节省内存并且体现实例对象之间的联系

下面是为对象指定原型
JS规定每个函数都有一个prototype属性用于指向一个对象

function f() {}
typeof f.prototype // "object"

上面代码中,函数f默认具有prototype属性,指向一个对象
对于普通函数来说,该属性基本无用
但是对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型

function Animal(name) {this.name = name;
}
Animal.prototype.color = 'white';var cat1 = new Animal('LH');
var cat2 = new Animal('EH');cat1.color // 'white'
cat2.color // 'white'

上面代码中构造函数Animal的prototype属性,就是实例对象cat1和cat2的原型对象
原型对象上添加一个color属性则实例对象都共享该属性

原型对象的属性不是实例对象自身的属性
只要修改原型对象,变动就立刻会体现在所有实例对象上

Animal.prototype.color = 'yellow';cat1.color // "yellow"
cat2.color // "yellow"

原型对象的color属性的值变为yellow,两个实例对象的color属性立刻跟着变了
这是因为实例对象其实没有color属性,都是读取原型对象的color属性
也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法(否则直接使用本身的属性和方法)
这就是原型对象的美妙之处

cat1.color = 'black';cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow'

总结一下原型对象的作用,就是定义所有实例对象共享的属性和方法
这也是被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象

Animal.prototype.walk = function () {console.log(this.name + ' is walking');
};

上面代码中Animal.prototype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用

3 原型链
JavaScript 规定所有对象都有自己的原型对象(prototype)
一方面任何一个对象都可以充当其他对象的原型
另一方面由于原型对象也是对象,所以它也有自己的原型
由此形成一个原型链(prototype chain)

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype(也即Object构造函数的prototype属性
所有对象都继承Object构造函数的prototype属性
这是所有对象都有valueOftoString方法的原因,因为都是从Object.prototype继承的

而Object.prototype对象的原型是null
null没有任何属性与方法也没有自己的原型
Object.getPrototypeOf方法返回参数对象的原型

Object.getPrototypeOf(Object.prototype)
// null

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找
如果直到最顶层的Object.prototype还是找不到,则返回undefined

如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做覆盖(overriding)

注意一级级向上在整个原型链上寻找某个属性对性能是有影响的

举例来说,如果让构造函数的prototype属性指向一个数组,就意味着实例对象可以调用数组方法

var MyArray = function () {};MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

上面代码中,mine是构造函数MyArray的实例对象
由于MyArray.prototype指向一个数组的实例使得mine可以调用数组方法(这些方法定义在数组实例的prototype对象上面)
最后那行instanceof表达式,用来比较一个对象是否为某个构造函数的实例,结果就是证明mine为Array的实例

4 constructor属性
prototype对象有constructor属性默认指向prototype对象所在的构造函数

function P() {}
P.prototype.constructor === P // true

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承

function P() {}
var p = new P();p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false

上面代码中,p是构造函数P的实例对象,但是p自身没有constructor属性该属性其实是读取原型链上面的P.prototype.constructor属性

constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的

function F() {};
var f = new F();f.constructor === F // true
f.constructor === RegExp // false 

上面代码中constructor属性确定了实例对象f的构造函数是F,而不是RegExp

此外由于constructor属性我们可以从一个实例对象新建另一个实例

function Constr() {}
var x = new Constr();var y = new x.constructor();
y instanceof Constr // true

上面代码中x是构造函数Constr的实例,可以从x.constructor间接调用构造函数
这也使得在实例方法中调用自身的构造函数成为可能

Constr.prototype.createCopy = function () {return new this.constructor();
};

上面代码createCopy方法调用构造函数新建另一个实例

constructor属性表示原型对象构造函数之间的关联关系
如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错

function Person(name) {this.name = name;
}Person.prototype.constructor === Person // truePerson.prototype = {method: function () {}
};Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true

上面代码中,构造函数Person的原型对象改掉了,但是没有修改constructor属性
导致这个属性不再指向Person
由于Person的新原型是一个普通对象,而普通对象的constructor属性指向Object构造函数
导致Person.prototype.constructor变成了Object

修改原型对象时,一般要同时修改constructor属性的指向

// 坏的写法
C.prototype = {method1: function (...) { ... },// ...
};// Zane的写法
C.prototype = {constructor: C,method1: function (...) { ... },// ...
};// 更好的Zane写法
C.prototype.method1 = function (...) { ... };

如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称

funciton Foo() {};
var f = new Foo();
f.constructor.name // "Foo"

instanceof运算符

instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例

var v = new Vehicle();
v instanceof Vehicle // true

instanceof运算符的左边是实例对象,右边是构造函数
instanceof检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上
下面两种写法等价

v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)

上面代码中,Vehicle是对象v的构造函数,它的原型对象是Vehicle.prototype
isPrototypeOf()方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型

由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true

var d = new Date();
d instanceof Date // true
d instanceof Object // true

由于任意对象(除了null)都是Object的实例,所以instanceof运算符可以判断一个值是否为非null的对象

有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真

var obj = Object.create(null);
typeof obj // "object"
obj instanceof Object // false

上面代码中,Object.create(null)返回一个新对象obj,它的原型是null
右边的构造函数Object的prototype属性,不在左边的原型链上,因此instanceof就认为obj不是Object的实例
这是唯一的instanceof运算符判断会失真的情况

instanceof运算符的一个用处是判断值的类型

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true

上面代码中instanceof运算符判断,变量x是数组,变量y是对象
注意,instanceof运算符只能用于对象,不适用原始类型的值
此外,对于undefined和null,instanceof运算符总是返回false。

var s = 'hello';
s instanceof String // falseundefined instanceof Object // false
null instanceof Object // false

上面代码中,字符串不是String对象的实例(因为字符串不是对象),所以返回false

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题

function Fubar(foo, bar) {if (this instanceof Fubar) {this._foo = foo;this._bar = bar;} else {return new Fubar(foo, bar);}
}

上面代码使用instanceof运算符,在函数体内部判断this关键字是否为构造函数Fubar的实例
如果不是就表明忘了加new命令

构造函数的继承

构造函数的继承是非常常见的需求(分为两步)

第一步是在子类构造函数中调用父类构造函数

function Sub(value) {Super.call(this);this.prop = value;
}

上面代码中,Sub是子类的构造函数,this是子类的实例
在实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性

第二步是让子类的原型指向父类的原型(子类继承父类原型)

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

上面代码中,Sub.prototype是子类的原型,要将它赋值为Object.create(Super.prototype),而不是直接等于Super.prototype
否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改

另外还可以写成Sub.prototype等于一个父类实例

Sub.prototype = new Super();

上面这种写法也有继承的效果,但是子类会具有父类实例的方法
有时这可能不是我们需要的,所以不推荐使用这种写法

举个栗子

function Shape() {this.x = 0;this.y = 0;
}Shape.prototype.move = function (x, y) {this.x += x;this.y += y;console.info('Shape moved');
};

让Rectangle构造函数继承Shape

// 1. 子类继承父类实例
function Rectangle() {Shape.call(this); // 调用父类构造函数
}
// or
function Rectangle() {this.base = Shape;this.base();
}// 2. 子类继承父类原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

采用这样的写法以后,instanceof运算符会对子类和父类的构造函数都返回true

var rect = new Rectangle();rect instanceof Rectangle // true
rect instanceof Shape // true

上面代码中,子类是整体继承父类
有时只需要单个方法的继承可以采用下面的写法

ClassB.prototype.print = function() {ClassA.prototype.print.call(this);// some code
}

上面代码中子类B的print方法先调用父类A的print方法再部署自己的代码
这就等于继承了父类A的print方法

多重继承

JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象
但是可以通过变通方法实现这个功能

function M1() {this.hello = 'hello';
}function M2() {this.world = 'world';
}function S() {M1.call(this);M2.call(this);
}// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);// 指定构造函数
S.prototype.constructor = S;var s = new S();
s.hello // 'hello'
s.world // 'world'

上面代码中子类S同时继承了父类M1和M2
这种模式又称为 Mixin(混入)

模块

随着网站逐渐变成互联网应用程序,嵌入网页的 JavaScript 代码越来越庞大复杂
网页越来越像桌面程序,需要一个团队分工协作进度管理单元测试等等,开发者必须使用软件工程的方法管理网页的业务逻辑

这就需要JS的模块化编程
在理想的情况下开发者只需要实现核心的业务逻辑而其他都可以加载他人写好的模块
但是JS刚开始不是一种模块化编程语言,到ES6才开始支持类和模块
下面是使用传统方法利用对象实现模块效果

1 基本实现
模块是实现特定功能的一组属性和方法的封装
简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面

var module1 = new Object({_count : 0,m1 : function () {// ...},m2 : function () {// ...}
});

上面的函数m1和m2都封装在module1对象中
使用时直接调用该对象的属性

module1.m1();

但是这样的写法会暴露所有模块成员,内部状态可以被外部改写
比如外部代码可以直接改变内部计数器的值

module1._count = 5;

2 封装私有变量:构造函数写法
使用构造函数封装私有变量

function StringBuilder() {var buffer = [];this.add = function (str) {buffer.push(str);};this.toString = function () {return buffer.join('');};
}

上面代码中buffer是模块的私有变量
一旦生成实例对象,外部是无法直接访问buffer的
但是这种方法将私有变量封装在构造函数中,导致构造函数与实例对象是一体的,总是存在于内存之中,无法在使用完成后清除
这意味着构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据,违背了构造函数与实例对象在数据上相分离的原则
即实例对象的数据,不应该保存在实例对象以外
同时耗费内存

function StringBuilder() {this._buffer = [];
}StringBuilder.prototype = {constructor: StringBuilder,add: function (str) {this._buffer.push(str);},toString: function () {return this._buffer.join('');}
};

这种方法将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全

3 封装私有变量:IIFE写法
使用IIFE(Immediately-Invoked Function Expression)
将相关属性和方法封装在一个函数作用域中从而不暴露私有成员

var module1 = (function () {var _count = 0;var m1 = function () {// some code};var m2 = function () {// some code};return {m1: m1,m2: m2};
})();

使用上面的方法外部代码无法读取内部的_count变量

console.info(module1._count); // undefined

上面的module1就是 JavaScript 模块的基本写法

下面是对该模块写法的优化

4 模块的放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用放大模式(augmentation)

var module1 = (function (mod) {mod.m3 = function () {// ...};return mod;
})(module1);

上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块

在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载
如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用宽放大模式(Loose augmentation)

var module1 = (function (mod) {// ...return mod;
})(window.module1 || {});

与放大模式相比,宽放大模式就是立即执行函数的参数可以是空对象

5 输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互
为了在模块内部调用全局变量,必须显式地将其他变量输入模块

var module1 = (function ($, YAHOO) {// ...
})(jQuery, YAHOO);

上面的module1模块需要使用 jQuery 库和 YUI 库,就把这两个库(其实是两个模块)当作参数输入module1
这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显

立即执行函数还可以起到命名空间的作用

(function($, window, document) {function go(num) {}function handleEvents() {}function initialize() {}function dieCarouselDie() {}// attach to the global scopewindow.finalCarousel = {init : initialize,destroy: dieCarouselDie}})( jQuery, window, document );

上面代码中,finalCarousel对象输出到全局
对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/68529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络面试题整理

TCP通讯原理:三次握手,四次挥手 TCP(Transmission Control Protocol)通信中的"三次握手"和"四次挥手"是建立和终止TCP连接时的标准过程,用于确保数据的可靠传输和连接的正确关闭。 三次握手&…

【LeetCode】2511.最多可以摧毁的敌人城堡数目

题目 给你一个长度为 n ,下标从 0 开始的整数数组 forts ,表示一些城堡。forts[i] 可以是 -1 ,0 或者 1 ,其中: -1 表示第 i 个位置 没有 城堡。0 表示第 i 个位置有一个 敌人 的城堡。1 表示第 i 个位置有一个你控制…

Python 加密解密实战:非对称与对称,如何在实际应用中各显神通?

建议阅读原文链接,效果更佳 Python 加密解密实战:非对称与对称,如何在实际应用中各显神通? 1.非对称加密的实际应用 在 Python 世界中,非对称加密算法,如 RSA,的应用已经越来越广泛&#xff0…

【STM32】学习笔记-SPI通信

SPI通信 SPI通信(Serial Peripheral Interface)是一种同步的串行通信协议,用于在微控制器、传感器、存储器、数字信号处理器等之间进行通信。SPI通信协议需要使用4个线路进行通信:时钟线(SCLK)、主输入/主输出线(MISO)、主输出/主…

Elasticsearch、Kibana以及Java操作ES 的快速使用

docker 安装elastic search 、 kibana(可视化管理elastic search) docker pull elasticsearch:7.12.1 docker pull kibana:7.12.1创建docker自定义网络 docker自定义网络可以使得容器之间使用容器名网络互连,默认的网络不会有这功能。 一定…

聚焦磷酸铁锂产线革新,宏工科技一站式解决方案

兼顾了低成本与安全性两大属性,磷酸铁锂市场在全球范围内持续升温,并有望保持较高的景气度。巨大的需求空间之下,行业对于锂电装备企业的自动化与智能化水平、整线交付能力、产品效率与稳定性等均提出了新的要求。 以宏工科技股份有限公司&a…

ES查询报错内容长度超过104857600

项目场景: 使用 ElasticsearchRestTemplate 或者使用 RestHighLevelClient 查询 ES 报错 内容长度超过 104857600 问题描述 ES 查询报错 entiity content is too long xxx for the configured buffer limit 104857600 Overridepublic void esQuery() {restHighL…

LiveGBS流媒体平台GB/T28181功能-支持数据库切换为高斯数据库信创瀚高数据信创数据库

LiveGBS流媒体平台GB/T28181功能-支持数据库切换为高斯数据库信创瀚高数据信创数据库 1、如何配置切换高斯数据库?2、如何配置切换信创瀚高数据库?3、搭建GB28181视频直播平台 1、如何配置切换高斯数据库? livecms.ini -> [db]下面添加配…

优思学院|亲和图案例:寻找六西格玛的项目

什么是亲和图? 亲和图(Affinity Diagram)主要功能在於分类归纳,协助在一堆杂乱无章的资料之中,有系统的归纳出几个大类,以利后续作业。通常先利用头脑风暴(Brainstorming)方式得到大…

视频监控/视频云存储EasyCVR平台接入华为ivs3800平台提示400报错,如何解决?

开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,视频云存储/安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多路视频…

并行计算之计算平台体系结构(流水线,异构处理器与异构并行)

并行计算粒度 分布式并行(多主机,多进程) : MPI 共享存储式并行 (多线程) :OpenMP OpenCL OpenACC 指令级并行 CPU流水线 CPU流水线 指将一条指令的执行过程划分为多个阶段,使得在每个时钟周期内能够并行执行多条指令&#…

CSS中图片旋转超出父元素解决办法

下面的两种解决办法都会导致图片缩小&#xff0c;可以给图片进行初始化的宽高设置 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">…

使用 Web HID API 在浏览器中进行HID设备交互(纯前端)

文章目录 目的基础说明示例工程&#xff08;HID透传测试工具&#xff09;总结 目的 最近再搞HID透传 《STM32 USB使用记录&#xff1a;HID类设备&#xff08;后篇&#xff09;》 。 市面上的各种测试工具都或多或少存在问题&#xff0c;所以就自己写一个工具进行测试。目前来…

Java设计模式之原型模式

原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式之一。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当直接…

uni-app 客服按钮可上下拖动动

项目需求&#xff1a; 因为悬浮客服有时候会遮挡住界面内容&#xff0c;故需要对悬浮的气泡弹窗做可拖动操作 movable-area&#xff1a;可拖动区域 movable-view&#xff1a;可移动的视图容器&#xff0c;在页面中可以拖拽滑动或双指缩放。 属性说明 属性名类型默认值说…

四川玖璨电子商务有限公司:短视频有什么运营

根据短视频有什么运营&#xff0c;短视频的拍摄工具多种多样。无论是在手机上拍摄还是使用专业摄影设备&#xff0c;拍摄短视频的目的都是为了吸引观众的注意力和提升内容的质量。从小花费到高投入&#xff0c;在不断发展的短视频行业中&#xff0c;拍摄方法也得到了不断创新和…

QT的补充知识

一、文件 QFile QT提供了QFile类用于对文件进行读写操作&#xff0c;也提供了其他的两个类&#xff1a;文本流&#xff08;QTextSream&#xff09;和数据流&#xff08;QDataStream&#xff09; 文本流&#xff08;QTextSream&#xff09;&#xff1a;用于对文本数据的处理&am…

Redis未授权访问漏洞复现

Redis 简单使用 Redis 未设置密码&#xff0c;客户端工具可以直接链接。 Redis 是非关系型数据库系统&#xff0c;没有库表列的逻辑结构&#xff0c;仅仅以键值对的方式存储数据。 先启动容器 Redis 未设置密码&#xff0c;客户端工具可以直接链接 https://github.com/xk11z/…

Redis的数据类型到底有什么奥秘

这里我们先只介绍五种常用的数据类型~ 目录 1、string 2、hash 3、list 4、set 5、zset 6、示例 1、string 数据类型&#xff1a;string内部编码&#xff1a;raw、int、embstr 说明&#xff1a; raw是最基本的字符串--底层是一个char数组&#xff08;此处的char是一个字…

【Element】Vue+Element表单校验详解

Element表单校验 作为一个后端开发&#xff0c;总结一下实际工作中表单校验的场景和用法。 Element官网&#xff1a;https://element.eleme.cn/#/zh-CN/component/form 代码地址&#xff1a;https://gitee.com/kkmy/kw-microservices/tree/master/kw-ui/kwsphere 常用表单校验场…