Javascript语言精粹笔记整理
这两天读了一下《Javascript语言精粹》一书,没有想象中的惊艳,不过在薄薄的100多页中浓缩了许多Javascript的编程技巧,值得细细品味。于是做了该笔记,算是对Javascript知识体系的一个回顾和整理,但并没有把书中的代码直接copy&paste,而是对技巧的罗列,其中对继承那部分,有一些自己的理解。
一些零星的技巧和知识点
Javascript的简单数据类型(原始类型 Primitive)包括Number、String、Boolean、null、undefined,其他所有值都是对象。
在Javascript内部,string以UTF-16存储,每个字符固定2个字节。
当一个Number以0开头时,会试着将其解释为八进制数,如果这个Number包含小数点,则会报出语法错误。
js中的假值(falsy):false,0,null,undefined,’’,NaN
1 | 012 // 10 |
ParseInt
方法会试着解析一个整形数字
1 | parseInt(012) // 10 |
将数字以0开头并试着将它认为是8进制数不是个好的做法,ES6中有八进制数字面量表示法:0o12
下面是NaN表现出的一些奇怪行为,要检测NaN应该使用方法isNaN()
。
1 | typeof NaN === 'number' // true |
for...in
会遍历到原型链中的元素,如果要避免遍历到原型链中的元素,可以在循环中使用object.hasOwnProperty(variable)
来判断其是否属于原型链。
typeof
运算符结果的可能值有’number’、’string’、’boolean’、’undefined’、’function’和’object’。在用typeof判断数组或null
时,将返回’object’。1
2
3
4
5
6
7
8// 判断是否为数组
function isArray(arr) {
return Object.prototype.toString.apply(arr) === '[object Array]';
}
function isNull(obj) {
return obj === null; // 注意要三个等号,因为undefined == null返回true
}
||
与&&
的妙用:
- 对于
||
,如果第一个表达式为false,则返回第二个表达式的值,否则返回第一个表达式的值。 - 对于
&&
,如果第一个表达式为true,则返回第二个表达式的值,否则返回第一个表达式的值。
1 | var a = a || "default"; // 广泛被用于设置默认值 |
函数相关
Javascript没有块级作用域,只有函数级作用域。
闭包:通过函数字面量创建的函数对象包含一个连到外部上下文的连接,即该函数可以访问它被创建时所处的上下文环境。
模块:一个提供接口却隐藏状态与实现的函数或对象。
函数在Javascript中有4种调用模式:
方法调用模式1
myobject.method(args); // 此时函数绑定上下文为myobject对象
函数调用模式1
var sum = add(3, 4); // 此时函数绑定上下文为global对象(window)
构造器调用模式1
var animal = new Animal(); // 如果构造器函数返回值不是一个对象,则返回this
其中new
运算符将创建一个继承于Animal原型的对象,并在调用Animal时将该对象绑定给this。
apply调用模式1
Object.prototype.toString.apply(obj, args);
一个普通的函数声明,其上下文绑定为global对象(window),在对象字面量中声明的函数,其上下文绑定为该对象。
原型和继承相关
原型连接尽在检索值时才会被用到,当该对象没有相应的属性值时,会试着从原型中获取。对象属性的改变不会涉及到原型对象。
delete
运算符不会触及原型链中的任何对象。
伪类继承:给Function.prototype赋一个对象,可以把这个对象看作是’基类’1
2
3
4
5
6
7
8
9
10
11function Animal() {
this.name = "animal";
}
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.sayHi = function() {
console.log("Miao");
};
var cat = new Cat();
cat.sayHi();
这种继承方式还是挺常见的,不过给prototype赋的对象是有讲究的,如果是new一个对象,则会执行构造器,也就是说上例中我们创建的Cat对象可以从原型链上检索到name这个属性。这里我们来对比一下node.js中的util模块里的inherits方法的实现吧:1
2
3
4
5
6
7
8
9
10
11exports.inherits = function(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
在node.js的实现中,ctor.prototype的值是以superCtor的原型为原型创建的一个新对象,和上面一种的区别就在于没有执行构造器方法,也就是说如果使用这种方式的继承,Cat对象是无法从原型链上检索到name属性,只能检索到从原型链上继承来的方法。
函数化模式:该模式并没有像伪类继承那样模仿类继承的概念,而是直接舍弃了new操作符,通过普通函数调用的方式实现继承。
1 | function Animal() { |
这种方法不需要使用new操作符,也不涉及到原型,还可以通过各种闭包来保存私有的状态。
我个人还是会更倾向于写伪类继承的模式,可能是后端出身的关系,觉得其更符合我的思维方式。不过函数化模式更多是用于保存私用的状态,主要不太会用它实现继承。
看到过如下的一种做法:
1 | var SkyScroll = function(opts) { return new SkyScroll.prototype.init(opts); } |
这种方法说不上和继承有关系,不过有一定特点,就是不论你使用var a = SkyScroll({})
还是用new操作符调用构造器var b = new SkyScroll({})
返回的结果都是相同的,不会因为使用函数调用模式调用构造器而污染全局环境,使用构造器模式调用返回的为一个对象,这个对象可以被正常返回,所以这两种方式都可以成功构造一个SkyScroll对象。