高手总结的javascript范式好文,摘录保存,以备忘,感谢大大的辛苦总结和奉献。
Maintainable code:
1, Is readable
2, Is consistent
3, Is predictable
4, Looks as if it was written by the same person
5, Is documented
Minimizing Globals
Always declare variables with var.
1
function foo() {
var a = b = 0; //
错误!在这里b是全局变量
}
通过var声明的全局变量无法用delete删除(
测试之后发现同样可以删除,疑问?)
没有使用var声明的全局变量可以使用delete删除
2,
hoisting,使用var声明的变量,会被提升到最上面
myname = 'global';
function func() {
alert(myname); //
'undefined'
var myname = 'local';
alert(myname); //
'local'
}
func();
上面代码中,func函数中的myname会先被声明,所以第一次打印myname会显示undefined
3,for循环,缓存循环的长度
for(var i=0,
max=myarray.length; i<max; i++) {
// do sth with myarray[i]
}
另外两种策略,使用更少的变量,以递减的方式
(1)
var i, myarray = [];
for(i=myarray.length; i--) {
// do sth with myarray[i]
}
(2)
var myarray = [], i = myarray.length;
while(i--) {
// do sth with myarray[i]
}
4,替换 i++
使用
i = i + 1
或
i += 1
5,使用hasOwnProperty来过滤for循环
var man = {
hands : 2,
legs : 2,
heads : 1
}
if(typeof Object.prototype.clone == 'undefined') {
Object.prototype.clone = function() { }
}
for( var i in man) {
if(man.hasOwnProperty(i)) { //
将不是man的属性过滤掉,在这里会将
clone
函数过滤
console.log(i, ':',
man[i]);
}
}
也可以使用另外一种方式来验证,这种方式避免了当你在man中重新声明hasOwnProperty之后引起的命名冲突
for( var i in man) {
if(
Object.prototype.hasOwnProperty.call(man, i)) { //
将不是man的属性过滤掉,在这里会将
clone
函数过滤
console.log(i, ':', man[i]);
}
}
也可以将
Object.prototype.hasOwnProperty缓存起来
var i, hasOwn = Object.prototype.hasOwnProperty;
for(i in man) {
if(hasOwn.call(man, i)) {
console.log(i, ':', man[i]);
}
}
6,不要使用eval
7,使用parseInt,需要传入第二个参数,指定进制
parseInt('06', 10); 表示使用十进制转换
另外两种更快的转换方式:
+'08' // 结果为8
Number('09') // 结果为9
但如果传入类似'08 hello',parseInt可以返回数字,而其他两种方法只能返回NaN
8,使用字面量创建,而不使用内建构造函数
var car = { goes : 'far' }; // 推荐使用
var car =
new Object(); // 不推荐使用
car.goes = 'far';
9,调用构造函数的时候,如果
忘记写new,那么构造函数的
this属性会绑定到全局变量。
function Waffle() {
this.tastes = 'yummy';
}
var good_morning = Waffle(); // 忘记写 new
typeof good_morning; // 'undefined'
window.tastes; // 'yummy' 被绑定到了全局变量
解决方法:
1,命名约定:构造函数的首字母大写,普通函数首字母小写。
2,使用that:
就是在构造函数中返回一个新的对象
function Waffle() {
var that = {};
that.tastes = 'yummy';
return that;
}
或者直接返回一个字面量对象:
function Waffle() {
return {
tastes : 'yummy'
};
}
这个模式的缺点在于prototype的连接丢失,新生成的对象无法使用通过Waffle.prototype增加的成员
3,检测this是否为构造函数的对象,如果不是,调用new,返回一个对象:
function Waffle() {
if(!(this instanceof Waffle)) {
return
new Waffle();
}
this.tastes = 'yummy';
}
也可以使用arguments.callee代替函数名:
if(!(this instanceof arguments.callee)) {
return new arguments.callee();
}
注意,在ECMAScript5中,arguments.callee已不被使用。
10,验证对象是否为数组
对数组使用typeof,返回 'object',没有什么意义
在ES5中定义了一个新的方法:Array.isArray(),返回true,表示参数为数组。
如果上面方法不可用,则使用Object.prototype.toString()方法:
if(
typeof Array.isArray === 'undefined') {
Array.isArray =
function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
11,ES5提供了
JSON.parse()方法对json进行解析,还有
JSON.stringify()方法将对象序列化成json字符串
12,Error Objects
JS内置了异常构造函数,比如Error(), SyntaxError(), TypeError()等。这些对象拥有下列属性:
name : 异常名称
message : 创建异常对象时传递的参数
函数
// 命名的函数表达式
var add =
function add(a, b) {
return a + b;
};
// 函数表达式, 匿名函数
var add =
function(a, b) {
return a + b;
};
// 函数声明
function foo() {
// function body goes here
}
对象创建模式:
命名空间:
只声明一个全局变量,将其他的变量与函数作为该全局变量的属性与方法:
var MYAPP = {};
MYAPP.Parent =
function() {};
MYAPP.Child =
function() {};
MYAPP.modules = {};
MYAPP.modules.module1 = {};
当使用命名空间模式时,应该注意新建的全局变量是否存在:
var MYAPP = {};
// 不安全的方式
if(typeof MYAPP === 'undefined') {
// 相对安全的方式
var MYAPP = {};
}
var MYAPP = MYAPP || {};
// 更简短的方式
可以使用一个函数来避免这些检查的代码:
var MYAPP = MYAPP || {};
MYAPP.namespace =
function(ns_string) {
var parts = ns_string.split('.'), parent = MYAPP, i;
if(parts[0] === 'MYAPP') {
parts = parts.slice(1);
}
for(i=0; i<parts.length; i += 1) {
if(typeof parent[parts[i]] === 'undefined') {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};
// 支持以下使用方式
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2;
// true
MYAPP.namespace('modules.modules1');
// 不带MYAPP的形式
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property'); // 长名空间
声明依赖:
在YUI2中,有一个全局变量YAHOO,调用需要的模块时会写如下代码:YAHOO.util.Dom,或者 YAHOO.util.Event,一个好的做法是将这些模块的声明放在函数或者模块的开头,并将它们赋值给一个局部变量,比如:
var myFunction =
function() {
var event = YAHOO.util.Event,
dom = YAHOO.util.Dom;
}
这样的好处有:
1,更好的看清依赖层次,能够很方便的检查某些模块是否引入
2,使用局部变量比全局变量(比如YAHOO)快,比调用全部变量的属性(比如YAHOO.util.Dom)更快
3,高级的压缩器比如YUICompressor和Google Closure会将局部变量改写成更短的形式,但是不会修改全局变量,这样可以获得更好的压缩效果
私有变量:
function Gadget() {
var name = 'iPod';
this.getName =
function() {
return name;
};
}
var toy =
new Gadget();
console.log(toy.name);
// undefined
console.log(toy.getName());
// 'iPod'
在Gadget函数里,name为私有变量,只能通过getName方法访问,而getName函数被称为"
Privileged Method",指的是那些能够访问私有变量的公共方法
当私有变量为一个数组或者对象时,如果通过公共方法访问,那么外部代码就能够修改这个变量,因为它是按引用传递:
function Gadget() {
var specs = {
screen_width : 320,
screen_height : 480,
color : 'white'
};
this
.getSpecs =
function
() {
return
specs;
};
}
var toy =
new Gadget(),
specs = toy.getSpecs();
specs.color = 'black';
specs.price = 'free';
// 这样,Gadget中的私有变量specs就被改变了。
防止这样的事情发生,你可以:
1,让getSpecs方法返回一个新对象,该对象里只包含感兴趣的数据,这叫做
Principle of Least Authority(POLA)
2,当你需要传递全部数据时,使用一个specs的拷贝。
对象字面量与隐私:
如果使用字面量来创建对象,那么需要使用一个函数将私有变量包装起来:
var myobj;
// this will be the object
(
function() {
// private members
var name = 'my, oh my';
myobj = {
// privileged method
getName :
function() {
return name;
}
};
}());
另一种样式:
var myobj = (
function() {
// private members
var name = 'my, oh my';
// implement the public part
return {
getName :
function() {
return name;
}
};
}());
原型与隐私:
在构造函数中使用的私有变量的一个坏处:在使用构造函数创建对象时,这些私有变量会被重复创建,可以将这些私有变量创建在原型上,这样它们就会被对象所有的实例共享:
function Gadget() {
var name = 'iPod';
this.getName =
function() {
return name;
};
}
Gadget.prototype = (
function() {
// private member
var browser = 'Mobile Webkit';
// public prototype members
return {
getBrowser :
function() {
return browser;
}
}
}());
revelation pattern 没看懂~~
模块模式:
该模式是几种模式的组合:
名空间、立即函数、私有成员、声明依赖……
第一步是声明一个名空间,使用上面的
namespace()函数,下一步是定义模块,然后是增加一些公共接口,最后完整的代码如下:
MYAPP.namespace('MYAPP.utilities.array');
MYAPP.utilities.array = (
function() {
// dependencies
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// private properties
array_string = '[object Array]',
ops = Object.prototype.toString,
// private methods
inArray :
function(haystack, needle) {
for
(var i=0, max=haystack.length; i<max; i+=1) {
if
(haystack[i] === needle) {
return
i;
}
}
return
-1;
},
isArray :
function(a) {
return ops.call(a) === array_string;
};
// revealing public API
return {
isArray : isArray,
indexOf : inArray
};
}());
模块构造函数,与上面不同的地方在于立即函数会返回一个函数而不是对象:
MYAPP.namespace('MYAPP.utilities.Array');
MYAPP.utilities.Array = (function() {
// dependencies
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// private properties and methods...
Constr;
// public API -- constructor
Constr =
function(o) {
this.elements =
this.toArray(o);
};
// public API -- prototype
Constr.
prototype = {
constructor : MYAPP.utilities.Array,
version : '2.0',
toArray :
function(obj) {
for(var i=0, a=[], len=obj.length; i<len; i+=1) {
a[i]= obj[i];
}
return a;
}
};
// return the constructor to be assigned to the new namespace
return Constr;
}());
使用这个构造函数的方法:
var arr =
new MYAPP.utilities.Array(obj);
在模块中引入全局变量:
MYAPP.utilities.module = (function(app, global) {
// references to the global object and to
// the global app namespace object are now localized
}(MYAPP, this));
沙箱模式:
119页,暂时没有理解
静态成员:
公有静态成员:
// constructor
var Gadget =
function() {};
// a static method
Gadget.isShiny =
function() {
return 'you bet';
};
// a normal method added to the prototype
Gadget.
prototype.setPrice =
function(price) {
this.price = price;
};
// calling a static method
Gadget.isShiny();
// 'you bet'
// creating an instance and calling a method
var iphone =
new Gadget();
iphone.setPrice(500);
typeof Gadget.setPrice;
// 'undefined'
typeof iphone.isShiny;
// 'undefined'
如果想让实例也可以调用静态方法,那么可以在原型上增加一个方法,作为门面指向原来的静态方法:
Gadget.prototype.isShiny = Gadget.isShiny;
iphone.isShiny();
// 'you bet'
在上面的情况中需要注意,如果在静态方法中使用了this,那么调用Gadget.isShiny()会使this绑定到Gadget的构造函数上,调用iphone.isShiny()会使this绑定到iphone上。
下面的代码显示了如何使一个方法在被构造器与实例调用时产生不同结果,这里使用instanceof来检测:
// constructor
var Gadget =
function(price) {
this.price = price;
};
// a static method
Gadget.isShiny =
function() {
// this always works
var msg = 'you bet';
if(
this instanceof Gadget) {
// this only works if called non-statically
msg += ', it costs $' + this.price + '!';
}
return msg;
};
// a normal method added to the prototype
Gadget.prototype.isShiny =
function() {
return Gadget.isShiny.call(
this);
};
Gadget.isShiny();
// 'you bet'
var a =
new Gadget('499.99');
a.isShiny(); // 'you bet, it costs $499.99!';
私有静态函数:
和私有成员一样,需要一构建一个包含私有成员的闭包,然后返回一个函数:
// constructor
var Gadget = (
function() {
// static variable/property
var counter = 0, NewGadget;
// this will become the new constructor implementation
NewGadget =
function() {
counter += 1;
};
// a privileged method
NewGadget.prototype.getLastId =
function() {
return counter;
};
// overwrite the constructor
return NewGadget;
}());
var iphone =
new Gadget();
iphone.getLastId();
// 1
var ipod =
new Gadget();
ipod.getLastId();
// 2
var ipad =
new Gadget();
ipad.getLastId();
// 3
常量:
var constant = (
function() {
var constants = {},
ownProp = Object.prototype.hasOwnProperty,
allowed = {
string : 1,
number : 1,
boolean : 1
},
prefix = (Math.random() + '_').slice(2);
return {
set :
function(name, value) {
if(
this.isDefined(name)) {
return false;
}
if(!ownProp.call(allowed,
typeof value) {
return false;
}
constants[prefix + name] = value;
return true;
},
isDefined :
function(name) {
return ownProp.call(constants, prefix + name);
},
get :
function(name) {
if(
this.isDefined(name)) {
return constants[prefix + name];
}
return null;
}
};
}());
测试上面的方法:
// check if defined
constant.isDefined('maxWidth');
// false
// define
constant.set('maxWidth', 480);
// true
// check again
constant.isDefined('maxWidth');
// true
// attempt to redefine
constant.set('maxWidth', 320);
// false
// is the value still intact?
constant.get('maxWidth');
// 480
链式调用:
当没有明确的返回值时,可以返回this:
var obj = {
value : 1,
increment :
function() {
this.value += 1;
return
this;
},
add :
function(v) {
this.value += v;
return
this;
},
shout :
function() {
alert(
this.value);
}
};
// chain method calls
obj.increment().add(3).shout();
// 5
// as opposed to calling them one by one
obj.increment();
obj.add(3);
obj.shout();
// 5
method方法:
if(typeof Function.prototype.method !== 'function') {
Function.prototype.method =
function(name, implementation) {
this.prototype[name] = implementation;
return
this;
};
}
使用:
var Person =
function(name) {
this.name = name;
}.method('getName',
function() {
return
this.name;
}).method('setName',
function(name) {
this.name = name;
return
this;
});
代码重用模式:
在JavaScript中有经典和现代两种继承模式——Classical VS Modern Inheritance Patterns
在下面的代码中,定义了Parent与Child的构造函数,其中Child需要继承Parent,这里使用了inherit方法
// the parent constructor
function Parent(name) {
this.name = name || 'Adam';
}
// adding functionality to the prototype
Parent.prototype.say =
function() {
return
this.name;
};
// empty child constructor
function Child(name) {}
// inheritance magic happens here
inherit(Child, Parent);
首先使用
第一种经典继承方式:设置原型来实现inherit函数:
function inherit(C, P) {
C.prototype =
new P();
}
这种方式的缺点为:第一个缺点,子类不仅获得了prototype上的属性,还获得了绑定到this上的属性,一般来说绑定给this的属性是不需要重用的。第二个缺点是无法在子类初始化时向父类传递参数。
第二种经典继承方式:借用构造函数,解决了向父类传递参数的问题:
function Child(a, c, b, d) {
Parent.apply(
this, arguments);
}
这样,Child就会只继承绑定到this上的属性,而不会继承prototype上的。
用这种方式与第一种截然不同,第一种方式继承的父类属性是通过prototype来访问,而这第二种借用构造函数的方式,是直接将属性绑定到子类的this上,换句话说,使用hasOwnProperty测试两种方式继承的属性,第一种方式会得到结果false,第二种结果是true
这种方式的缺点在于,父类prototype上的成员不会被继承。
优点是,真正从父类继承了成员,而不是获得它们的引用
第三种继承方式:借用构造函数并设置原型,是前两种方式的组合:
function Child(a, c, b, d) {
Parent.apply(
this, arguments);
}
Child.prototype =
new Parent();
缺点:父类的构造函数被调用了两次
第四种继承方式:分享原型对象:
function inherit(C, P) {
C.prototype = P.prototype;
}
缺点:如果子类修改了原型对象,那么会影响到父类,因为现在它们共用一个原型对象了。
第五种继承方式:临时构造函数,它解决了上面提到的分享原型对象问题,它使用一个空的函数F作为代理,F的原型对象指向父类,子类的原型对象指向F的一个实例:
function inherit(C, P) {
var F =
function() {};
F.prototype = P.prototype;
C.prototype =
new F();
}
下面的代码增加了uber属性,可以通过它访问父类;还有对子类的构造函数进行了重新指定:
function inherit(C, P) {
var F =
function() {};
F.prototype = P.prototype;
C.prototype =
new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
也可以不必每次都创建F函数,使用一个匿名函数和闭包即可:
var inherit = (
function() {
var F =
function() {};
return
function(C, P) {
F.prototype = P.prototype;
C.prototype =
new
F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
});
Klass:
var klass =
function(Parent, props) {
var Child, F, i;
// 1. new constructor
Child =
function() {
if(Child.uber && Child.uber.hasOwnProperty('__construct')) {
Child.uber.__construct.apply(
this, arguments);
}
if(Child.prototype.hasOwnProperty('__construct')) {
Child.prototype.__construct.apply(
this, arguments);
}
};
// 2. inherit
Parent = Parent || Object;
F =
function() {};
F.prototype = Parent.prototype;
Child.prototype =
new F();
Child.uber = Parent.prototype;
Child.prototype.constructor = Child;
// 3. add implementation methods
for( i in props) {
if(props.hasOwnProperty(i)) {
Child.prototype[i] = props[i];
}
}
// return the 'class'
return Child;
};
现代继承方式-
原型式继承:
没有类的概念,有别于经典继承方式。
// object to inherit from
var parent = {
name : 'Papa'
};
// the new object
var child = object(parent);
// testing
alert(child.name);
// 'Papa'
实现object方法:
function object(o) {
function F() {}
F.prototype = o;
return
new F();
}
在ES5中,Object.create()提供了同样的功能。
使用复制属性来实现继承:
function extend(parent, child) {
var i;
child = child || {};
for( i in parent) {
if(parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}
上面的实现称为"
浅拷贝(shallow copy)",如果parent中有为对象的属性,那么对象就会以引用的方式复制给child,那么在child中对这些属性修改时,会对parent造成影响。
下面是"
深拷贝(deep copy)"的实现:
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = '[object Array]';
child = child || {};
for( i in parent) {
if(parent.hasOwnProperty(i)) {
if( typeof parent[i] === 'object') {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
}
else {
child[i] = parent[i];
}
}
}
return child;
}
Mix-ins:
可以将多个对象的属性复制到一个对象中:
function mix() {
var arg, prop, child = {};
for(arg = 0; arg < arguments.length; arg += 1) {
for(prop in arguments[arg]) {
if(arguments[arg].hasOwnProperty(prop)) {
child[prop] = arguments[arg][prop];
}
}
}
return child;
}
例如:
var cake = mix(
{eggs : 2, large : true},
{butter : 1, salted : true},
{flour : '3 cups'},
{sugar : 'sure!'}
};
设计模式:
单例模式:
最终要实现这样的效果:
var uni = new Universe();
var uni2 = new Universe();
uni === uni2;
// true
可以使用静态属性实现:
function Universe() {
// do we have an existing instance?
if(typeof Universe.instance === 'object') {
return Universe.instance;
}
// proceed as normal
this.start_time = 0;
this.bang = 'Big';
// cache
Universe.instance =
this;
}
这种方式的缺点是instance是公共属性,能够被外界所修改,可以使用闭包来解决这个问题:
function Universe() {
// the cached instance;
var instance =
this;
// proceed as normal
this.start_time = 0;
this.bang = 'Big';
// rewrite the constructor
Universe =
function() {
return instance;
};
}
这样做的缺点在于重新定义了构造函数,而返回的实例是定义前的构造函数,这样导致重新定义后的构造函数无法通过prototype对象来共享属性或者方法,解决办法:
function Universe() {
// the cached instance
var instance;
// rewrite the constructor
Universe =
function Universe() {
return instance;
};
// carry over the prototype properties
Universe.prototype =
this;
// the instance
instance =
new Universe();
// reset the constructor pointer
instance.constructor = Universe;
return instance;
}
另外一个更好的解决方法:
var Universe;
(
function() {
var instance;
Universe =
function Universe() {
if(instance) {
return instance;
}
instance =
this;
};
}());
工厂模式:
DOM与浏览器模式:
DOM:
DOM访问:
1,避免在循环中访问DOM
2,将DOM引用赋值给局部变量,然后操作这个变量
3,尽量使用选择器API,即
document.querySelector或
document.querySelectorAll
4,遍历HTML集合时将长度缓存
操作DOM会引起浏览器的
repaint和
reflow,这些都是消耗十分严重的操作,推荐在添加子节点时,对
fragment操作,然后将
fragment添加进dom树中
当需要更新一个节点时,可以使用
clone方法将原节点复制,然后操作clone的节点,操作结束后,再将原节点替换掉。