据说《JavaScript语言精粹》是一本好书!学JS不得不看的一本书之一,然后买了一本花了一天时间一口气读完了,嗯…感觉还可以,虽然还不错但是没有感觉有什么特别的地方…还是JS高程不错,据说老尼最近出了一本ES6的书,有时间买来拜读拜读…
嗯…跑题了,看了一本书还是有点收获的,做个笔记记录一下。
复习一些知识点
- for in语句会枚举一个对象的所有属性名,可以通过object.hasOwnProperty()来检测该属性是该对象自身的还是来自原型链的。注意for in枚举属性名出现的顺序是不确定的,如果需要确保顺序出现,就不应该使用for in语句,可以创建一个数组,以特定的顺序包含属性名,再用for循环来顺序遍历。
- 对象通过引用传递,不会被复制。
- 委托:检索对象的属性值时,若不存在会向原型对象中检索,直至Object。
- 函数的arguments不是真正的数组,只是一个类数组(array-like),他有一个length属性,但是没有任何数组的方法。
检测数组和对象的方法:
// 1 (识别不同的窗口window和frame里的数组会失败) var is_array = function(value) { return value && typeof value === 'object' && value.constructor === Array; } // 2 (更好的方法) var is_array = function(value) { return Object.prototype.toString.apply(value) === '[object Array]'; }
array.slice(start, end)中如果任何一个参数为负数,array.length会与负数相加让他们变成非负数。
array.sort(fn)如果第一个参数应该排在第二个参数前面则返回负数,否则返回正数,相等返回0;
一些感觉挺重要的知识点
函数的四种调用模式:方法调用、函数调用、构造器调用、apply调用
- 方法调用模式:函数被保存为对象的属性时称为方法,方法调用时,this绑定到该对象。
函数调用模式:当函数不是一个对象的属性时,就是作为函数调用的,以这种方式调用时,this被绑定到全局对象上。这是存在问题的,当内部函数被调用时,正常的this应该被绑定到外部函数的this变量上,实际上绑定到了全局。
//函数调用模式 示例 global.a = 4; var obj = {a: 5}; obj.action = function() { var that = this; // 解决方法 function f2() { // this绑定了全局对象 console.log(this.a);// 4 console.log(that.a);// 5 } f2(); } obj.action();
构造器调用模式:函数前带上new来调用,则会背地里创建一个连接到该函数prototype的新对象,this指向这个新对象。(书上说的比较绕…我理解的就是this指向该函数的prototype对象)
// 构造器调用模式 示例 function A() { var value = 4; console.log(this.value);// 5 } A.prototype = new B(); function B() { this.value = 5; } new A(); // 5
Apply调用模式:applu方法接收两个参数,第一个是要绑定this的值,第二个就是一个参数数组。
尾递归优化:如果一个函数返回自身递归调用的结果,则调用过程会被替换为一个循环,可以显著的提高速度;javascript没有提供尾递归优化,深度递归的函数可能因为堆栈溢出二运行失败。(ES6支持尾调用优化,但只在严格模式下开启,正常模式是无效的)关于尾递归调用优化,可以参考阮一峰老师的博客尾调用优化
记忆:函数可以将每次计算得到的结果存储在某个对象中,以后每次计算时先查找这个对象,避免多余的计算;以计算斐波那契数列为例:
// 没有优化的 var count = 0; function fib(n) { count++; return n < 2 ? n : fib(n - 1) + fib(n - 2); } console.log("fib(i)\t\t结果\t调用次数") for(var i = 0; i <= 10;i++) { console.log("fib(" + i + ")\t\t" + fib(i) + "\t" + count) } // 有记忆功能的 var count = 0; var fibWithMemo = (function() { var memo = [0, 1]; var fib = function(n) { count++; var result = memo[n]; if(typeof result !== 'number') { result = fib(n - 1) + fib(n - 2); memo[n] = result; } return result; } return fib; })(); console.log("fibWithMemo(i)\t\t结果\t调用次数") for(var i = 0; i <= 10;i++) { console.log("fibWithMemo(" + i + ")\t\t" + fibWithMemo(i) + "\t" + count) }
我们看两次运行的结果
一些没有关注到的细节
- JS在被创建的时候,Unicode是一个16位的字符集,所以JS中的所有字符都是16位的,即2个字节
- 字符串是不可变的,一旦字符串被创建,就永远无法改变,但是可以通过+运算符来创建新的字符串
JavaScript中 % 不是通常数学意义上的模运算,实际上是求余运算。
关于求余和模运算的区别:两个运算数都是正数时,模运算和求余运算结果相同,但当有一个数是负数时,结果不同。
对象的属性名可以是包括空字符串在内的任意字符串,属性值可以使除undefined之外的任何值
- delete运算符可以用来删除对象的属性,但是不会触及原型链中的任何对象。
函数名的作用:1.可以递归的调用自己 2.可以被调试器和开发工具用来识别函数。函数参数是动态初始化的,不像其他变量被初始化为undefined,而是在函数被调用的时候初始化为实际提供的参数值。
关于函数这一块,正巧今天做了一道题目,问以下输出的结果是什么:var f = function g() { return 23; }; typeof g();
A.”number” B.”undefined” C.”function” D.Error
答案是D,正确的解释如下
在 JS 里,声明函数只有 2 种方法:
第 1 种: function foo(){…} (函数声明)
第 2 种: var foo = function(){…} (等号后面必须是匿名函数,这句实质是函数表达式)
除此之外,类似于 var foo = function bar(){…} 这样的东西统一按 2 方法处理,即在函数外部无法通过 bar 访问到函数,因为这已经变成了一个表达式。
但为什么不是 “undefined”?
这里如果求 typeof g ,会返回 undefined,但求的是 g(),所以会去先去调用函数 g,这里就会直接抛出异常,所以是 Error。throw语句中断函数执行,抛出exception对象,该对象包含一个用来识别异常类型的name属性和描述性的message属性,exception对象被传到catch从句。
- 级联:让方法返回this而不是undefined就可以启动级联。(级联即obj.fun1().fun2().fun3();这种形式)
- 数组的[]后置下标运算符把它所含的表达式转换成一个字符串,如果该表达式有toString()方法,则调用该方法的值。
- 直接给数组设置更大的length值不会给数组分配更大的空间。
- javascript没有多维数组,但是支持元素为数组的数组。
- Object.create()用在数组上面是没有意义的,它产生一个对象而不是一个数组,产生的对象继承数组的值和方法,但是没有length属性。
- 低版本浏览器中,Array.join()方法比字符串的+运算速度快;高版本浏览器对+运算符做了很多优化,性能远高于Array.join()。
- parseInt()将字符串转换成整数,如果该字符串第一个字符为0,则该字符串基于8进制转换。
- typeof null == object; typeof NaN == ‘number’
- 位运算符:js没有整数类型,只有双精度的浮点数。位操作符把他们的数字运算数先转换成整数,接着执行运算,再转换回去。在C/C++/Java等语言中,位运算符接近硬件处理,所以非常快;但js的执行环境一般接触不到硬件,所以非常慢。
编程习惯
- 尽量避免使用/**/注释,因为这个字符对也可能出现在正则表达式里面,所以块注释对于代码块来说是不安全的,尽量使用//注释(挺鸡肋的一点…编辑器都有自动注释的快捷键,就sublime来说默认使用的就是行注释)
- 最小化全局变量:只创建一个唯一的全局变量,其他变量都是这个全局变量的属性。
- 扩充基本类型的原型是比较危险的一件事情,在混用类库的时候需要更小心,在确定没有该方法的时候再添加
一些实践
- JSON解析器
- 对象数组的排序
- array.splice()的实现
- 构造带记忆功能的函数(生成器)