本书作者douglas Crockford是JSON的创立者,一位javascript大师。
1.精华(Good Parts)
javascript好的想法包括函数,弱类型,动态对象和一个富有表现力的对象字面量表示法。坏的想法包括基于全局变量的编程模型。
javascript函数是基于词法作用域(lexical scoping)的顶级对象,是第一个成为主流的lamda语言。一个糟糕的选择:javascript依赖于全局变量来进行连接。所有编译单元的所有顶级变量被撮合到一个被称为全局对象的公共命名空间中。
这本书之后的很多例子都与用到一个method方法去定义新方法
Function.prototype.method = function (name,fn) {
this.prototype[name] = fn;
return this
}
2.语法(grammar)
- number类型总是64位浮点数,两个整数相除也可能出现非整数结果,
1 === 1.0 //true
0.1+0.2 != 0.3 //true
- 6种值会为假(==false),分别是false,null,undefined,’ ‘,0,NaN。字符串’false’为真。
3.对象(Objects)
- ‘&&’:如果第一个操作数的值为false,那么运算符&&产生它的第一个操作数的值(并且执行短路操作:第一个操作数为false,不对第二个操作数求职)。否则它产生第二个操作数的值,可利用&&运算符避免检索undefined引起的异常
var flight= {};
flight.equipment //undefined
flight.equipment.model //throw"TypeError"
flight.equipment && flight.equipment.model //undefined
- ‘||’:如果第一个操作数的值为true,那么运算符||产生它的第一个操作数的值(第一个操作数为true,不对第二个操作数求值)。否则它产生第二个操作数的值。可利用||运算符来填充默认值。
var status = flight.status || '';
- beget方法创建一个使用原对象作为其原型的新对象
if(typeof Object.beget != 'function') {
Object.beget = function(o) {
var F = function () {};
F.prototype = 0;
return new F();
}
}
当我们对某个对象的属性做出改变时,不会触及到该对象的原型。原型链只有在检索值得时候,才会自下至上直到Object.prototype中去寻找,找到就用,然后停止寻找,都没有就返回undefined,这个过程称为委托。hasOwnProperty方法可以检测对象自身拥有的属性。
原型关系是一种动态关系。如果添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
delete可以删除对象的属性,不会触及对象原型链中的任何对象。
4.函数(Functions)
函数也是对象(Function Objects)
函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype),它的值是一个拥有constructor属性且值为该函数的对象。
每个函数在创建时附有两个附加的隐藏属性:函数的上下文和实现函数行为的代码。调用一个函数将暂停当前执行,传递控制权和参数给新函数。除了声明时定义的形参,每个函数接收两个附加的参数:this和arguments,arguments.callee(ES5严格模式报错)代表函数自身,通常用于递归。可用命名函数表达式来代替(js高程第七章函数表达式-7.1递归)
var factorial = (function f(num) {
if(num <= 1) {
return 1;
} else {
return num * f(num-1);
}
});
调用(Invocation)
函数的实参(arguments)和型参(parameters)的个数不匹配时不会报错,如果实参多了,超出的将被忽略。如果实参过少,缺少的会被传入undefined。
一共四种调用模式:方法调用模式,函数调用模式,构造器调用模式和apply调用模式。
- 方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法,一个方法被调用时,this绑定到该对象(只要函数被调用的时候用一个.点或者[subscript]下标表达式),那么它被当做一个方法来调用。
- 函数调用模式
当一个函数并非一个对象的属性时,那么它被当做一个函数来调用,this被绑定到全局对象。解决内部函数的this被指向全局的设计缺陷时,可以在外部var that=this。如在浏览器中,this == window;在node中,this == global。
- 构造器调用模式
如果在一个函数前面带上new来调用,那么将创建一个隐藏链接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上。
- apply调用模式
类似有call和bind。apply和call的区别在于第二个参数,apply接收数组对象,call接收任意个参数。
this绑定到第一个参数,即传入对象。
在EcmaScript5中扩展了叫bind的方法(IE6,7,8不支持),使用方法如下
function T(c) {
this.id = "Object";
this.dom = document.getElementById("kenny");
}
T.prototype = {
init: function() {
//①
this.dom.onmouseover = function() {
console.log("Over-->"+this.id);//'kenny'
}
//②
this.dom.onmouseout = function() {
console.log("Out -->"+this.id);//'Object'
} .bind(this)
}
};
(new T()).init();
bind与call很相似,,例如,可接受的参数都分为两部分,且第一个参数都是作为执行时函数上下文中的this的对象。
不同点有两个:
1.bind的返回值是函数,(类似事件绑定,改变this指向,执行需在调用一次)。
bind改变上下文this并返回(return)函数,call是 改变上下文this并执行(excute)函数
2.后面的参数的使用也有区别
function f(a,b,c){
console.log(a,b,c);
}
var f_Extend = f.bind(null,"extend_A");
f("A","B","C") //这里会输出--> A B C
f_Extend("A","B","C") //这里会输出--> extend_A A B
f_Extend("B","C") //这里会输出--> extend_A B C
f.call(null,"extend_A") //这里会输出--> extend_A undefined undefined
bind的兼容处理
if (!Function.prototype.bind) {
Function.prototype.bind = function(obj) {
var _self = this
,args = arguments;
return function() {
_self.apply(obj, Array.prototype.slice.call(args, 1));
}
}
}
返回(Return)
return语句可以使函数提前返回。当return被执行(不管ture or false),函数立即返回而不再执行余下代码。
一个函数总会返回一个值,没有指定返回值,则返回undefined。如果函数以new方式调用,且返回值不是一个对象,则返回this(该新对象)。
补充:continue中断本次循环,继续下一次循环。break 语句用于跳出循环。jquery中的$.each()用return ture中断本次循环,return false跳出循环。
闭包(Closure)
利用闭包来返回外部函数变量的方式,可以避免一个函数被加上new来使用
//闭包
var quo = function (status){
return {
get_status : function () {
return status ;
}
};
};
var myQuo = quo ("amazed");
myQuo.get_status();
//构造函数
var Quo = function (status){
this.status =status;
};
var myQuo = new Quo("amazed");
当我们调用myQuo时,它返回包含get_status方法的一个新对象。该对象的一个引用保存在myQuo中。即使quo已经返回了,但get_status方法仍然享有访问quo对象的的status属性的特权。get_status方法并不是访问该参数的一个拷贝,它访问的就是该参数本身。因为该函数可以访问它被创建时所处的上下文环境,这就是闭包。
内部函数能访问外部函数的实际变量而无须复制(闭包是活动对象的一个子对象,包含变量的属性)
回调(Callback)
模拟异步请求,避免客户端被阻塞
request = prepare_the_request();
send_request_asynchronously(request,function(response){
display(response);
};
模块(Module)
通过函数和闭包来构造模块。只暴露可用的public方法,其它私有方法全部隐藏,如果你不需要传参数或者没有一些特殊苛刻的要求的话,我们可以在最后一个}后面加上一个括号,来达到自执行的目的,这样该实例在内存中只会存在一份copy。可通过模块模式来实现单例模式
var theModule = (function(){
var i = 1;
return {
result :function(x) {
i++;
return x*i;
}
};
}());
通过递归记忆函数
var fibonacci = function (n) {
return n < 2 ? n : a(n-1) + a(n-2);
}
for(let i=0; i<10; i++) {
console.log(i + ':' + fibonacci(i));
}
// 0:0
// 1:1
// 2:1
// 3:3
// 4:5
// 5:7
// 6:9
// 7:11
// 8:13
// 9:15
这样写fiboacci函数被调用了453次。我们调用了11次。
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;
})();
fibonacci(10) //55
抽象的递归记忆函数
var memoizer = function (memo, fundamental) {
var shell = function(n) {
var result = memo[n];
if(typeof result != 'number') {
result = fundamental(shell, n);
memo[n] = result;
}
return result;
}
return shell;
};
var fibonacci = memoizer([0, 1], function (shell, n) {
return shell(n-1) + shell(n-2);
});//传递函数
fibonacci(10) //55
5.继承
在伪类模式与模块模式中,尽量选择后者,函数化构造器伪代码模板
“`javascript
var constructor = function (spec ,my) {
var that,
//其他的私有实例变量;
my = my || {};
//把共享的变量和函数添加到my中;
that = 一个新对象
//添加给that的特权方法
return that;
}
- 给数组增加一个key属性(非数字和length),不会改变该数组的长度
js中的糟粕
- 全局变量 :越少越好
- 作用域 :无块级作用域,在每个函数开头部分声明所有变量(let属性)
- 自动插入分号 :因此大括号风格需使用埃及括号
- Unicode :javascript字符是16位的,只能覆盖65535个字符
- parseInt :增加第二个参数,明确进制,parseInt(“08”,10)
- 浮点数 :二进制浮点数不能正确的处理十进制小数,0.1+0.2不等于0.3,不过整数部分是精确的
- NaN : typeof NaN === ‘number’ //true; NaN === NaN //false;
- 对象 :因为原型链的存在,javascript对象永远不会有真的空对象(ES5新增,Object.create)
js中的鸡肋
- 运算符 ==和!= :会试图强制转化判断值的类型,规则复杂
- with语句 :可能出现歧义,并影响处理器速度
- eval :降低安全性,增加阅读复杂性
- continue :重构移出continue后,性能得到改善
- ++ – :使代码变得更隐晦
- 位运算符 :javascript没有整数类型,位操作符将它们的运算数先转换成整数,接着执行运算,然后再转化回去,非常慢
- function :不在if语句中使用function,尽量用var func = function… 的形式声明
- new :更好的应对策略是根本不去使用new
- void :将返回undefined,没有什么用,而且让人困惑