本篇为 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. 构造函数


  • 创建对象三种方式
  1. 对象字面量
  2. new Object()
  3. 自定义构造函数
  • 构造函数

构造函数 是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,总与 new 一起使用。可以将对象中一些公共的属性和方法抽取出来,然后封装到该函数中。

  • 静态成员、实例成员

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部 this 上添加。这两种方式添加的成员,分别称为 静态成员 和 实例成员。

  1. 静态成员:在构造函数本身上添加的成员,只能由构造函数本身访问
  2. 实例成员:在构造函数内部创建的对象成员,只能由实例化对象访问
  • 构造函数的问题

用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。每一个实例对象,都有自己的属性和方法的副本,需要开辟另外空间去存储。这不仅无法做到数据共享,也是极大的资源浪费。

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(显式原型)

  1. __proto__ 是每个对象都有的一个属性,而 prototype 是函数才会有的属性。
  2. __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.singprototype 添加新方法,其原来的对象元素不会变化。如果但如果给 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, ...)
  1. thisArg:可选的,指 function 函数运行时使用的 this 值
  2. 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 为数组中每个元素执行的函数,该函数接收一至三个参数:

  1. currentValue :数组中正在处理的当前元素
  2. index 可选,数组中正在处理的当前元素的索引
  3. array 可选,forEach() 方法正在操作的数组

  • filter() 方法

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 主要用于 筛选数组。

var newArray = arr.filter(callback(element, index, array))

callback 用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。它接受以下三个参数:

  1. element :数组中当前正在处理的元素
  2. index :可选,正在处理的元素在数组中的索引
  3. array:可选,调用 filter 的数组本身

  • some() 方法

some() 方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值,如果查找到这个元素就返回 true,否则返回 false

arr.some(callback(element, index, array))

callback 用来测试数组的每个元素的函数。它接受以下三个参数:

  1. element :数组中当前正在处理的元素
  2. index :可选,正在处理的元素在数组中的索引
  3. 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)
  1. obj:必需,要定义属性的对象
  2. prop:必需,要定义或修改的属性的名称或 Symbol
  3. descriptor:必需,要定义或修改的属性描述符

Object.defineProperty() 第三个参数 descriptor 说明:以对象形式 { } 书写;{ } 内可选参数有:

  1. value:设置属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)
  2. writable:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被重写。默认为 false。
  3. enumerable:当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。
  4. configurable:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。


JavaScript 面向对象编程(二) —— 构造函数 / 原型 / 继承 / ES5 新增方法相关推荐

  1. (二)Javascript面向对象编程:构造函数的继承

    Javascript面向对象编程:构造函数的继承 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承&q ...

  2. Javascript面向对象编程:构造函数的继承

    今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...

  3. javascript面向对象编程实现[定义(静态)属性方法--继承]

    javascript面向对象编程实现:             1.类的声明:                 function test1(){                     this.p ...

  4. 总结 构造函数与非构造函数 原型继承的一个方法

    这两天真的一直在看原型以及继承之间的千丝万缕,哇,收获颇多,不过所谓温故知新,还是要多复习复习知识点,才能察觉那些之前不易发现的小小sparkle 真心推荐MDN文档--对象原型,JavaScript ...

  5. JavaScript面向对象编程指南(五) 原型

    第5章 原型 5.1 原型属性 function f(a,b){return a*b;};// length 属性f.length; //2// constructor 构造属性f.construct ...

  6. 「JavaScript面向对象编程指南」原型

    在 JS 中,函数本身也是一个包含了方法(如apply和call)和属性(如length和constructor)的对象,而prototype也是函数对象的一个属性 function f(){} f. ...

  7. JavaScript 面向对象编程(四) —— 正则表达式

    本篇为 JavaScript 进阶 ES6 系列笔记第四篇,将陆续更新后续内容.参考:JavaScript 进阶面向对象 ES6 : 系列笔记: JavaScript 面向对象编程(一) -- 面向对 ...

  8. JavaScript 面向对象编程(三) —— 函数进阶 / 严格模式 / 高阶函数 / 闭包 / 浅拷贝和深拷贝

    本篇为 JavaScript 进阶 ES6 系列笔记第三篇,将陆续更新后续内容.参考:JavaScript 进阶面向对象 ES6 :ECMAScript 6 入门 系列笔记: JavaScript 面 ...

  9. JavaScript面向对象编程深入分析

    JavaScript面向对象编程深入分析 一. Javascript 面向对象编程:封装 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又 ...

最新文章

  1. 高通与苹果宣布“复合”,英特尔黯然退场 | 极客头条
  2. laravel5.5 不能正常自动回复的问题
  3. 嵌入式设备带操作系统的启动过程
  4. 数值计算方法(二)——复化求积公式
  5. scrapy 爬虫利器初体验(1)
  6. 再见2018,你好2019
  7. 软件开发模式之敏捷开发模型,应用之DevOps
  8. 主梁弹性模量计算_轮扣模板计算书(GB51210-2016规范)
  9. 中国模式识别与计算机视觉会议,第二届中国模式识别与计算机视觉大会(PRCV2019)征稿...
  10. 利用NCBIdatasets批量下载大规模生信数据集
  11. vue3代码检查以及格式化配置
  12. LA@determinant@行列式@Vandermonde行列式
  13. Android中自定义悬浮窗
  14. 通俗易懂告诉你:何为95%置信区间?
  15. 1.Object.create() 是什么
  16. 超级码力在线编程大赛初赛 第2场 T1-T4题解
  17. matlab程序代码 伪码捕获_MATLAB程序转为伪代码
  18. AltiumDesigner16\Quartus13\Labview2017\Matlab安装包网盘
  19. 零基础制作【武林外传】辅助工具(二)
  20. 徐志摩的再别康桥 .

热门文章

  1. vmware linux版本_vmware无法在kali下打开问题
  2. oracle过程包保存乱码_这些火遍网络的哆啦A梦表情包,你知道出处吗?
  3. JAVA 正则表达式 RegexUtil
  4. go web db每次关闭_竟然不用写代码!一款可视化 Web 管理后台生成工具
  5. jupyternotebook 报告_基本操作!在VS 代码中如何使用Jupyter Notebook
  6. canal 历史数据如何处理_MySQL日志解析工具Canal的使用
  7. JS-面向对象---属性的特性 / 修改属性的特性
  8. 寻找两个有序数组中的中位数
  9. Cookie案例-显示用户的上次访问时间代码实现
  10. Oracle对表空间、用户、用户权限的操作