编程
所谓编程,就是将一组需求分解成一组函数与数据结构的技能。
假与真
下面列出的值被当作假(false)
- false
- null
- undefined
- 空字符串('')
- 数字0
- 数字NaN
其它所有的值都被当做真,包括true、字符串“false”、字符串“0”,以及所有的对象。
for in语句
for in语句会枚举一个对象的所有属性名(或键名)。
通常你需要检测object.hasOwnProperty(ppty)来确定这个属性名是该对象的成员,还是来自于原型链,代码如下:
for (ppty in obj) {
if (obj.hasOwnProperty(ppty)) {
// do something
}
}
注:hasOwnProperty方法不会检查原型链。属性名的出现顺序是不确定的。
typeof运算符
typeof运算符产生的值有:'number'、'string'、'boolean'、'undefined'、'function'和'object'。
数据类型
JavaScript的简单数据类型包括数字、字符串、布尔值(true和false)、null值和undefined值。其它所有的值都是对象。即数据类型有:number, string, boolean, null, undefined和object。
对象
JavaScript的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性。
对象是属性的容器,其中每个属性都拥有名字和值。属性的名字可以是包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。
如果你尝试检索一个并不存在的成员属性的值,将返回undefined,示例:
var obj = {a: 'av'};
obj.b // undefined
对象通过引用来传递,它们永远不会被复制,示例:
var x = stooge;
x.nickname = 'Curly';
var nickname = stooge.nickname; // 因为x和stooge指向同一个对象的引用,所以nickname为'Curly'
var a = {}, b = {}, c = {}; // a、b和c每个都引用一个不同的空对象
a = b = c = {}; // a、b和c都引用同一个空对象
判断对象:
var is_object = function(v) {
return (v && typeof v === 'object')? true : false;
};
原型
给Object增加一个create方法,这个方法创建一个使用原对象作为其原型的新对象:
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function() {};
F.prototype = o;
return new F();
}
}
原型连接“只有”在检索值的时候才被用到。如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程称为委托。
原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
||运算符
||运算符可以用来填充默认值,示例:
var b = obj.b || 'bv';
全局变量
最小化使用全局变量的方法之一是为你的应用只创建一个唯一的全局变量:
var MYAPP = {};
函数
函数的与众不同之处在于它们可以被调用。函数的调用模式有4种:
方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当作一个函数来调用的,示例:
var sum = add(3, 4); // sum的值为7
以此模式调用函数时,this被绑定到全局对象。因此对象内部的函数,无法通过this访问对象属性,如果要访问,则解决方案的示例如下:
obj.v = 2;
// 给obj对象增加一个double方法
obj.double = function() {
var that = this; // 解决方法
var helper = function() {
that.v = add(that.v, that.v);
};
helper(); // 以函数的形式调用helper
}
// 以方法的形式调用double
obj.double();
console.log(obj.v); // 4
- 构造器调用模式
如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个新对象上。
一个函数,如果创建的目的就是希望结合new前缀来调用,那它就被称为构造器函数。按照约定,它们保存在以大写格式命名的变量里。
// 创建一个名为Quo的构造器函数。它构造一个带有status属性的对象。
var Quo = function(status) {
this.status = status;
};
// 给Quo的所有实例提供一个名为getStatus的公共方法
Quo.prototype.getStatus = function() {
return this.status;
};
// 构造一个Quo实例
var myQuo = new Quo('confused');
console.log(myQuo.getStatus()); // confused
- Apply调用模式
因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法。
apply方法让我们构建一个参数数组传递给调用函数。它也允许我们选择this的值。apply方法接收两个参数,第1个是要绑定给this的值,第2个就是一个参数数组。
// 构造一个包含两个数字的数组,并将它们相加
var add = function(a, b) {
return a + b;
};
var arr = [3, 4];
var sum = add.apply(null, arr); // sum值为7
// 构造一个包含status成员的对象
var statusObj = {
status: 'A-OK'
};
// statusObj并没有继承自Quo.prototype,但我们可以在statusObj上调用getStatus方法,尽管statusObj并没有一个名为getStatus的方法
var status = Quo.prototype.getStatus.apply(statusObj); // status值为'A-OK'
参数
因为语言的一个设计错误,arguments并不是一个真正的数组。它只是一个“类似数组(array-like)”的对象。arguments拥有一个length属性,但它没有任何数组的方法。
返回
一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。
如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回this(该对象)。
如果没有指定return语句的返回表达式,那么返回值是undefined,即:
return; // 返回undefined
return undefined; // 返回undefined
return this; // 返回this(该对象)
扩充类型的功能
// 通过给Function.prototype增加一个method方法,我们下次给对象增加方法的时候就不必键入prototype这几个字符,省掉了一点麻烦。
Function.prototype.method = function(name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
// 通过给Number.prototype增加一个integer方法,它会根据数字的正负来判断是使用Math.ceiling还是Math.floor:
Number.method('integer', function() {
return Math[this < 0? 'ceil' : 'floor'](this);
});
// 通过给String.prototype增加一个trim方法,它会删除字符串首尾的空白:
String.method('trim', function() {
return this.replace(/^\s+|\s+$/g, '');
});
闭包
闭包示例:
// 创建一个名为quo的构造函数。它构造出带有getStatus方法和status私有属性的一个对象。
var quo = function(status) {
return {
getStatus: function() {
return status;
}
};
};
// 构造一个quo实例
var myQuo = quo('amazed');
console.log(myQuo.getStatus()); // amazed
这个quo函数被设计成无须在前面加上new来使用,所以名字也没有首字母大写。当我们调用quo时,它返回包含getStatus方法的一个新对象。该对象的一个引用保存在myQuo中。即使quo已经返回了,但getStatus方法仍然享有访问quo对象的status属性的特权。getStatus方法并不是访问该参数的一个副本,它访问的就是该参数本身。这是可能的,因为该函数可以访问它被创建时所处的上下文环境。这被称为闭包。
模块
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可访问到的地方。
模块模式通常结合单例模式(Singleton Pattern)使用。JavaScript的单例就是用对象字面量表示法创建的对象,对象的属性值可以是数值或函数,并且属性值在该对象的生命同期中不会发生变化。它通常作为工具为程序其它部分提供功能支持。单例模式的更多内容请参见:https://zh.wikipedia.org/wiki/单例模式
级联
有一些方法没有返回值,如果我们让这些方法返回this而不是undefined,就可以启用级联。
记忆
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称为记忆(memorization)。以下是一个帮助我们构造带记忆功能函数的函数:
var memorizer = function(memo, formula) {
var recur = function(n) {
var result = memo[n];
if (typeof result !== 'number') {
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
};
使用以上构造函数,构造带记忆功能的fibonacci函数:
var fibonacci = memorizer([0, 1], function(recur, n) {
return recur(n - 1) + recur(n - 2);
});
使用以上构造函数,构造带记忆功能的阶乘函数:
var factorial = memorizer([0, 1], function(recur, n) {
return n * recur(n - 1);
});
继承
在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其它对象继承。
函数化
构造一个生成对象的函数,以小写字母开头来命名,因为它并不需要使用new前缀,该函数包括4个步骤:
- 创建一个新对象。有很多的方式去构造一个对象。它可以构造一个对象字面量,或者它可以和new前缀连用去调用一个构造器函数,或者它可以使用Object.create方法去构造一个已经存在的对象的新实例,或者它可以调用任意一个会返回一个对象的函数。
- 有选择地定义私有实例变量和方法。这些就是函数中通过var语句定义的普通变量。
- 给这个新对象扩充方法。这些方法拥有特权去访问参数,以及在第2步中通过var语句定义的变量。
- 返回那个新对象。
这里是一个函数化构造器的伪代码模板:
var constructor = function(spec, my) {
var that;
var 其它的私有实例变量;
my || my || {};
把共享的变量和函数添加到my中;
that = 一个新对象;
添加给that的特权方法;
return that;
};
函数化模式还给我们提供了一个处理父类方法的方法。我们会构造一个supervisor方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,尽管属性已经变化了。
Object.method('supervisor', function(name) {
var that = this;
var method = that[name];
return function() {
return method.apply(that, arguments);
}
});
如果我们用函数化的样式创建一个对象,并且该对象的所有方法都不使用this或that,那么该对象就是持久性(durable)的。一个持久性对象就是一个简单功能函数的集合。
数组Array
数组的length属性的值是这个数组的最大整数属性名加上1。
设置更大的length不会给数组分配更多的空间,而把length设小将导致所有下标大于等于新length的属性被删除。
当属性名是小而连续的整数时,你应该使用数组,否则,使用对象。
JavaScript没有一个好的机制来区别数组和对象,我们可以通过定义自己的is_array函数来弥补这个缺陷:
var is_array = function(v) {
return Object.prototype.toString.apply(v) === '[object Array]';
};
JavaScript提供了一套数组可用的方法,这些方法是被储存在Array.prototype中的函数。
创建一个用于构造数组的方法Array.dim:
Array.dim = function(dimension, initial) {
var a = [];
var i;
for (i = 0; i< dimension; i += 1) {
a[i] = initial;
}
return a;
};
创建一个包含10个0的数组:
var my_arr = Array.dim(10, 0);
创建一个用于构造矩阵的方法Array.matrix:
Array.matrix = function(m, n, initial) {
var a;
var i;
var j;
var mat = [];
for (i = 0; i< m; i += 1) {
a = [];
for (j = 0; j < n; j += 1) {
a[j] = initial;
}
mat[i] = a;
}
return mat;
};
构造一个用0填充的4 × 4矩阵:
var my_mat = Array.matrix(4, 4, 0);
console.log(my_mat[3][3]); // 0
创建一个用于构造单位矩阵的方法Array.identity:
Array.identity = function(n) {
var i;
var mat = Array.matrix(n, n , 0);
for (i = 0; i < n; i += 1) {
mat[i][i] = 1;
}
return mat;
};
构造一个4 × 4的单位矩阵:
my_mat = Array.identity(4);
console.log(my_mat[3][3]); // 1
正则表达式
在JavaScript程序中,正则表达式必须写在一行中。
.会匹配除行结束符以外的所有字符。
一个未转义的$将匹配文本的结束,当指定了m标识时,它也能匹配行结束符。
一般情况下,最好坚持使用贪婪性匹配。
方法
- array.sort(comparefn)
你的比较函数应该接受两个参数,并且如果这两个参数相等则返回0,如是第1个参数应该排在前面,则返回一个负数,如果第2个参数应该排列在前面,则返回一个正数。
编写一个构造比较函数的函数,支持基于多个键值进行排序,如下:
// by函数接收一个成员名字符串,和一个可选的次要比较函数作为参数,
// 并返回一个可以用来对包含该成员的对象数组进行排序的比较函数。
// 当o[name]和p[name]相等时,次要比较函数被用来决出高下。
var by = function(name, minor) {
return function(o, p) {
var a;
var b;
if (o && p && typeof o === 'object' && typeof p === 'object') {
a = o[name];
b = p[name];
if (a === b) {
return typeof minor === 'function'? minor(o, p) : 0;
}
if (typeof a === typeof b) {
return a < b? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
} else {
throw {
name: 'Error',
message: 'Expected an object when sorting by ' + name;
};
}
};
};
var s = [
{first: 'Joe', last: 'Besser'},
{first: 'Moe', last: 'Howard'},
{first: 'Joe', last: 'DeRita'},
{first: 'Shemp', last: 'Howard'},
{first: 'Larry', last: 'Fine'},
{first: 'Curly', last: 'Howard'}
];
s.sort(by('last', by('first')));
// 排序结果是s:
[
{first: 'Joe', last: 'Besser'},
{first: 'Joe', last: 'DeRita'},
{first: 'Larry', last: 'Fine'},
{first: 'Curly', last: 'Howard'},
{first: 'Moe', last: 'Howard'},
{first: 'Shemp', last: 'Howard'}
]
数字
判断数字:
var is_number = function(v) {
return typeof v === 'number' && isFinite(v);
};
function语句对比function表达式
一个语句不能以一个函数表达式开头,因为官方的语法假定以单词function开头的语句是一个function语句。解决方法就是把函数调用括在一个圆括号之中。
(function() {
var hidden_variable;
// 这个函数可能对环境有一些影响,但不会引入新的全局变量。
}());
JSON
JavaScript对象表示法(JavaScript Object Notation,简称JSON)是一种轻量级的数据交换格式。
JSON有6种类型的值:对象、数组、字符串、数字、布尔值(true和false)和特殊值null。空白(空格符、制表符、回车符和换行符)可被插到任何值的前后。
JSON对象是一个容纳“名/值”对的无序集合。名字可以是任何字符串。值可以是任何类型的JSON值,包括数组和对象。
JSON数组是一个值的有序序列。其值可以是任何类型的JSON值,包括数组和对象。
JSON字符串被包围在一对双引号之间。
JSON数字与JavaScript的数字相似。
代码风格
对于一个组织机构来说,软件的长远价值和代码库的质量成正比。
优美的特性
一门语言是否具备为其自身编写一个编译器的能力,仍然是对这门语言完整性的一个测试。
我们发现人们想要的产品其实只要能工作即可。
在设计产品和编程语言时,我们希望直接使用核心的精华部分,因为是这些精华创造了大部分的价值。
如果产品和编程语言被设计得仅留下精华,那该有多好。