注意
- 声明提前(hoisting)
var scope = "global";
function f() {
console.log(scope); // -> undefined, 该作用域中scope已定义,但在这个地方还未赋值
var scope = "local";
console.log(scope); // -> local
}
- null和undefined不能包含属性
- with语句
o = {"x" : 0};
with(o) x = 1; // o.x = 1
with(o) y = 2; // 定义了全局变量y
// 即with语句提供访问对象属性的简便方法,但并不创建对象属性
- 函数的call方法
f = (function (x) {
return this.a + x;
})
(function () {
this.a = 20;
f.call(this, 30); // 50
f.apply(this, [22]) // 42
} ())
对象
JavaScript类型有数字,字符串,布尔(true/false), null和undefined. 所有其它值均为对象。
Object Literals
- 空对象: {}
- 非空对象为大括号扩住的键值对: {name : value, ...}
- 值value部分可以是Object Literal
- 键name部分可以是任意字符串, 若name是一个合法的JavaScript标志符且不是保留字,则引号可以省略
- 原型为Object.prototype
取值
- obj["name"]
- obj.name
更新值
- obj["name"] = value
- obj.name = value
引用
- 对象通过引用传递
原型
每个对象关联到一个原型对象,并且它可以从该对象继承属性. 所有创建自Object Literal的对象均关联到Object.prototype.
当创建一个对象时,你可以选择作为它原型的对象.
// create方法接收一个对象,并以改对象为原型创建一个新的对象
if (typeof Object.create !== 'function') {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
原型关系只在取值操作中起作用. 当试图获取对象的某个不存在的属性,JavaScript转而从该对象的原型获取该属性,若仍不存在,继续从原型的原型获取,以此类推,若一直到Object.prototype都没有该属性,则返回undefined
反射
typeof "hello" // 'string'
typeof 1 // 'number'
typeof {} // 'object'
typeof undefined // 'undefined'
typeof ''.toString // 'function'
typeof 会沿着原型链查找属性,若不希望这样可以使用hasOwnProperty属性
枚举
for (var in aobject) {...}会枚举对象aobject的所有属性,包括函数和原型属性,且对属性的枚举顺序不定。当然也可以通过将想要枚举的属性放在列表中使用for而不是for in进行枚举:
var i;
var properties = [
'first-name',
'middle-name',
'last-name',
'profession'
];
for (i = 0; i < properties.length; i += 1) {
document.writeln(properties[i] + ': ' + another_stooge[properties[i]]);
}
删除
delete操作符用于删除对象的属性,它不会干涉原型
减少全局变量的使用
减少全局变量的使用可以通过创建一个单一的全局变量作为你应用的容器:
var MyApp = {};
MyApp.var1 = ...;
MyApp.var2 = ...;
还可以使用闭包减少全局变量的使用
函数
函数对象
- JavaScript中的函数是对象
- 原型为Function.prototype,Function.prototype的原型为Object.prototype
Function Literals
函数通过function literal创建
var add = function(a, b) {
return a+b;
};
调用
在调用函数时,除了声明的形参之外,还接收了两个参数: this和arguments。this的值取决于调用模式,在JavaScript中有四种调用模式:
- 方法调用模式(method invocation pattern)
当一个函数作为一个属性存储在对象中,称其为方法,当方法被调用时,this绑定到该对象上
var myObject = {
value: 0,
increment: function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
} };
myObject.increment( );
document.writeln(myObject.value); // 1
myObject.increment(2);
document.writeln(myObject.value); // 3
方法可以通过this访问对象的属性, this和对象的绑定时在方法调用时完成的
- 函数调用模式(function invocation pattern)
若函数不是对象的属性,对其的调用为函数调用。这种模式的调用,this被绑定到全局对象上(global object),这种设计使得嵌套函数中内部函数调用时不能直接使用this获取调用它的函数的上下文,当然这可通过简单的方法解决
myObject.double = function () {
var that = this; // Workaround.
var helper = function () {
that.value = add(that.value, that.value);
};
helper(); // Invoke helper as a function.
};
myObject.double( ); // Invoke double as a method.
document.writeln(myObject.getValue()); // 6
- 构造函数调用模式(constructor invocation pattern)
如果一个函数通过new前缀调用,一个新的对象被创建,该对象隐式关联到该函数的原型成员,this被绑定到该新建的对象
var Quo = function(string) {
this.status = string;
};
Quo.prototype.get_status = function() {
return this.status;
};
var myQuo = new Quo("Confused");
document.writeln(myQuo.get_status()); // "Confused"
通过前缀new调用的函数被称为构造函数(constructor)
- apply调用模式(apply invocation pattern)
JavaScript中函数可以有方法,函数的apply方法可以用来进行函数调用,它接收两个参数,第一个时this要绑定的对象,第二个是调用函数所需的参数列表
var add = function(a, b) {return a+b;};
var array = [1,2];
var sum = add.apply(null, array); // sum = 3
var statusObject = {"status" : "OK"};
Quo.prototype.get_status.apply(statusObject) // "OK"
(function () {return this.status;}).apply(statusObject) // "OK"
arguments
函数的隐式参数arguments里存放了所有的调用参数,它不是数组,只是一个类似列表的对象,它有length属性,但缺少数组的其它属性
返回
函数可以通过return语句返回一个值,若是通过构造函数模式调用(new),没有通过return返回对象,则this被返回
异常
var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
var try_it = function () { try {
add("seven");
} catch (e) {
document.writeln(e.name + ': ' + e.message);
}
}
try_it( );
throw语句应该接收一个异常对象,它至少包含两个属性,name表明异常类型和一个描述异常的message属性。异常对象可以被catch语句捕捉
作用域
var foo = function() {
var a = 3, b = 5; // 此时a=3, b=5
var bar = function() {
var b = 7, c = 11; // 此时a=3, b=7, c=11
a += b + c; // 此时a=21, b=7, c=11
};
bar(); // 此时a=21, b=5
}
在函数内部定义的变量,函数外部不可见,对函数内的所有区块都可见。
闭包
对于JavaScript的作用域,其好的部分是,一个函数的内部定义的函数能够获取该函数的除this和arguments的所有参数和变量的值。
一种极为有趣的情形是内部函数可以有比它外部函数更长的生命周期:
var myObject = function () {
var value = 0;
return {
"increment": function(inc) {
value += typeof inc === "number" ? inc : 1;
},
"getValue": function() {
return value;
}
};
}();
Callbacks
// 异步处理请求
request = prepare_the_request(); // 准备请求
send_request_asynchronously(request, function(response) {
deal_with(response);
}); // 异步发送请求,将处理请求响应的回调函数作为参数传入
模块
一个模块是提供接口但是隐藏状态和实现的函数或者对象。可以通过函数和闭包构造模块。通过函数构造模块可以几乎完全消除全局变量,因而可以规避JavaScript最糟糕的特性。
例如我们要给字符串增加一个方法deentityify,它用于翻译HTML实体到对应的字符。最直观的实现方式就是将实体和对应的字符存放在一个对象中,但是在什么地方存放这个对象呢?放在全局变量中?呵...,放在函数中?每次调用函数时解释器该对象要重新求值。理想的方式是放在闭包中,甚至可以添加一个方法用于添加新的HTML实体
// 给Function.prototype添加一个新的方法method,该方法接收两个参数
// 字符串name和函数func。于是所有的函数都增加了这样一个方法(因为所有
// 函数的原型链的底端都是Function.prototype)
// 当一个函数调用这个method方法时,该函数为自己的原型添加了一个
// 名为name的方法(参考上面的方法调用模式)
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
// 为String的原型添加deentityify方法
String.method("deentityify", function(){
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
return function () {
return this.replace(/&([^&;]+);/g,
function (a, b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
});
};
}());
'<">'.deentityify() // -> <">
该实现中entity变量仅deentityfy方法可以访问
模块的一般模式----将一个函数作为模块,在该模块函数中可以定义一些私有变量和函数,另有一些特权函数,这些函数作为模块函数的返回值或者被存放在外部可见的地方,这些特权函数可以通过闭包访问模块函数中的私有变量和函数
这种模式可以消除全局变量,还可以用于生成安全的对象
var serial_maker = function () {
var prefix = '',
var seq = 0;
return {
set_prefix: function (p) {
prefix = String(p);
},
set_seq: function (s) {
seq = s; },
gensym: function () {
var result = prefix + seq; seq += 1;
return result;
}
};
};
var seqer = serial_maker();
seqer.set_prefix = ('Q';)
seqer.set_seq = (1000);
var unique = seqer.gensym();
如果将上面的seqer.gensym提供给第三方使用,该函数可以生成唯一的标识符但是前缀和序列号不可更改。
级联(cascade)
如果一个方法没有返回值,我们可以令其返回this(即返回调用该方法的对象自己),这样我们就开启了级联模式。
Curry
JavaScript中可以这样实现curry
Function.method("curry", function() {
// 之前提到过arguments不是数组,所以这里需要将它转换为数组(为了使用concat方法)
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function() {
return that.apply(null, args.concat(slice.apply(arguments)));
};
});
Memorization
考虑递归求解Fibonacci数列
var fibonacci = function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
这种求解方法非常低效,太多的工作被重做,记住已做的工作:
var fibonacci = function() {
var memo = [0, 1];
var fib = function(n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n-1) + fib(n-2);
memo[n] = result;
}
return result
};
return fib;
}();
这种方式可以一般化成memorizer,自行实现吧
func = memorizer(init_values, func)
继承
创建函数对象时,生成函数对象的构造函数执行类似下面的操作:
this.prototype = {constructor: this};
生成的函数对象被赋予了一个属性prototype,其(属性prototype)值为一个对象,该对象有值为生成的函数对象的属性constructor。
函数的构造函数模式调用(new前缀调用)若以函数的方法实现的话,可以这样实现:
Function.method("new", function() {
# 创建一个以构造函数原型为原型的对象
var that = Object.create(this.prototype);
# 调用构造构造函数,绑定构造函数的this到新建对象that
var other = this.apply(that, arguments);
# 构造函数返回的是对象返回该对象,否则返回新建对象that
return (typeof other === 'object' && other) || that;
})