继承、原型链、在组件开发上的应用
大半年前写过关于原型链和继承的笔记了,不过今天看到一篇关于组件化开发页面的博客,14年的,那时候react、angular还没那么火吧,略有感触,还是写篇简单的读后感吧。
博客原文:【组件化开发】前端进阶篇之如何编写可维护可升级的代码
创建对象和对象继承:
我一直觉得,前端js有两个地方是必须掌握的,一个是对象,一个是函数。其他相关概念,比如原型、继承、闭包等都是二者的延伸和应用罢了。虽然说函数也是对象,但它在javascript这门语言中和具体实践中已经远远超出对象的范畴,除了可以作为函数的输入和返回结果外,其可嵌套、可构成闭包形成隔绝作用域、可被调用(call、apply和IE9+支持的bind)等特性让我们可以轻易地构建灵活复杂的web应用。
不过今天想讲的是js的继承。事实上“继承”这个概念,本来就是为了代码的复用和扩展。我们让新的对象能拥有(或说能使用)已有对象的属性or方法,这就是继承,当然,我们往往也会对新对象进行功能扩展。
JS中的对象都有一个原型属性,指向另一个对象。这个对象中保存了一些属性和方法,以之为原型的实例都能共享到这些属性方法。A的原型是B,B也可以有个原型是C,A->B->C->...就构成了一个原型链。
A能通过原型链获取到B、C中的属性方法。如果其中存在同名属性名,则调用时,原型链下游的属性会把上游的属性覆盖。B、C有同名方法,调用时只会调用B的方法。如果A已经有同名方法,则不会遍历原型链。
这就是js中关于原型的基本常识。JS中的内置的对象如Array、Function等,其原型的原型也都指向Object.prototype。
在日常,我们创建对象时,一般也就4种方式:
1.直接创建字面量对象var o = {a:1}; 此时o默认原型指向Object.prototype
2.var o = Object.create(ProtoObj); 此时o的原型指向ProtoObj
3.var o = new Foo();此时o.__proto__ 指向 Foo.prototype (Foo构造函数的原型属性为prototype,但其实例的原型属性是__proto__)
4.ES6的class定义类,并通过extends 继承父类. 但ES6的class只是语法糖而已。
初学者通过敲示例,或用简单的对象来做试验,很快就能弄清楚是怎么一回事。但在平时工作中,我们面对较多的还是写页面逻辑、绑定事件、样式切换、动画、后台交互数据等等,需要用的继承的地方并不多。就好像我们也很少在具体业务中用到Object.defineProperty()一样。
但Vue框架背后的响应式原理,就是通过生成Vue实例时,对其data项的对象使用defineProperty()方法设置get/set函数,每当data中的数据被修改的时候观察者对视图进行刷新。
有些只是在基础场景中体现不出价值,但在特定场景中善于利用往往可以成为利器。
在MVVM框架出现以前,前辈们写组件、插件,往往遵循非常传统的方式,在一个文件的闭包中写一段功能代码,返回一个对象或函数。然后在其他页面中引入并调用这个接口。
可是,如果应用中不同的地方都可以复用这个组件的抽象部分,我们就可以只创建一个基本版的组件。然后在需要的地方对它进行定向功能拓展就好了。
1 var AbstractView = create({ 2 initialize: function (opts) { 3 opts = opts || {}; 4 //... 5 } 6 }); 7 8 //特定组件 9 var baseComponent = create(AbstractView, { 10 //... 11 } 12 }); 13 //带特定行为的组件 14 var componentA = create(baseComponent, { 15 //渐进增强功能... 16 } 17 }); 18 19 var v1 = new baseComponent(); 20 v1.show(); 21 22 var v2 = new componentA(); 23 v2.show();
然而,使用react的话,我们就不用这么麻烦走原声js的构建,因为每个class都是一个组件,通过HOC(高级组件)我们可以由一个基类扩展其功能生成另一个子类,实现按需增强。