1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > JavaScript 面向对象编程(二) —— 构造函数 / 原型 / 继承 / ES5 新增方法

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

时间:2021-09-02 18:33:49

相关推荐

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

本篇为 JavaScript 进阶 ES6 系列笔记第二篇,将陆续更新后续内容。参考:JavaScript 进阶面向对象 ES6 ;ECMAScript 6 入门 ; Javascript 继承机制的设计思想;javascript —— 原型与原型链

系列笔记:

JavaScript 面向对象编程(一) —— 面向对象基础

文章脉络预览:

「一」构造函数和原型

在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前,JS 中没有引入类的概念。

ES6, 全称 ECMAScript 6.0 ,.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.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, ...)

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 mapconst 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。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。