JavaScript 面向对象编程(二) —— 构造函数 / 原型 / 继承 / ES5 新增方法
本篇为 JavaScript 进阶 ES6 系列笔记第二篇,将陆续更新后续内容。参考:JavaScript 进阶面向对象 ES6 ;ECMAScript 6 入门 ; Javascript 继承机制的设计思想;javascript —— 原型与原型链
系列笔记:
JavaScript 面向对象编程(一) —— 面向对象基础
文章脉络预览:
「一」构造函数和原型
在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前,JS 中没有引入类的概念。
ES6, 全称 ECMAScript 6.0 ,2015.6 发版,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6, 不过只实现了 ES6 部分特性和功能。
在 ES6 之前,对象不是基于类创建的,而是用一种称为 构造函数 的特殊函数来定义对象和它们的特征。
ES6 之前,JavaScript 没有 " 子类 " 和 " 父类 " 的概念,也没有 " 类 "(class)和 " 实例 "(instance)的区分,全靠一种很奇特的 " 原型链 "(prototype chain)模式,来实现继承。
参见:阮一峰大佬的 Javascript 继承机制的设计思想
1. 构造函数
- 创建对象三种方式
- 对象字面量
- new Object()
- 自定义构造函数
- 构造函数
构造函数 是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,总与 new
一起使用。可以将对象中一些公共的属性和方法抽取出来,然后封装到该函数中。
- 静态成员、实例成员
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部 this
上添加。这两种方式添加的成员,分别称为 静态成员 和 实例成员。
- 静态成员:在构造函数本身上添加的成员,只能由构造函数本身访问
- 实例成员:在构造函数内部创建的对象成员,只能由实例化对象访问
- 构造函数的问题
用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。每一个实例对象,都有自己的属性和方法的副本,需要开辟另外空间去存储。这不仅无法做到数据共享,也是极大的资源浪费。
2. 原型
为解决上述资源浪费、无法共享的问题,构造函数设置了一个 prototype
属性。这个属性包含一个对象,所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用 prototype
对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
由于所有的实例对象共享同一个 prototype
对象,那么从外界看起来,prototype
对象就好像是实例对象的原型,而实例对象则好像 " 继承 " 了 prototype
对象一样。
- 显式原型 prototype
可以将公共属性和方法直接定义到 prototype
对象上,所有对象实例都可以共享这些属性和方法
function Dog(name) {this.name = name;
}
Dog.prototype = { species: '犬科' };var dogA = new Dog('毛毛');
var dogB = new Dog('吉吉');
console.log(dogA.species); // 犬科
console.log(dogB.species); // 犬科// species 属性放在 prototype 对象里,是两个实例对象共享的
// 只要修改了 prototype 对象,就会同时影响到两个实例对象
Dog.prototype.species = '猫科';
console.log(dogA.species); // 猫科
console.log(dogB.species); // 猫科
- 隐式原型 __proto__
每个实例对象都会有一个属性 __proto__
,它指向构造函数的 prototype
对象,之所以实例对象可以使用 prototype
的属性和方法,就是因为对象有 __proto__
的存在。
console.log(dogA.__proto__ === Dog.prototype); // true
__proto__(隐式原型)与 prototype(显式原型)
- __proto__ 是每个对象都有的一个属性,而 prototype 是函数才会有的属性。
- __proto__ 指向的是当前对象的原型对象,而 prototype 指向的,是以当前函数作为构造函数构造出来的对象的原型对象。
参考:__proto__ 和 prototype 的区别和关系?
补充:
MDN 中对 __proto__
的说明
console.log(dogA.__proto__ === Object.getPrototypeOf(dogA)); // true
绝大部分浏览器都支持这个非标准的方法 dogA.__proto__
访问原型,然而它并不存在于 Dog.prototype
中,实际上,它是来自于 Object.prototype
,与其说是一个属性,不如说是一个 getter/setter
,当使用 dogA.__proto__
时,可以理解成返回了 Object.getPrototypeOf(dogA)
。
- constructor 构造函数
隐式原型 __proto__
和 显式原型 prototype
里面都有一个 constructor
属性, 称其为构造函数,它指回构造函数本身。
上图采取 prototype.sing
向 prototype
添加新方法,其原来的对象元素不会变化。如果但如果给 prototype
赋值一个对象,则必须手动利用 constructor
指回原函数。
如果不 constructor
指回原函数,新赋值的对象则会将原来的原型对象覆盖。里面不再有 constructor
属性。
- 构造函数、实例、原型对象三者之间的关系
3. 原型链
当一个对象在查找一个属性的时候,自身没有就会根据 __proto__
向它的原型进行查找,如果都没有,则向它的原型的原型继续查找,直到查到 Object.prototype.__proto__
为 null
,这样也就形成了原型链。
对于原型链详细解释、推导可以参考 javascript —— 原型与原型链 ,下面引入此文内容:
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线
4. JavaScript 成员查找机制
当访问一个对象的属性时,先在对象自身中查找,如果没有就沿 原型链 向上查找该属性。
但是,如果多个原型中都含此对象时,因为是按顺序查找,所以该属性的值采取 就近原则 。
5. this 指向问题
构造函数和原型对象中的 this
所指向的都是实例对象
6. 扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
注意:不能通过会造成覆盖的 Array.prototype = {}
进行添加内置方法
「二」继承
ES6 之前并没有给我们提供 extends 继承,只能通过 构造函数 + 原型对象 模拟实现继承,被称为 组合继承。下面来讲解这种继承。
在此之前,先拓展一个方法 call()
1. call() 方法
call()
允许为不同的对象分配和调用属于一个对象的函数/方法。
call()
提供新的 this
值给当前调用的函数 / 方法
- 语法格式
function.call(thisArg, arg1, arg2, ...)
thisArg
:可选的,指 function 函数运行时使用的 this 值arg1, arg2, ...
:指定的参数列表
2. 利用父构造函数继承属性
此外,也可以使用 call
来实现子构造函数继承父构造函数中的属性
3. 借用原型对象继承方法
这样就解决了子构造函数继承父构造函数中的方法问题,而且不会影响父构造函数中的原型。需要注意一点,要将子构造函数原型的 constructor
指回子构造函数,否则 constructor
会指向父构造函数。
4. 类的本质
其实,类的本质仍是函数。
class Star { }
console.log(typeof Star); // function
可以将类看作构造函数的另一种写法。类的所有方法都定义在类的 prototype
属性上;类创建的实例,里面也有 __proto__
指向类的 prototype
原型对象。
因此,ES6 中类的绝大部分功能,ES5 都可以做到,class 的写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。ES6 的类其实就是语法糖:
语法糖(Syntactic sugar)指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
「三」ES5 新增方法
ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,主要包括:
- 数组方法
- 字符串方法
- 对象方法
1. 数组方法
迭代(遍历)方法:forEach()
、map()
、filter()
、some()
、every()
- forEach() 方法
forEach()
方法对数组的每个元素执行一次给定的函数。
arr.forEach(callback(currentValue , index , array))
callback
为数组中每个元素执行的函数,该函数接收一至三个参数:
currentValue
:数组中正在处理的当前元素index
可选,数组中正在处理的当前元素的索引array
可选,forEach()
方法正在操作的数组
- filter() 方法
filter()
方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 主要用于 筛选数组。
var newArray = arr.filter(callback(element, index, array))
callback
用来测试数组的每个元素的函数。返回 true
表示该元素通过测试,保留该元素,false
则不保留。它接受以下三个参数:
element
:数组中当前正在处理的元素index
:可选,正在处理的元素在数组中的索引array
:可选,调用filter
的数组本身
- some() 方法
some()
方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值,如果查找到这个元素就返回 true
,否则返回 false
arr.some(callback(element, index, array))
callback
用来测试数组的每个元素的函数。它接受以下三个参数:
element
:数组中当前正在处理的元素index
:可选,正在处理的元素在数组中的索引array
:可选,调用some
的数组本身
注意:如果找到第一个满足条件的元素,则终止循环,不在继续查找
- map() 方法
map()
方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。用法与 forEach()
类似
const array1 = [1, 4, 9, 16];// pass a function to map
const map1 = array1.map(x => x * 2);console.log(map1);
// expected output: Array [2, 8, 18, 32]
- every() 方法
every()
方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。用法与 some()
类似
- 案例:查询商品
HTML 代码
<div class="search">按照价格查询: <input type="text" class="start"> - <input type="text" class="end"> <buttonclass="search-price">搜索</button> 按照商品名称查询: <input type="text" class="product"> <buttonclass="search-pro">查询</button></div><table><thead><tr><th>id</th><th>产品名称</th><th>价格</th></tr></thead><tbody><!-- 动态渲染 --></tbody></table>
JS 代码
// 利用新增数组方法操作数据var data = [{id: 1,pname: '小米',price: 1099}, {id: 2,pname: 'oppo',price: 2699}, {id: 3,pname: '荣耀',price: 2299}, {id: 4,pname: '华为',price: 6499}];var tbody = document.querySelector('tbody');var search_price = document.querySelector('.search-price');var start = document.querySelector('.start');var end = document.querySelector('.end');var product = document.querySelector('.product');var search_pro = document.querySelector('.search-pro');setDate(data); // 先将原始数据进行渲染// 渲染数据的函数function setDate(mydata) {tbody.innerHTML = ''; // 清空原来数据mydata.forEach(function (value) {var tr = document.createElement('tr');tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>';tbody.appendChild(tr);});}// 利用 filter 筛选符合价格添加的数据search_price.addEventListener('click', function () {var newData = data.filter(function (value) {return value.price >= start.value && value.price <= end.value;})if (!start.value || !end.value) {alert('请输入正确的查询范围!');search_price.onclick = ''; // 解除绑定return;}console.log(newData);setDate(newData); // 将筛选后的数据进行渲染})// 利用 some 根据商品名称查找search_pro.addEventListener('click', function () {// 给出原始数据var arr = [];// 这里利用 some 查唯一元素,效率高,找到后就不再进行循环var flag = data.some(function (value) {if (value.pname === product.value) {// console.log(value);arr.push(value);return true;}return false;});if (!flag) {alert('查询不到该商品!');search_pro.onclick = '';return;}setDate(arr);})
- forEach() 和 some() 的区别
2. 字符串方法
trim()
方法返回一个从两头去掉空白字符的字符串,并不影响原字符串本身。
str.trim()
trim()
可以避免空格的影响,拿到正确的数据,如下图:
3. 对象方法
- Object.keys() 方法
Object.keys()
方法会返回一个由一个给定对象的自身 可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
Object.keys(obj)
效果类似 for...in
,返回一个由属性名组成的数组。
- Object.defineProperty() 方法
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
obj
:必需,要定义属性的对象prop
:必需,要定义或修改的属性的名称或 Symboldescriptor
:必需,要定义或修改的属性描述符
Object.defineProperty()
第三个参数 descriptor
说明:以对象形式 { }
书写;{ }
内可选参数有:
value
:设置属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)writable
:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被重写。默认为 false。enumerable
:当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。configurable
:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
JavaScript 面向对象编程(二) —— 构造函数 / 原型 / 继承 / ES5 新增方法相关推荐
- (二)Javascript面向对象编程:构造函数的继承
Javascript面向对象编程:构造函数的继承 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承&q ...
- Javascript面向对象编程:构造函数的继承
今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...
- javascript面向对象编程实现[定义(静态)属性方法--继承]
javascript面向对象编程实现: 1.类的声明: function test1(){ this.p ...
- 总结 构造函数与非构造函数 原型继承的一个方法
这两天真的一直在看原型以及继承之间的千丝万缕,哇,收获颇多,不过所谓温故知新,还是要多复习复习知识点,才能察觉那些之前不易发现的小小sparkle 真心推荐MDN文档--对象原型,JavaScript ...
- JavaScript面向对象编程指南(五) 原型
第5章 原型 5.1 原型属性 function f(a,b){return a*b;};// length 属性f.length; //2// constructor 构造属性f.construct ...
- 「JavaScript面向对象编程指南」原型
在 JS 中,函数本身也是一个包含了方法(如apply和call)和属性(如length和constructor)的对象,而prototype也是函数对象的一个属性 function f(){} f. ...
- JavaScript 面向对象编程(四) —— 正则表达式
本篇为 JavaScript 进阶 ES6 系列笔记第四篇,将陆续更新后续内容.参考:JavaScript 进阶面向对象 ES6 : 系列笔记: JavaScript 面向对象编程(一) -- 面向对 ...
- JavaScript 面向对象编程(三) —— 函数进阶 / 严格模式 / 高阶函数 / 闭包 / 浅拷贝和深拷贝
本篇为 JavaScript 进阶 ES6 系列笔记第三篇,将陆续更新后续内容.参考:JavaScript 进阶面向对象 ES6 :ECMAScript 6 入门 系列笔记: JavaScript 面 ...
- JavaScript面向对象编程深入分析
JavaScript面向对象编程深入分析 一. Javascript 面向对象编程:封装 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又 ...
最新文章
- 高通与苹果宣布“复合”,英特尔黯然退场 | 极客头条
- laravel5.5 不能正常自动回复的问题
- 嵌入式设备带操作系统的启动过程
- 数值计算方法(二)——复化求积公式
- scrapy 爬虫利器初体验(1)
- 再见2018,你好2019
- 软件开发模式之敏捷开发模型,应用之DevOps
- 主梁弹性模量计算_轮扣模板计算书(GB51210-2016规范)
- 中国模式识别与计算机视觉会议,第二届中国模式识别与计算机视觉大会(PRCV2019)征稿...
- 利用NCBIdatasets批量下载大规模生信数据集
- vue3代码检查以及格式化配置
- LA@determinant@行列式@Vandermonde行列式
- Android中自定义悬浮窗
- 通俗易懂告诉你:何为95%置信区间?
- 1.Object.create() 是什么
- 超级码力在线编程大赛初赛 第2场 T1-T4题解
- matlab程序代码 伪码捕获_MATLAB程序转为伪代码
- AltiumDesigner16\Quartus13\Labview2017\Matlab安装包网盘
- 零基础制作【武林外传】辅助工具(二)
- 徐志摩的再别康桥 .
热门文章
- vmware linux版本_vmware无法在kali下打开问题
- oracle过程包保存乱码_这些火遍网络的哆啦A梦表情包,你知道出处吗?
- JAVA 正则表达式 RegexUtil
- go web db每次关闭_竟然不用写代码!一款可视化 Web 管理后台生成工具
- jupyternotebook 报告_基本操作!在VS 代码中如何使用Jupyter Notebook
- canal 历史数据如何处理_MySQL日志解析工具Canal的使用
- JS-面向对象---属性的特性 / 修改属性的特性
- 寻找两个有序数组中的中位数
- Cookie案例-显示用户的上次访问时间代码实现
- Oracle对表空间、用户、用户权限的操作