类型
• 原始类型:我们可以直接使用值。
ο string
ο number
ο boolean
ο null
ο undefined
• 复合类型:我们通过`引用`对值进行间接访问。
ο object
ο array
ο function
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
Objects
• 使用{}创建对象。
var item = {};
• 不要使用保留字作为关键字。
Arrays
• 使用[]创建数组
// bad
var items = new Array();
// good
var items = [];
• 如果你不知道数组长度,使用Array#push。
var someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
• 当你需要复制数组的时候,请使用Array#slice。
var len = items.length,
itemsCopy = [],
i;
// bad
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
itemsCopy = items.slice();
Strings
• 对于字符串,我们使用单引号''。
• 当我们在编程的时候,需要拼接出一个字符串,我们可以使用Array#join 代替字符串连接。尤其是对IE浏览器。
var items,
messages,
length, i;
messages = [{
state: 'success',
message: 'This one worked.'
},{
state: 'success',
message: 'This one worked as well.'
},{
state: 'error',
message: 'This one did not work.'
}];
length = messages.length;
// bad
function inbox(messages) {
items = '<ul>';
for (i = 0; i < length; i++) {
items += '<li>' + messages[i].message + '</li>';
}
return items + '</ul>';
}
// good
function inbox(messages) {
items = [];
for (i = 0; i < length; i++) {
items[i] = messages[i].message;
}
return '<ul><li>' + items.join('</li><li>') + '</li></ul>';
}
Functions
• 绝对不要在非函数块(if,while)申明一个函数。我们可以把函数申明变成一个函数表达式。
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
if (currentUser) {
var test = function test() {
console.log('Yup.');
};
}
• 绝对不要把一个参数命名为arguments,arguments参数是函数作用域内给出的一个特殊变量,如果你把参数命名为arguments,那么这个参数就会覆盖它原有的特殊变量。
Properties
• 当访问属性的时候,我们使用点(.)操作符。
• 当以变量的方式访问属性的时候,用下标符号([])。——除非特殊需求,否则尽量避免使用obj[variable]的方式进行属性访问。
var luke = {
jedi: true,
age: 28
};
function getProp(prop) {
return luke[prop];
}
var isJedi = getProp('jedi');
Variables
• 总是使用var定义变量,否则会导致产生隐含全局变量。我们要尽量避免污染全局变量命名空间。
汤姆大叔—javascript系列文章中提到"JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的。"
• 使用一个var定义多个变量,每个变量在一个新行上。
// good
var items = getItems(),
goSportsTeam = true,
dragonball = 'z';
• 用var定义多个变量的时候,把不进行赋值的变量放置到最后——这是相当有益的。尤其是当你的变量需要赋前面变量值的时候。
// good
var items = getItems(),
goSportsTeam = true,
dragonball,
length,
i;
• 把你的赋值变量放置在当前作用域的顶端。这将避免变量声明和hoisting(悬置/置顶解析/预解析)的问题。
Hoisting
汤姆大叔:
1、JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting(悬置/置顶解析/预解析)。
2、对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。
• 匿名表达式会自动提升它们的变量名称(也就是说在var anonymous上面,example函数就已经知道有这个变量了),但是它们的函数体不会。
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function() {
console.log('anonymous function expression');
};
}
• 命名函数表达式也会提升它们的变量名称,而它们的函数名称和函数体不会这样做。
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('Flying');
};
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
};
}
}
• 注意:函数声明会提升它们的变量名称还有它们的函数体。
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
Conditional Expressions & Equality
• 使用 === 和 !== 代替==和!=。
== 和 != 会进行隐式类型转换,所以建议使用===和!==。
• 强制使用对象的特性(ToBoolean)得到条件表达式的值,大致遵循以下简单规则。
◊ Objects 得到的值是true。
◊ Undefined得到的值是false。
◊ Null得到的值是false。
◊ Booleans得到的值是Boolean值(呵呵,当然)。
◊ Numbers 得到的值是:如果是+0,-0,或者NaN就是false,否则就是true。
◊ Strings 得到的值是:如果是'',就是false,否则就是true。
if ([0]) {
// true
// An array is an object, objects evaluate to true
}
• 使用快捷方式。
// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
• 有{}的代码,我们换行处理。
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function() { return false; }
// good
function() {
return false;
}
Comments
• 对于多行注释使用/** ... */。包含描述信息、参数类型和返回值。
• 对于单行注释使用//。单行注释单独放置在一个新行上。在注释前面放置一个空行。
• 对于一些问题,注释前加FIXME或TODO,这样将快速帮助开发者快速明白代码意图。
• 使用 // FIXME: 注释问题
function Calculator() {
// FIXME: shouldn't use a global here
total = 0;
return this;
}
• 使用 // TODO: 注释问题的解决方案
function Calculator() {
// TODO: total should be configurable by an options param
this.total = 0;
return this;
}
Constructors
• 用方法扩展对象,而不是用一个新对象。
function Jedi() {
console.log('new jedi');
}
// bad
Jedi.prototype = {
fight: function fight() {
console.log('fighting');
},
block: function block() {
console.log('blocking');
}
};
// good
Jedi.prototype.fight = function fight() {
console.log('fighting');
};
Jedi.prototype.block = function block() {
console.log('blocking');
};
直接初始化法
优点:
1、userData本身就是对象实例。——上来就实例化一个类出来,也不怕浏览器受不了。
2、代码紧凑。
3、编程效率高。
缺点:
1、代码的重用性比较差。
2、不符合面向对象的编程思路。——设计模式这种玩意,经常是在类上,弄来弄去。这种直接在对象上使用,够呛!
总结:应避免使用该方法创建自定义对象。
构造函数法
使用方法:需要使用"new 和 构造函数"创建实例。
优点:
只有new的时候,才分配内存。如果不new,浏览器一辈子都不会给你内存资源的。——想要就要,不想要就不理她!
Object表达式法
类似于第一种"直接初始化法"。——其实是一种变种。
上面那一坨红色标出来的部分,让人看着眼晕。——这代码让谁看了,估计都要狂吐血!
匿名函数法
上面那个是我经常用的,我在项目中写了一堆的扩展,用这玩意,层次感非常强。
案例:jQuery
第一部分中,我们把extend相关的方法抽离,剩余代码如下:
(function () {
var yQuery = (function () {
var yQuery = function () {
return yQuery.fn.init();
};
yQuery.fn = yQuery.prototype = {
init: function () {
return this;
}
};
return yQuery;
})();
window.yQuery = window.$ = yQuery();
})();
(function() {
})();
这种写法作用是声明并执行一个方法,等同于:
function Test() {
}
Test();
var obj = new Object();
obj.name = "abc";
obj并没有name属性,但无需声明就可以使用,初始值为undefined。上例中yQuery.fn 就没有声明。
var obj = new Object();
var obj = { };
在js中,{}除了可作为复合语句边界以外,还有创建一个空对象的作用,因此上面这两句相同。而在例子中用到的情况如下:
{
init: function () {
return this;
}
};
这段代码猛一看像方法,实质是一个包含了init方法的对象,而方法中的this指向这个对象本身。
var a = b = 1;
这个比较好理解,相当于对他们分别赋同一个值。在上例中,yQuery.fn就用到了这个写法。
yQuery.fn = yQuery.prototype = {
init: function () {
return this;
}
};
例子中,通过连等分别对yQuery.fn和yQuery.prototype赋值给了同一个对象。而fn的作用只是一个别名,只为书写方便,重点是给prototype赋值,为什么要给它赋值?在本例中无法解释。
在jQuery中,init方法会返回不同对象,而本例中永远返回同一个对象,因此这里prototype没有多大意义。至于jQuery为什么要用prototype,算是题外话了,有兴趣的可点这里。
这段代码第二行yQuery和第三行的yQuery是两个变量,因名字相同,所有很有迷惑性。整段代码意思就是:yQuery.prototype指向了一个包含init方法的对象,prototype有个别名fn,可通过yQuery.fn.init()返回这个对象,最后把这个对象赋值给window.$和window.yQuery属性。
看完了第一部分,第二部分就相对简单了,代码如下:
yQuery.extend = yQuery.fn.extend = function () {
var options, name, src, copy,
target = arguments[0] || {},
i = 1,
length = arguments.length;
if (length === i) {
target = this;
--i;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name];
copy = options[name];
if (src === copy) {
continue;
}
if (copy!==undefined) {
target[name] = copy;
}
}
}
}
return target;
};
函数内部会自带一个arguments变量,该变量记录传入的参数,从左至右分别是arguments[0],arguments[1]等,js奇怪的地方在于,你声明了一个无参函数,在调用的时候依然可以传入参数,比如:
function NoArg() {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
NoArg(1, 2, "a");
该例NoArg调用时,会正常显示传入参数。
js中,所有类型都可以进行逻辑判断(true或false),js会将值转化为布尔值,但并不改变原值。如:
var str = "a";
if(str) {
}
此处的str为true,该特性与逻辑运算符"||"和"&&"结合形成了js一大特点,代码如下:
var str = "a";
var num = 1;
var x = str || num; //x="a"
var y = str && num; //y=1;
js支持"逻辑短路",所谓逻辑短路是指:
因此,"str || num"的str为true,则结束判断,返回str。"str&&num"的str为true,则继续判断num,num为true,则返回num。在本文案例中,有几个地方用到了这个特性:
target = arguments[0] || {}
容易看出,如果arguments[0]有值则返回该值,不然就通过{}返回一个空的对象。还有一处在图片中有,我文中没有打出来的代码:
$.ui = $.ui || { };
var obj = new Object();
obj.name = "a";
obj["name"] = "a";
最后两行代码等效。
这部分代码可简单描述为:定义一个方法,将参数1之外的所有参数的属性成员赋值给参数1。我把循环部分修改一下,能更容易看懂,代码如下:
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (var name in options) {
if (options[name] !== undefined) {
target[name] = options[name];
}
}
}
}