这一节梳理对象的继承。javascript
咱们主要使用继承来实现代码的抽象和代码的复用,在应用层实现功能的封装。java
javascript 的对象继承方式真的是百花齐放,属性继承、原型继承、call/aplly继承、原型链继承、对象继承、构造函数继承、组合继承、类继承... 十几种,每一种都细讲须要花不少时间,这里大体梳理经常使用的几种。 javascript 中的继承并非明确规定的,而是经过模仿实现的。下面咱们简单梳理几种有表明性的继承。jquery
ECMAScript 5 中引入了一个新方法: Object.create
。能够调用这个方法来建立一个新对象。新对象的原型就是调用 create
方法时传入的参数:编程
let too = { a: 1 } let foo = Object.create(too) console.log(foo.a) // 1
经过使用Object.create
方法, 对象 too 会被自动加入到 foo 的原型上。咱们能够手动模拟实现一个Object.create
相同功能的函数:数组
let too = { a: 1 } function create (prot) { let o = function () {} o.prototype = prot return new o() } let foo = create(too) console.log(foo.a) // 1
或者用更简单直白的方式来写:闭包
function Foo() {} Foo.prototype = { a: 1 } let too = new Foo() console.log(too.a) // 1
原型继承是基于函数的prototype
属性app
function Foo (id) { this.a = 1234 this.b = id || 0 } Foo.prototype.showData = function () { console.log(`${this.a}, id: ${this.b}`) } function Too (id) { Foo.apply(this, arguments) } Too.prototype = new Foo() let bar = new Too(999) bar.showData() // 1234, id: 999
上面构造函数TOO
经过从新指定prototype
属性,指向了构造函数Foo
的一个实例,而后在Too
构造函数中调用Foo
的构造函数,从而完成对构造函数Foo
功能的继承。实例bar
经过属性__proto__
来访问原型链上的共享属性和方法。ide
javascript 中的 class继承又称模拟类继承。ES6中正式引入了 class 关键字来实现类语言方式建立对象。今后咱们也可使用抽象类的方式来实现继承。函数
// 父类 class Polygon { constructor(height, width) { this.height = height; this.width = width; } } // 子类 class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); // 调用父对象的搞糟函数 } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; } } var square = new Square(2);
在JavaScript中没有类的概念,只有对象。虽然咱们使用class
关键字,这让 JavaScript
看起来彷佛是拥有了”类”,可表面看到的不必定是本质,class
只是语法糖,实质仍是原型链那一套。所以,JavaScript中的继承只是对象与对象之间的继承。反观继承的本质,继承即是让子类拥有父类的一些属性和方法,在JavaScript中即是让一个对象拥有另外一个对象的属性和方法。性能
继承的实现是有不少种,这里不一一列举。须要注意的是 javascript 引擎在原型链上查找属性是比较耗时的,对性能有反作用。与此同时咱们遍历对象时,原型上的属性也会被枚举出来。要识别属性是在对象上仍是从原型上继承的,咱们可使用对象上的hasOwnProperty
方法:
let foo = { a: 1 } foo.hasOwnProperty('a') // true foo.hasOwnProperty('toString') // false
使用hasOwnProperty
方法检测属性是否直接存在于该对象上并不会遍历原型链。
javascript 支持的是实现继承,不支持接口继承,实现继承主要依赖的是原型链。
前面咱们讲到的基本是 javascript 怎么实现面向对象编程的一些知识点。
不从概念来说,简单来讲当咱们有属性和方法须要被重复使用,或者属性须要被多个对象共享时就须要去考虑继承的问题。在函数层面,你们一般的作法是使用做用域链来实现内层做用域对外层做用域属性或函数的共享访问。举个栗子吧~~
function car (userName) { let color = 'red' let wheelNumber = 4 let user = userName let driving = function () { console.log(`${user} 的汽车,${wheelNumber}个轮子滚啊滚...`) } let stop = function () { console.log(`${user} 的汽车,${wheelNumber}个轮子滚不动了,嘎。。。`) } return { driving: driving, stop: stop } } var maruko = car('小丸子') maruko.driving() // 小丸子 的汽车,4个轮子滚啊滚... maruko.stop() // 小丸子 的汽车,4个轮子滚不动了,嘎。。。 var nobita = car('大雄') nobita.driving() // 大雄 的汽车,4个轮子滚啊滚... nobita.stop() // 大雄 的汽车,4个轮子滚不动了,嘎。。。
这。。。什么鬼。是否是有种似曾相识的感受,这其实就是经典的闭包
,jquery 年代不少插件 js 库都采用这种方式去封装独立的功能。说闭包也是继承是否是有点勉强,可是 javascript 里函数也是对象,闭包利用函数的做用域链来访问上层做用域的属性和函数。固然像闭包这样不使用this
去实现私有属性比较麻烦, 闭包只适合单实例的场景。再举一个栗子:
function GoToBed (name) { console.log(`${name}, 睡觉了...`) } function maruko () { let name = '小丸子' function dinner () { console.log(`${name}, 吃完晚餐`) GoToBed(name) } dinner() } function nobita () { let name = '大雄' function homework () { console.log(`${name}, 作完做业`) GoToBed(name) } homework() } maruko() nobita() // 小丸子, 吃完晚餐 // 小丸子, 睡觉了... // 大雄, 作完做业 // 大雄, 睡觉了...
像上面栗子中这样,以面向过程的方式将公共方法抽离到上层做用域的用法比较常见, 至少我很长时间都是这么干的。将GoToBed
函数抽离到全局对象中,函数maruko
、nobita
内部直接经过做用域链查找GoToBed
函数。这种松散结构的代码块组织其实跟上面闭包含义是差很少的。
因此依据做用域链来进行公共属性、方法的管理严格意义上不能算是继承, 只能算是 javascript 面向过程的一种代码抽象分解的方式,一种编程范式。这种范式编程是基于做用域链
,与前面讲的继承是基于原型链
的本质区别是属性查找方式的不一样。
全局对象 window 造成一个闭合上下文,若是咱们将整个 window 对象假设为一个全局函数,全部建立的局部函数都是该函数的内部函数。当咱们使用这个假设时不少问题就要清晰多了,全局函数在页面被关闭前是一直存在的,且在存活期间为内嵌函数提供执行环境,全部内嵌函数都共享对全局环境的读写权限。
这种函数调用时命令式的,函数组织是嵌套的,使用闭包(函数嵌套)的方式来组织代码流是无模式的一种常态。