js高级-es6笔记(3)
JavaScript 进阶 - 第3天
了解构造函数原型对象的语法特征,掌握 JavaScript 中面向对象编程的实现方式,基于面向对象编程思想实现 DOM 操作的封装。
- 了解面向对象编程的一般特征
- 掌握基于构造函数原型对象的逻辑封装
- 掌握基于原型对象实现的继承
- 理解什么原型链及其作用
- 能够处理程序异常提升程序执行的健壮性
一、面向对象
学习 JavaScript 中基于原型的面向对象编程序的语法实现,理解面向对象编程的特征。
面向对象编程是一种程序设计思想,它具有 3 个显著的特征:封装、继承、多态。
1.1 封装
封装的本质是将具有关联的代码组合在一起,其优势是能够保证代码复用且易于维护,函数是最典型也是最基础的代码封装形式,面向对象思想中的封装仍以函数为基础,但提供了更高级的封装形式。
命名空间
先来回顾一下以往代码封装的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> let beats = { name: '狼', setName: function (name) { this.name = this.name; }, getName() { console.log(this.name); } }
beats.setName('熊'); beats.getName(); </script>
|
以往以普通对象(命名空间)形式封装的代码只是单纯把一系列的变量或函数组合到一起,所有的数据变量都被用来共享(使用 this 访问)。
构造函数
对比以下通过面向对象的构造函数实现的封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script> function Person() { this.name = '佚名'; this.setName = function (name) { this.name = name; } this.getName = () => { console.log(this.name); } }
let p1 = new Person(); p1.setName('小明'); console.log(p1.name);
let p2 = new Person(); console.log(p2.name); </script>
|
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装
前面我们学过的构造函数方法很好用,但是存在浪费内存的问题
总结:
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
- 命名空间式的封装无法保证数据的独立性
注:可以举一些例子,如女娲造人等例子,加深对构造函数的理解。
原型对象
实际上每一个构造函数都有一个名为 prototype
的属性,译成中文是原型的意思,prototype
的是对象类据类型,称为构造函数的原型对象,每个原型对象都具有 constructor
属性代表了该原型对象对应的构造函数。
1 2 3 4 5 6 7 8
| <script> function Person() { }
console.log(Person.prototype); </script>
|
了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> function Person() { }
Person.prototype.sayHi = function () { console.log('Hi~'); } let p1 = new Person(); p1.sayHi(); </script>
|
构造函数 Person
中未定义任何方法,这时实例对象调用了原型对象中的方法 sayHi
,接下来改动一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script> function Person() { this.sayHi = function () { console.log('嗨!'); } }
Person.prototype.sayHi = function () { console.log('Hi~'); }
let p1 = new Person(); p1.sayHi(); </script>
|
构造函数 Person
中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 sayHi
。
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script> function Person() { this.sayHi = function () { console.log('嗨!' + this.name); } }
Person.prototype.sayHi = function () { console.log('Hi~' + this.name); } Person.prototype.name = '小明';
let p1 = new Person(); p1.sayHi(); let p2 = new Person(); p2.sayHi(); </script>
|
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。
1.2 继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义,分别封装中国人和日本人的行为特征来理解编程中继承的含义,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <script> function Chinese() { this.arms = 2; this.legs = 2; this.eyes = 2;
this.skin = 'yellow'; this.language = '中文';
this.walk = function () {} this.sing = function () {} this.sleep = function () {} }
function Japanese() { this.arms = 2; this.legs = 2; this.eyes = 2;
this.skin = 'yellow'; this.language = '日文';
this.walk = function () {} this.sing = function () {} this.sleep = function () {} } </script>
|
其实我们都知道无论是中国人、日本人还是其它民族,人们的大部分特征是一致的,然而体现在代码中时人的相同的行为特征被重复编写了多次,代码显得十分冗余,我们可以将重复的代码抽离出来:
原型继承
基于构造函数原型对象实现面向对象的继承特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <script> function Person() { this.arms = 2; this.legs = 2; this.eyes = 2; this.walk = function () {} this.sing = function () {} this.sleep = function () {} } function Chinese() { this.skin = 'yellow'; this.language = '中文'; } function Japanese() { this.skin = 'yellow'; this.language = '日文'; } </script>
|
上述代码可以理解成将 Chinese
和 Japanese
共有的属性和方法提取出来了,也就是说 Chinese
和 Japanese
需要【共享】一些属性和方法,而原型对象的属性和方法恰好是可以被用来共享的,因此我们看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <script> function Chinese() { this.skin = 'yellow'; this.language = '中文'; } function Japanese() { this.skin = 'yellow'; this.language = '日文'; } let people = { arms: 2, legs: 2, eyes:2, walk: function () {}, sleep: function () {}, sing: function () {} } Chinese.prototype = people; Chinese.prototype.constructor = Chinese; </script>
|
如下图所示:
创建对象 people
将公共的的属性和方法独立出来,然后赋值给构造函数的 prototype
这样无论有多少个民族都可以共享公共的属性和方法了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <script> let people = { arms: 2, legs: 2, eyes:2, walk: function () {}, sleep: function () {}, sing: function () {} } function Chinese() { this.skin = 'yellow'; this.language = '中文'; } function Japanese() { this.skin = 'yellow'; this.language = '日文'; } function Englist() { this.skin = 'white'; this.language= '英文'; } Chinese.prototype = people; Chinese.prototype.constructor = Chinese; let c1 = new Chinese(); Japanese.prototype = people; Janpanese.prototype.constructor = Japanese; English.prototype = people; English.prototype.constructor = English; </script>
|
继承是一种可以“不劳而获”的手段!!!上述代码中 Chinese
、Japanese
、English
都轻松的获得了 people
的公共的方法和属性,我们说 Chinese
、Japanese
、English
继承了 people
。
上述代码中是以命名空间的形式实现的继承,事实上 JavaScript 中继承更常见的是借助构造函数来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <script> function Person() { this.arms = 2; this.legs = 2; this.eyes = 2;
this.walk = function () {} this.sing = function () {} this.sleep = function () {} }
function Chinese() { this.skin = 'yellow'; this.language = '中文'; }
function Japanese() { this.skin = 'yellow'; this.language = '日文'; }
let human = new Person();
Chinese.prototype = human; Chinese.prototype.constructor = Chinese; Japanese.prototype = human; Japanese.prototype.constructor = Japanese; </script>
|
如下图所示:
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <script> function Person() { this.arms = 2; this.walk = function () {} } Person.prototype.legs = 2; Person.prototype.eyes = 2; Person.prototype.sing = function () {} Person.prototype.sleep = function () {} function Chinese() { this.skin = 'yellow'; this.language = '中文'; } Chinese.prototype = new Person(); Chinese.prototype.constructor = Chinese; let c1 = new Chinese();
console.log(c1); </script>
|
在 JavaScript 对象中包括了一个非标准备的属性 __proto__
它指向了构造函数的原型对象,通过它可以清楚的查看原型对象的链状结构。