《JavaScript语言精粹》学习笔记

一.in的用法

  1. for...in

    枚举一个对象的所有可枚举属性

  2. 检测DOM/BOM属性

    if ("onclick" in elem) {
        // 元素支持onclick
    }
    if ("onerror" in window) {
        // window支持onerror
    }
    
  3. 检测js对象的原型属性(结合hasOwnProperty()函数)

    if ("attr" in obj && !obj.hasOwnProperty("attr")) {
        // obj有原型属性attr
    }
    

    注意:原型属性会被同名的实例属性屏蔽掉,所以无法检测这样的:

    function Super(){
        this.attr = 1;
    }
    function Sub(){
        this.attr = 2;
    }
    Sub.prototype = new Super();
    var obj = new Sub();
    alert(("attr" in obj && !obj.hasOwnProperty("attr")));  /// false
    

二.||运算符和&&运算符

  • ||可以用来填充默认值,例如:

    var obj = {};
    obj.name = "";  // 注意js有一堆假值:+0、-0、0、NaN、null、undefined、""、''、false
    var name = obj.name || "ayqy";
    alert(name);
    

    P.S.个人认为这个东西并不好用,除非能确定原属性值不可能是任何形式的假值,否则默认值会覆盖原值

  • &&可以用来避免从undefined值读取属性引起TypeError,例如:

    var obj = {};
    //var attr = obj.obj.attr;   // 报错,TypeError,不能从undefined值读取属性
    var attr = obj.obj && obj.obj.attr; // 只有obj.obj存在且不是假值时才会取其attr
    

    P.S.个人认为和&&差不多,不如直接用if检测,不仅能展现更清晰的逻辑流,还便于添加更多的检测条件

P.S.这两种用法是在书的第一部分介绍的,可能更偏向于展示语言特性,并不是推荐用法

个人建议不要这样用,当然,看到这样的代码要能明白其作用与漏洞

三.减少全局变量污染

  1. 用“命名空间”,即空对象,实质是把创建的全局变量减少到1个,比如YUI的Y对象,JQuery的JQuery和$对象。。例如:

    var app = {};   //命名空间:app
    app.Consts = {
        // 子命名空间:常量
    
        URL : {
            // 子子命名空间:URL
        }
    }
    app.Modules = {
        // 子命名空间:模块
    }
    app.Data = {
        // 子命名空间:数据
    }
    
  2. 用IIFE(匿名函数立即执行),实质完全不创建全局变量了,0污染

    (function(){
        // Module1
    })();
    (function(){
        // Module2
    })();
    

    缺点很明显,无法实现对象缓存和共享,出了闭包就没了

所以一般做法是结合命名空间和IIFE,整体用命名空间组织起来,在模块内部合适的地方用IIFE

四.4种调用模式

  • 方法调用模式:obj.fun();或者obj[”fun”]();

  • 函数调用模式:fun();此时this指向global对象

  • 构造器调用模式:new Fun();新对象的prototype指向Fun的prototype,this指向这个新对象

  • apply调用模式:fun.apply(this, arrArgs);

五.关于arguments对象

arguments对象不是数组对象,也不支持所有数组方法,只是一个有点特殊的普通对象,特殊在其length属性(动态更新)

可以做如下测试:

    function fun(){
        var x = Object.prototype.toString.call(arguments);
        alert(x);
        var arr = [];
        alert(Object.prototype.toString.call(arr));
    };

    fun();
    // IE8:[object Object]和[object Array],Chrome:[object Arguments]和[object Array]
    // 不一样不要紧,反正都不是Array

一个小技巧:可以用slice方法把arguments对象转换为数组,例如:

    function fun(){
        //arguments.sort();   // 报错,不支持sort()函数
        var arr = Array.prototype.slice.call(arguments);    // 转换
        arr.sort(); //不报错,说明转换成功
        alert(arr);
    };

    fun(3, 2);  // 2, 3

注意:只有slice有这种奇效,concat就没有,虽然无参的slice和无参的concat用于数组的效果一样(都是复制一份)

六.级联(链式调用)的实现方式

让没有返回值的函数返回this,所以支持链式调用,比如JQuery中的:

$("#adBlock").removeClass("active").hide();

七.构造函数调用方式不当会引起作用域污染

如果不用new操作符,直接调用构造函数,比如Fun();此时this指向global属性,即浏览器环境下的window对象,可能会污染全局作用域

八.把形参列表简化为单一对象

例如:

function fun(height, width, margin, padding){   // 参数太长,顺序记不住
    // do something
}

/*
 * @param {number} arg.height 高度
 * @param {number} arg.width 宽度 
 * @param {number} arg.margin 留白
 * @param {number} arg.padding 补白
 */
function fun(arg){  // 不用记参数顺序了
    // do something
}

好处如下:

  1. 调用时不用再关心参数顺序了,接口变得更加易用

  2. 可以直接传个JSON对象进去

缺点:要写一堆注释说明具体需要哪些参数,因为通过一个arg完全看不出来

九.防伪对象(持久性的对象)

防伪对象的属性可以被替换或者删除,但该对象的完整性不会受到损害

也被称为持久性的对象,一个持久性对象就是一个简单功能函数的集合

P.S.防伪对象的概念出现在书的[函数化]部分,目前还不能完全理解函数化想要表达的意思,把自己养肥了再看

函数化部分请查看黯羽轻扬:《JavaScript语言精粹》之函数化,内容已经补充完整了

十.数组

本质是键值对,也就是对象,所以并没有访问速度上的优势

区别是Array.prototype提供了很多数组操作函数,此外还有特殊的length属性

注意: 1. 数组实际占用的内存空间

    var arr = [1];
    arr[99] = 100;

此时arr指向的值并没有占用100个元素的空间,因为上面的结果相当于:

    var arr = {
        "0" : 1,
        "99" : 100
    };
  1. length属性是可写的

    可以用arr.length = n;删掉下标值 >= n的所有元素

  2. 常见的length属性用法

    省计数器的方式:arr[arr.length] = value;

    或者更简单的:arr.push(value);

  3. 数组类型检测

    因为typeof arr返回”object”,而且instanceof操作符在跨frame时失效,所以检测数组类型不太容易

    书上给了一种超麻烦的方式:

    function isArray(value){
        return value &&
            typeof value === "object" &&
            typeof value.length === "number" &&
            typeof value.splice === "function" &&
            !(value.propertyIsEnumerable("length"));
    }
    

    其实有在作者写书的时候还没出现的简单方法:

    可以用Object.prototype.toString.call(value) === ‘[object Array/Function…]’来做类型检查,也可以用来区分原生对象和自定义对象,例如:

    [object JSON]   // 原生JSON对象
    [object Object] // 自定义JSON对象
    

    关于类型检测的更多信息请查看黯羽轻扬:JS学习笔记11_高级技巧

十一.正则表达式

js的正则表达式功能并不完整,但基本够用,比如只支持3种模式:g/i/m ~ 全局模式/忽略大小写模式/多行模式

而且支持的特殊元字符(\d、\s之类的)也比较少,但好在支持非捕获型分组和正则环视,还是很不错的

所以在js中使用正则表达式需要做更多的测试,更多关于正则表达式的信息请查看黯羽轻扬:正则表达式学习笔记

还有一个不常用的点:RegExp对象的属性

  • global:是否开了g模式

  • ignoreCase:返回是否开了i模式

  • lastIndex:返回下一次exec匹配的起始位置

  • multiline:返回是否开了多行模式

  • source:返回正则表达式串

需要特别注意:用字面量方式创建的RegExp对象可能引用同一个实例,例如:

var regex1 = /abc/g;
var regex2 = /abc/g;
var str = "aaa\r\nabc\r\naaa";

alert([regex1.lastIndex, regex2.lastIndex]);    // 0, 0
regex1.test(str);
alert([regex1.lastIndex, regex2.lastIndex]);    // 8, 0
alert(regex1 === regex2);   // false

老版本浏览器最后两行会输出8, 8和true,据说正则字面量共享实例是ES3的规定,本机测试发现IE8不存在问题,但理论上IE6就存在这个问题(出IE6的时候,ES5还没出)

十二. 原生对象操作函数

P.S.此处只介绍需要特别注意的点,完整的各种操作函数请查看黯羽轻扬:JS学习笔记1_基础与常识

1.数组操作函数

  1. arr1.push(arr2);会把arr2作为单个元素插入,例如:

    var arr1 = [1];
    var arr2 = [2, 3];
    arr1.push(arr2);
    alert(arr1[2]); // undefined
    alert(arr1[1][1]);  // 3
    
  2. arr.shift()比arr.pop()慢很多,因为删掉首元需要更新所有元素的索引,而删掉尾元不需要

  3. arr.unshift(),在数组头部插入元素,IE6返回undefined,本来应该返回新数组的长度

2.对象操作函数

obj.hasOwnProperty(“hasOwnProperty”)返回false

3.正则表达式对象操作函数

regex.exec(str)函数功能最强大,当然,执行速度也最慢

regex.test(str)最简单,最快,注意:不要开g模式,因为纯属浪费(test只返回匹配/不匹配,一次扫描就够了)

4.字符串操作函数

  • str.replace(regex, fun);是很好用的一个函数,可以用regex匹配目标部分,还可以用fun进一步处理匹配的部分

    特别注意:如果regex没有开g模式,那么只替换第一个匹配部分,并不是全串替换

    fun的各个参数含义如下:

    Possible nameSupplied value
    matchThe matched substring. (Corresponds to $& above.)
    p1, p2, ...The nth parenthesized submatch string, provided the first argument to replace() was a RegExp object. (Corresponds to $1, $2, etc. above.) For example, if /(\a+)(\b+)/, was given, p1 is the match for \a+, and p2 for \b+.
    offsetThe offset of the matched substring within the total string being examined. (For example, if the total string was 'abcd', and the matched substring was 'bc', then this argument will be 1.)
    stringThe total string being examined.
  • str.replace(regex, replacement);的replacement部分中的$有特殊含义:

    • $$:表示$,如果替换部分中有$需要用$来转义

    • $&:表示整个匹配的文本

    • $n:例如$1, $2, $3...表示捕获到的文本

    • $`:表示位于匹配部分之前的文本

    • $’:表示位于匹配部分之后的文本

    更多详细示例请查看W3School:JavaScript replace() 方法

  • str.split(regex);存在一个特例,需要特别注意

    var str = "a & b & c";    // &是分隔符
    var arr = str.split(/\s*(&)\s*/);   // 含捕获
    var arr2 = str.split(/\s*&\s*/);    // 不含捕获
    var arr3 = str.split(/\s*(?:&)\s*/);    // 含非捕获
    alert(arr); // [a, &, b, &, c]
    alert(arr2);    // [a, b, c]
    alert(arr3);    // [a, b, c]
    

    如果不小心用了含捕获型分组的正则表达式,结果可能与预期的不同

十三.糟粕

P.S.这里不打算原样照搬书上的内容,只给出笔者同意的最糟糕的地方

  • 自动插入分号。确实不好,比如典型的ASI错误:

    function fun(){
        return
        {
            attr : 1;
        }
    }
    
    alert(fun().attr);  // TypeError: Cannot read property 'attr' of undefined
    
  • typeof。不好用,这是设计上的失误,历史原因

  • paseInt()与八进制。一个很隐蔽的错误

    var year = "2015";
    var month = "08";
    var day = "09";
    
    alert(parseInt(year));  // IE8:2015 Chrome:2015
    alert(parseInt(month)); // IE8:0    Chrome:8
    alert(parseInt(day));   // IE8:0    Chrome:9
    

    因为0开头的数值被认为是八进制,省略第二个参数的parseInt()会把08/09当作八进制来解析,所以结果是0

    处理时间日期太容易引起解析错误了,所以最好不要省略parseInt()的第二个参数

    P.S.Chrome中正常了,可能是某个ES版本对parseInt()做了修改,Chrome做了实现。所以糟粕并不是永远存在的,当然,是不是糟粕也要看程序员怎么用。。

  • Unicode。js对Unicode的支持不好,虽然也是历史原因,但不支持就是不支持

    P.S.出js的时候Unicode自己也没想到能有100万个字符,js的字符是16位的,能对应前65536个Unicode字符,而Unicode为了扩容,就把剩下的每个字符都用一对字符来表示,但js会把这样的字符当作两个字符,所以。。

十四.鸡肋

P.S.同上,只列笔者同意的

  • continue性能低。可以if消除

  • 位运算性能低。(其它的书都没有提到这一点)因为要折腾:double -> int -> 做位运算 -> double

  • new到底应该用还是不用。用的话可能污染作用域

  • void运算符。单目运算,返回undefined,所以可以用来实现IIFE,例如:

    void function(){
        // do something
    }();
    

    作者建议不要用void,因为没什么人知道它是做什么的

十五.JSON

整数的首位不能是0,道格拉斯(发明JSON的大爷)本人说的,数值可以是整数、实数或者科学计数,但首位不能是0,为了避免八进制歧义

测试代码如下:

var json = '{"number" : 9}';
var obj = JSON.parse(json);
alert(obj.number);  // 9

var json = '{"number" : 09}';   // 注意前导0
var obj = JSON.parse(json);
alert(obj.number);  // 报错
// Chrome:SyntaxError: Unexpected number
// IE8:语法错误
// FF:SyntaxError: JSON.parse: expected ',' or '}' after property value in object

参考资料:

  • 《JavaScript语言精粹》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code