好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ES6+ Class 前置知识

ES6+ Class 前置知识

1. 前言

在早期的 JavaScript 中是没类的概念的,如果想要实现类的 功能 需要通过构造 函数 来创建,使用 prototype 来实现类的继承。对于一些高级语言如 C++、Java、python 等,都是有类的概念的,而且 在这 些语言中类是非常重要的。而 JavaScript 由于历史原因在设计最初就没有想要引入类的概念,随着 JavaScript 越来越多地应用到大型项目中,JavaScript 的短板就显现了。虽然,可以使用原型等方式来 解决 ,但是还是存在各种各样的问题。

要学习 ES6 中的 class 首先要了解 ES5 中的构造 函数 ,主要了解在构造 函数 中是如何实现类和继承的,了解了这些知识带你有助于后面我们更深入的理解 ES6 中的 class。

2. 构造 函数

2.1 基本 用法

我们知道在 ES5 中如果想创建 一个 实例,是通过构造函来实现的。下面我们创建 一个 动物的类:

  function   Animal  ( type )   { 
   this  . type  =  type  ||   '鸟类'  ; 
 } 
Animal . prototype .  eat   =   function  (  )   { 
  console .  log  (  '鸟类吃虫子!'  ) 
 }  ; 

 var  animal  =   new   Animal  (  )  ; 
 

上面的 代码 就是使用构造 函数 来创建 一个 类,这里的构造 函数 首字母需要大写,这是约定俗成的,不需要解释记住就行。然后使用 new 的方式来实例化 一个 实例。

了解构造 函数 后,我们要明确地知道创建的实例有两种 属性 ,一种是自己的,一种是公用的?针对上面的 代码 中 type 和 eat 哪个是自己的那个是公用的呢?一般来说绑定在 this 上的是自有 属性 ,因为在实例化 一个 对象后 this 是指向这个实例的;而公共 属性 一般认为是 prototype 上的。另外,我们可以使用 hasOwnProperty 来判断是否是自身的 属性 。

 console .  log  ( animal .  hasOwnProperty  (  'type'  )  )  ; 		 // true 
console .  log  ( animal .  hasOwnProperty  (  'eat'  )  )  ; 		 // false 
 

为什么要知道 属性 是否是自己的呢?如果能想明白这个那么就会对类的继承有个深入的理解。下面我们来看两段 代码 :

  var  animal1  =   new   Animal  (  )  ; 
 var  animal2  =   new   Animal  (  )  ; 

console .  log  ( animal1 . type )  ; 	 // 鸟类 
console .  log  ( animal2 . type )  ; 	 // 鸟类 
animal1 . type  =   '家禽'  ; 
console .  log  ( animal1 . type )  ; 	 // 家禽 
console .  log  ( animal2 . type )  ; 	 // 鸟类 

console .  log  ( animal1 .  eat  (  )  )  ; 	 // 鸟类吃虫子! 
console .  log  ( animal2 .  eat  (  )  )  ; 	 // 鸟类吃虫子! 
animal1 . __proto__ .  eat   =   function  (  )   { 
  console .  log  (  '家禽吃粮食!'  ) 
 } 
console .  log  ( animal1 .  eat  (  )  )  ; 	 // 家禽吃粮食! 
console .  log  ( animal2 .  eat  (  )  )  ; 	 // 家禽吃粮食! 
 

上面的 代码 中我们可以看出当我们对 animal1 属性 type 修改 后不会影响 animal2 的 type 属性 ,但是我们可以通过 animal1 的原型链对原型上的 eat 方法 进行 修改 后,这时 animal2 上的 eat 方法 也被 修改 了。这说明在实例上 修改 自有 属性 不会影响其他实例上的 属性 ,但是,对非自有 属性 进行 修改 时就会影响其他 属性 的 方法 。主要这样会存在 一个 隐患,实例可以 修改 类的 方法 ,从而影响到其他继承这个类的实例。 在这 样的情况下我们要想实现 一个 完美的继承就需要考虑很多的东西了。

2.2 __proto__ 、 prototype 、 constructor

在说构造 函数 继承之前我们需要明确几个概念: __proto__ 、 prototype 、 constructor 这三个都是构造 函数 中的概念, 中文 的意思可以理解为 __proto__ (原型链) 、 prototype (原型) 、 constructor (构造 方法 )。它们在 class 上也是存在的。想要了解它们之 间的 关系,我们先看下面的几段 代码 :

  var  animal  =   new   Animal  (  )  ; 

animal . __proto__  ===  Animal . prototype ; 	 // true 
animal . __proto__ .  hasOwnProperty  (  'eat'  )  ; 	 // true 

animal . constructor  ===  animal . __proto__ . constructor ; 	 // true 
 

通过上面的关系对比可以使用示意图的方式更容易理解。

通过上面的 代码 和示意图我们知道,原型是构造 函数 上的 属性 ,实例可以通过自身的原型链查找到,并且可以 修改 属性 。

2.3 继承

了解了 __proto__ 、 prototype 、 constructor 三者的关系那么我们就要来学习一下构造 函数 的继承了,上面我们定义了 一个 动物的构造 函数 ,但是我们不能直接去 new 一个 实例,因为 new 出来的实例没有任何意义,是 一个 动物实例,没有具体指向。这时我们需要创建 一个 子类来继承它。这时可以对 Animal 类做个限制:

  function   Animal  ( type )   { 
   if   (  new  . target  ===  Animal )   { 
     throw   new   Error  (  'Animal 类不能被 new,只能被继承!'  ) 
   } 
   this  . type  =  type  ||   '鸟类'  ; 
 } 
Animal . prototype .  eat   =   function  (  )   { 
  console .  log  (  '鸟类吃虫子!'  ) 
 }  ; 

 var  animal  =   new   Animal  (  )  ; 
 //VM260:3 Uncaught Error: Animal 类不能被 new,只能被继承! 
 

既然不能被 new 那要怎么去继承呢?虽然不能被 new 但是我们可以去执行这个构造 函数 啊,比较它本质还是 一个 函数 。执行构造 函数 时 this 的指向就不是当前的实例了,所以还需要对 this 进行绑定。我们定义 一个 子类:Owl(猫头鹰)

  function   Owl  (  )   { 
  Animal .  call  (  this  )  ; 
 } 
 var  owl  =   new   Owl  (  )  ; 
 

通过使用 call 方法 在 Owl 内部绑定 this,这样实例就继承了 Animal 上 this 的 属性 了。但是在 Animal 的原型中还有关于 Animal 类的 方法 ,这些 方法 怎么继承呢?

首先要明确的是不能使用 Owl.prototype = Animal.prototype 这样的方式去继承,上面也说了这会使我们对子类原型 修改 的 方法 会作用到其他子类中去。那么怎么可以实现这一继承呢?这时就需要原型链出场了,我们可以使用 Owl 原型上的原型链指向 Animal 的原型,实例 owl 根据链的查找方式是可以继承 Animal 的原型上的 方法 的。

  function   Owl  (  )   { 
  Animal .  call  (  this  )  ; 
 } 
Owl . prototype . __proto__  =  Animal . prototype ; 

 var  owl  =   new   Owl  (  )  ; 
owl .  eat  (  )  ; 	 // 鸟类吃虫子! 
 

通过原型链的方式还是比较麻烦的,也不优雅,ES6 提供了 setPrototypeOf() 方法 可以实现相同的 效果 :

  // Owl.prototype.__proto__ = Animal.prototype; 
Owl .  setPrototypeOf  ( Owl . prototype ,  Animal . prototype )  ; 
 

这样在子类 Owl 的原型上 增加 方法 不会影响 父类 ,这样也算是比较好的方式 解决 了子类的继承。

3. 小结

本节没有去学习 class 的使用,而是复习了在 ES5 中是怎么定义类的存在的,使用的是构造 函数 的方式来定义 一个 类。在类的实际应用中继承是最为关键的,通过对如何实现构造 函数 中的继承,复习了原型、原型链和构造 方法 。在构造 函数 的继承中,子类不能直接去 new 一个 父类 ,因为这样没有意义。所以我们通过在子类中执行构造 函数 并绑定子类的this继承了 父类 的 属性 ,再通过子类原型的原型链继承了 父类 原型上的 属性 。通过本节的学习我们更加深刻地理解构造 函数 在 JavaScript 中扮演什么样的角色,继而 ES6 提出了 “真正“ 意义上的类,其实本质还是通过原型的方式,下一节我们将具体学习 ES6 的 class。

查看更多关于ES6+ Class 前置知识的详细内容...

  阅读:36次

上一篇

下一篇

第1节:ES6+ 简介    第2节:ES6 环境配置    第3节:ES6+ let    第4节:ES6+ const    第5节:ES6+ 展开语法    第6节:ES6+ 剩余参数    第7节:ES6+ 解构赋值    第8节:ES6+ 模版字符串    第9节:ES6+ 箭头函数    第10节:ES6+ 数值扩展    第11节:ES6+ isFinite()&isNaN()    第12节:ES6+ Number 对象的方法    第13节:ES6+ Math 对象的扩展    第14节:ES6+ includes()    第15节:ES6+ 字符串的扩展    第16节:ES6+ startsWith()    第17节:ES6+ endsWith()    第18节:ES6+ repeat()    第19节:ES6+ padStart()    第20节:ES6+ padEnd()    第21节:ES6+ trim()    第22节:ES6+ Array.from()    第23节:ES6+ of()    第24节:ES6+ find()和findIndex()    第25节:ES6+ copyWithin()    第26节:ES6+ fill()    第27节:ES6+ isArray()    第28节:ES6+ 对象的扩展    第29节:ES6+ flat()    第30节:ES6+ 可选链操作符    第31节:ES6+ Object.is()    第32节:ES6+ Object.assign()    第33节:ES6+ Object.keys()    第34节:ES6+ Object.values()    第35节:ES6+ Object.entries()    第36节:ES6+ 数据结构扩展    第37节:ES6+ Set    第38节:ES6+ WeakSet    第39节:ES6+ Map    第40节:ES6+ WeakMap    第41节:ES6+ Symbol    第42节:ES6+ for...of    第43节:ES6+ 迭代协议    第44节:ES6+ 实现一个简版的 Promise    第45节:ES6+ Promise 基础    第46节:ES6+ Promise 进阶    第47节:ES6+ Generator 基础    第48节:ES6+ Generator 函数应用    第49节:ES6+ async/await    第50节:ES6+ Class 前置知识    第51节:ES6+ Class    第52节:ES6+ Proxy    第53节:ES6+ Reflect(一)    第54节:ES6+ Reflect(二)    第55节:ES6+ 模块化(一)    第56节:ES6+ 模块化(二)    第57节:ES6实战1-实现Vue3 reactive 源码    第58节:ES6实战2-实现 Vue3 effect 源码    第59节:ES6 实战2-封装请求    第60节:ES6+ 实战3-代码整洁之道    第61节:ES6 Map原理分析    第62节:ES6module语法加载importexport    第63节:ES6的循环与可迭代对象示例详解