javascript常用的设计模式大概有以下几种:
单体模式/单例模式
工厂模式
观察者模式(发布者-订阅者模式)
策略模式
模板模式
代理模式
外观模式
使用设计模式是为了让代码可重用性更高,让代码更容易让人理解、保证代码可靠性。
单例模式定义了一个对象的创建过程,这个对象只有一个单独的实例,并提供一个访问它的全局访问点。单例保证一个类只有一个实例。实现的方法就是先判断这个实例是否存在,存在则直接返回,不存在则创建一个新的实例再返回。
单例的实现方式有很多种:
最简单的方式如:
var Singleton = function(name) {
this.name = name;
this.instance = null;
}
Singleton.prototype.getName = function() {
console.log(this.name);
}
Singleton.getInstance = function(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
var s1 = Singleton.getInstance('s1');
var s2 = Singleton.getInstance('s2');
console.log(s1 === s2);
或者使用闭包形式创建单例模式:
var Singleton = function(name) {
this.name = name;
}
Singleton.prototype.getName = function () {
console.log(this.name);
}
Singleton.getInstance = (function(name){
var instance = null;
return function(name) {
if(!instance) {
instance = new Singleton(name);
}
}
})()
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b);
应用场景
单例模式是一种常用的设计模式,有一些对象我们只需要一个,比如全局对象等。
我们可以借用它来划分命名空间;
借助单例模式,可以把代码组织的更为一致,方便阅读与维护。
工厂模式的定义:提供创建对象的接口。
应用场景:
对象的构建十分复杂
需要依赖具体环境创建不同实例
处理具有大量相同属性的小对象。
在了解原型之前,必须先弄清几个概念:
对象
js中的对象分为两种:
普通对象
函数对象
原型对象
每创建一个函数都会有一个prototype属性,这个属性是一个指针,指向一个对象。原型对象包含特定类型的所有实例共享的属性和方法。
来看看一张比较容易看得懂的图,深度理解对象的原型:
方法(Function)是对象,方法的原型(Function.prototype)是对象。
对象具有__proto__属性,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这样保证了实例能够访问在构造函数原型中定义的属性和方法。
方法也是对象,除了和其他对象一样拥有__proto__属性之外,还有自己特有的属性—-原型属性(prototype)。这个属性是一个指针,指向一个对象(及原型对象),这个对象包含了所有实例共享的属性和方法。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
再来看看上面那张图:
构造函数Foo()的原型属性Foo.prototype指向原型对象,在原型对象里有共有的属性和方法,所有构造函数声明的实例(这里是f1, f2)都可以共享这个方法。
Foo.prototype保存这实例共享的方法,有一个指回构造函数的指针constructor.
f1, f2是对象Foo的实例,对象的实例有__proto__属性,指向对象的构造函数的原型对象,这样就可以访问原型对象的所有方法了。
BTW,Foo()构造函数除了是方法,也是对象(函数对象),也有__proto__属性,那么这个__proto__指向谁呢?
函数的__proto__属性指向函数的构造函数的原型对象。函数的构造函数就是Function,因此,函数的__proto__属性指向Function.prototype.
除了Foo(), Function()和Object()都是一样的道理。
原型对象也是对象,那它(比如,Foo.prototype,Function.prototype)的__proto__属性指向谁呢?
同理,指向它的构造函数的原型对象,这里是Object.prototype。
最后,Object.prototype的__proto__属性指向null。
总结:
1. 对象有属性__proto__,它指向对象的构造函数的原型对象。
2. 函数有prototype属性,函数的prototype属性指向函数的原型对象,因为函数也是对象,函数的__proto__同样指向函数的构造函数的原型对象。
首先,看一张图了解一下实例、原型对象和构造函数之间的关系。
从上图可以看出,每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(constructor),而实例对象都包含一个指向原型对象的__prototype__内部指针。
如果让原型对象等于另一个类型的实例,那么此时的原型对象将包含一个指向另一个原型对象的(proto)指针,另一个原型对象也包含指向另一个构造函数的指针(constructor)。加入
另一个原型又是另一个类型的实例。。。。。。这就构成了实例与原型的链条。也就是原型链。
看图:
举例说明:
function Animal() {
this.type = 'animal';
}
Animal.prototype.getType = function() {
return this.type;
}
function Dog() {
this.name ='wangwang';
}
Dog.prototype = new Animal();
var wangcai = new Dog();
wangcai.getType(); // 'animal'
// 原型链的关系:
wangcai.__proto__ === Dog.prototype
Dog.prototype.__proto__ === Animal.prototype
Animal.prototye.__proto__ === Object.prototype
Object.prototype.__proto__ === null
js中实现继承主要依靠原型链查找
原型继承(prototype-based inheritance)的步骤大概是这样:
每一个函数F都有一个原型对象(prototype)F.prototype
每一个函数都可以通过new 关键字来创建一个类构造函数,new F()会产一个一个对象O
在调用对象的某个属性和方法时,比如O.xxx, 首先会查找这个对象自身有没有这个属性或者方法,没有就会去(这个对象的构造函数的原型对象中查找),也就是找O的构造函数F的原型对象F.prototype.xxx。
原型对象F.prototype.xxx也是一个对象,查找F.prototype.xxx会重复步骤3.
随着前端开发的不断发展,互联网业务的复杂度越来越高,JS模块化开发已经变得越来越重要。
我们在前端开发过程中,使用统一的编写方式进行模块化,可以对巨大系统和复杂业务的代码进行很好的组织和管理,方便多人的合作。
JS中的模块化规范包括(CommonJS, AMD, CMD)。
(1)CommonJS
CommonJS是一个有志于构建Javascript生态圈的组织。整个社区致力于提高javascript程序的可移植性和可交换性,无论是在服务端还是浏览器端。
a group with a goal of building up the javascript ecosystem for web browsers, desktop and command line apps and in the browser.
CommonJS是一个更偏向于服务器端的规范。Node.js采用了这个规范。根据CommonJS规范,一个单独的文件就是一个模块。
CommonJS规范的主要内容:模块必须通过module.exports导出对应的变量或接口,通过require()导入其他模块的输出到当前模块。
// moduleA.js
module.exports = function(value) {
return value++;
}
// moduleB.js
var moduleA = require('./moduleA');
var result = moduleA(2);
优点
* 便于服务器端重用
* NPM中已经提供近20W个模块包
* 简单并容易使用
缺点
* 使用同步的方式加载资源,阻塞加载,不适合在浏览器环境中使用。
* 不能非阻塞的并行加载多个模块
随着前端生态jade、less、scss、typescript和webpack等工具的完善,前端组件化开发效率已经有了很大的提升。
特别是像Ant Design、Element UI、iView这些优秀的前端组件库的流行,更是将组件化开发效率发挥到了极致。特别是在做管理系统的页面,脚手架的搭建、添加依赖包、配置路由、创建页面、引入组建,很快就可以搭建一个系统。
什么是前端组件化开发
可能你遇到过这种情况,一个页面的js文件可能包含几百甚至上千行代码。这种代码又臭又长,读懂已经很难了,更别说维护。
这个时候就需要利用组件化开发,进行功能拆分,封装组件,单独维护。
前端组件化开发就是将页面的某一部分独立出来,将这一部分的数据层(Model),视图层(View),控制层(Controller)用黑盒的形式全部封装到一个组件内,然后暴露一些开箱即用的属性和函数供外部组件调用。
一个前端组件,包含了html、css、javascript,基本上涵盖了组件的所有内容。外部只要按照组件设定的属性、函数及事件处理进行调用即可,完全不比考虑内部实现逻辑。组件是一个完全的黑盒。
有一些比较常用的前端组件,如vue-router、vuex、react-router,redux等,都是基于Vue和React的组件,它们只专注于路由、状态存储的工作,并且把这些事情做好。
只要利用好组件化开发,开发一个页面就像搭积木一样,将各个组件拼接到一起,最后融合到一起就是一个完整的系统。
组件化开发的优点
降低系统各个功能的耦合性,提高了功能内部的聚合性,降低开发复杂度,提高开发效率。
如何设计一个组件
专一
一个组件只专注做一件事情,并且把这件事做好
可配置型
组件除了要展示默认的内容,还需要做一些动态的适配,最基本的方式是通过属性向组件传递配置的值,然后在组件初始化的生命周期内,通过读取属性的值做对应的修改;还有一些方法,通过调用组件暴露的函数向函数传递有效的值;向组件传递特定事件,并在组件内监听该事件。
在做可配置性时,为了让组件更健壮,保证组件接收到的是有效的属性和参数。
组件的生命周期
一个组件,需要明确知道在生命周期的不同阶段做该做的事。
React提供了一些生命周期函数:componentWillMount, conponentDidMount, componentWillRecieveProps, shouldComponentUpdate, componentWillUpdate, componentDidUpdate, render, componentWillUnmount,…
Vue中提供了一些生命周期函数:beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, destroyed,…
CSS方面
随着现在web的发展,大部分浏览器厂商已经做好了兼容性。对开发人员来讲,不用刻意去关注某个样式的兼容写法,而是尽量在开发过程中,遵循一些规范来避免遇到这些css兼容问题。
margin: 0 auto
ul {margin:0;padding:0;}
尽量使用padding,慎用margin
js方面
evt = evt || window.event;
window.innerHeight || document.documentElement.clientHeight
window.innerWidth || document.documentElement.clientWidth
evt.preventDefault()?evt.preventDefault():evt.returnValue = false;