1 精华
- 函数
- 弱类型
- 动态对象
- 对象字面量:JSON的灵感来源
- 原型继承:争议
- 基于全局变量的编程模型
JS 主要是基于词法作用域(lexical scoping)
的顶级对象。
JS 是第一个成为主流的 Lambda 语言,与 Lisp、Scheme 有很多的共同点
Lambda :是一套用于研究函数定义、函数应用和递归的形式系统,对函数式编程有巨大的影响
Lisp:一种基于 λ 演算的函数式编程语言
Scheme:一种多范型的编程语言,是两种 Lisp 主要的方言之一
定义新的方法:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
}
2 语法
2.1 数字 number
- 64位浮点数,没有分离出整数类型
- NaN:isNaN(number)
- Math.floor(number)
2.2 字符串 String
- 16位
\u
:指定数字字符编码- str.length
2.3 语句
- 条件语句: if 和 switch
- 循环语句: while、for、do
- 强制跳转语句:break、return、throw
- 函数调用
2.3.1 假(falsy)
- false
- null
- undefined
- 空字符串 ‘ ‘
- 数字 0
- 数字 NaN
2.3.2 for in 语句
会枚举一个对象的所有属性名,通常你需要检测object.hasOwnProperty(variable)
来确认这个属性名是该对象的成员,而不是来自于原型链
typeof 来排除函数
属性值的顺序是不确定的
for (variable in obj) {
if (obj.hasOwnProperty(variable)) {...}
}
2.3.3 do 语句
就像while 语句,不同的是至少执行一次,是在代码块执行之后而非之前检测表达式的值
2.3.4 try catch 语句
try 执行一个代码块,并捕获该代码块抛出的任何异常,catch 从句定义一个新的变量 variable 来接收抛出的异常对象
2.3.5 throw 语句
抛出一个异常,如果 throw 语句在一个 try 代码块中,那么控制流会跳转到 catch 从句中。如果 throw 语句在函数中,则该函数调用被放弃,控制流跳转到调用该函数的 try 语句的 catch 从句中
exception 对象:
- name: 识别异常类型
- message:描述
3 对象
3.1 可变的键控集合
伪对象:Number、String、Boolean
可变的键控集合:Array、Function、RegExp
对象是属性的容器:
属性名:包括空字符串在内的任意字符串,如果属性名是合法的 JS 标识符且不是保留字,则并不强制要求用引号括住属性名,如 “first-name”、 first_name
属性值:除 undefined 值之外的任何值
检索:从 undefined 的成员属性中取值,会导致 TypeError,通过 &&
来避免错误
flight.equipment // undefined
flight.equipment && flight.equipment.model
对象通过引用来传递,永远不会被复制
3.2 原型对象
所有通过对象字面量创建的对象都连接到 Object.prototype,它是 JS 中的标配对象
3.3 delete
不会触及原型链中的任何对象,只要该对象中不再独有该属性,就会返回 true
4 函数
每个函数在创建时会附加两个隐藏属性:
- 函数的上下文:
实现函数行为的代码:JS 创建一个函数对象时,会给该对象设置一个“调用”书香,当JS调用一个函数时,可理解为调用此函数的“调用”属性
[ ] 函数声明时定义的形参:过多的实参会被忽略,过多的形参会被定义为 undefined
- this:上下文,指向调用对象,取决于调用方式
- arguments: 实参
4.1 扩充Function
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
}
4.2 基本类型的原型
提取数字中的整数部分:Number.integer
Number.method('integer', function () {
return Math[this < 0 ? 'ceil' : 'floor'](this)
})
移除字符串首尾空白的方法:
String.method('trim', function() {
return this.replace(/^\s + |\s + $/g, '')
})
" news ".trim()
4.3 递归
trivial solution:寻常解或明显解
一个递归函数调用自身去解决它的子问题
递归函数可以非常高效地操作树形结构,比如浏览器端的文档对象模型(DOM),每次递归调用时处理指定的树的一小段
尾递归:一种在函数的最后执行递归调用语句的特殊形式的递归
尾递归优化:如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,可以显著提高速度。有些语言提供了该优化,但 JS 没有提供
4.4 闭包
内部函数可以访问外部函数的参数和变量(除了 this 和 arguments)
内部函数拥有比它的外部函数更长的生命周期
内部函数访问的自由变量并不是一个副本而是该变量本身,并且只要内部函数需要,该变量就会持续保留
避免在循环中创建函数,它可能只会带来无谓的计算,还会引起混淆
// 设置一个DOM节点为黄色,然后把它渐变为白色
var fade = function (node) {
var level = 1;
var step = function () {
var hex = level.toString(16);
node.style.backgroundColor = '#FFFF' + hex + hex;
if (level < 15) {
level += 1;
setTimeout(step, 100);
}
setTimeout(step, 100);
}
}
fade(document.body)
// 单击一个节点时,将会弹出一个对话框显示节点的序号
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function(e) {
alert(i);
}
};
var i;
for (i = 0; i < nodes.length; i++) {
nodes[i].onclick = helper(i)
}
}
4.5 模块
模块是一个提供接口却隐藏状态与实现的函数或对象
模块模式:一个定义了私有变量和函数的函数,利用闭包创建可以访问私有变量和函数的特权函数,最后返回这个特权函数,或者把它们保存到一个可访问到的地方。
4.6 级联
让方法返回 this ,就可以启用级联,使得可以在单独一条语句中依次调用同一个对象的很多方法。这些方法每一个都返回该对象,所以每次调用返回的结果可以被下一次调用所用。
4.7 记忆(memoization)
将先前操作的结果记录在某个对象里,从而避免无谓的重复运算
4.71 斐波那契数列
var fibonacci = function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
}
将存储结果保存到 memo 数组中,并隐藏到闭包中
var fibonacci = function (n) {
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;
}()
4.72 通用记忆函数
var memoizer = 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
}
var factorial = memoizer([0, 1], function(recur, n){
return n * recur(n-1)
})
5 继承
其他语言的类继承:
- 代码重用
- 类型系统
- 对象是类的实例,并且类可以从另一个类继承
JS:
- 不需要类型转换,对象继承无关紧要
- 对象直接从其他对象继承
- 插入一个多余的间接层,通过构造器函数产生对象
5.1 伪类
var Mammal = function (name) {
this.name = name;
}
Mammal.prototype.get_name = function () {
return this.name
}
Mammal.prototype.says = function () {
return this.saying || ''
}
var Cat = function(name) {
this.name = name;
this.saying = 'meow';
}
Cat.prototype = new Mammal() // 将原型对象替换成父类
// 扩充新原型对象,增加 purr 和 get_name 方法
Cat.prototype.get_name = function (n) {
return this.says() + ' ' + this.name + ' ' + this.says()
}
5.2 原型
差异化继承:通过定制一个新的对象,指明它与所基于的基本对象的区别
对某些数据结构继承于其他数据结构的情形非常有用
对象的所有属性都是可见的,无法得到私有变量和私有函数
var myMammal = {
name: 'Herb the Mammal',
get_name: function() {
return this.name;
},
says: function() {
return this.saying || ''
}
}
var myCat = Object.create(myMammal)
myCat.name = 'lulu'
myCat.saying = 'WoW'
myCat.get_name = function () {
return this.says + ' ' + this.name + ' ' + this.says;
}
5.3 函数化
var constructor = function (spec, my) {
var that, 其他的私有实例变量;
my = my || {};
// 把共享的变量和函数添加到 my
that = 一个新对象
// 添加给 that 的特权方法
return that;
}
var mammal = function (spec) {
var that = {};
that.get_name = function () {
return spec.name;
}
that.says = function () {
return spec.saying || '';
}
return that;
}
var myMammal = mammal({name: 'Herb'})
var cat = function (spec) {
spec.saying = spec.saying || 'lulu';
var that = mammal(spec);
that.get_name = function () {
return that.says() + ' ' + spec.name + ' ' + that.says();
}
return that;
}
var myCat = cat({name: 'Tiantian'})
myMammal.get_name()
myCat.get_name()
5.4 部件
从一套部件中把对象组装出来
给任何对象添加简单事件处理特性的函数:
- on()
- fire()
- 私有的事件注册表对象
var eventuality = function (that) {
var registry = {}
// 在一个对象上触发一个事件,该事件可以是一个包含事件名称的字符串
// 或者是一个拥有包含事件名称的 type 属性的对象
// 通过 “on" 方法注册的事件处理程序中匹配事件名称的函数将被调用
that.fire = function (event) {
var array, func, handler, i, type = typeof event === 'string' ? event : event.type
// 如果这个事件存在一组事件处理程序,那么就遍历它们并按顺序依次执行
if (registry.hasOwnProperty(type)) {
array = registry[type];
for (var i = 0; i < array.length; i++) {
handler = array[i];
// 每个处理程序包含一个方法和一组可选的参数
// 如果该方法是一个字符串形式的名字,那么寻找到该函数
func = handler.method;
if (typeof func === 'string') {
func = this[func];
}
func.apply(this, handler.parameters || [event])
}
}
return this;
}
// 注册一个事件,构造一条处理程序条目,将它插入到处理程序数组中
// 如果这种类型的事件还不存在,就构造一个
that.on = function (type, method, parameters) {
var handler = {
method: method,
parameters: parameters
};
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
return this;
}
}
6 数组
- JS 的 length 没有上界,不会发生数组越界错误
- ES262标准:数组的下标必须是大于等于0并小于 232-1的整数
- 把 length 设小将导致所有下标大于等于新 length 的属性被删除
- 设置更大的 length 不会给数组分配更多的空间
6.1 is_array
var is_array = function(value) {
return Object.prototype.toString.apply(value) === '[object Array]'
}