【前端学习】《JavaScript语言精粹》读书笔记

第2章:语法

1. 通常需通过检测object.hasOwnProperty(variable)来确定这个属性名就是该对象的成员,还是从其原型链中找到的。

for (myvar in obj) {
    if (obj.hasOwnProperty(myvar)) {
        ...
    }
}

2. throw语句中的表达式通常是一个对象字面量,它包含一个name属性和一个message属性。

3. break语句可以指定一个可选的标签,那将会使程序退出带该标签的语句。

4. typeof运算符产生的值有'number', 'string', 'boolean', 'undefined', 'function'和'object'。如果运算符是一个数组或null,那么结果是'object'。

5. 如果第一个运算数的值为假,那么&&产生它的第一个运算数的值,否则产生第二个运算数的值。

6. 如果第一个运算数的值为真,那么||产生它的第一个运算数的值,否则产生第二个运算数的值。

第3章:对象

1. 在对象字面量中,如果属性名是一个合法的JavaScript标识符且不是保留字,并不强制要求用引号括住属性名。所以用引号括住"first-name"是必须的,但是否括住first_name则是可选的。

2. ||运算符可用于为对象检索填充默认值,如var status = flight.status || "unknown"。

3. &&运算符可用于避免尝试检索undefined值时将会导致的TypeError异常,例如flight.equipment && flight.equipment.model。

4. 对象通过引用来传递,它们永远不会被拷贝。

5. 原型连接只有在检索值的时候才被用到。如果我们尝试去获取对象的某个属性值,且该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程称为委托。

6. 原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

7. typeof操作符会检查原型链中的属性,而hasOwnProperty方法则不会检查原型链。这个方法还可以用于在for in语句中遍历属性。

8. for in语句中,属性名出现的顺序是不固定的。若想要保证属性以特定的顺序出现,最好的办法是创建一个数组,在其中以正确的顺序包含属性名。

9. delete运算符可以用来删除对象的属性,但它不会触及原型链中的任何对象。

10. 减少全局变量污染的一个方法是在应用中只创建一个全局变量,让该变量变成应用的容器。

第4章:函数

1. 当实际参数(arguments)的值与形式参数(parameters)的个数不匹配时不会导致运行时错误。如果实际参数过多了,超出的参数值将被忽略。如果实际参数值过少,缺失的值将会被替换为undefined。对参数值不会进行类型检查:任何类型的值都可以被传递给参数。

2. 函数的调用模式:

方法调用模式:

// 函数被保存为对象的一个属性
var myObject = {
    value = 0;
    increment = function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};

myObject.increment();
document.writeln(myObject.value);    // 1

myObject.increment();
document.writeln(myObject.value);    // 3

// 方法可以使用this去访问对象。this到对象的绑定发生在调用时(very late binding)
// 通过this可取得它们所属对象的上下文的方法称为公共方法。

函数调用模式:

// 一个函数并非一个对象的属性时,当作一个函数来调用
// 使用该模式调用时,this被绑定到全局对象
// 这是语言设计上的一个错误,错误的后果是方法不能利用内部函数来帮助它工作 var sum = add(3, 4) // sum的值为7 // 解决办法:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this myObject.double = function () { var that = this; var helper = function () { that.value = add(that.value, that.value); } helper(); }; myObject.double(); document.writeln(myObject.getValue()); // 6

构造器调用模式:

// 如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象
// 同时this将会被绑定到那个新对象上 var Quo = function (string) { this.status = string; }; Quo.prototype.get_status = function () { return this.status; }; var myQuo = new Quo("confused"); document.writeln(myQuo.get_status()); //令人困惑

// 按照约定,构造器函数被保存在以大写格式命名的变量里。大写约定很重要。
// 不推荐使用这种形式的构造器函数。

Apply调用模式:

var array = [3, 4];
var sum = add.apply(null, array);    // sum值为7

var statusObject = {
    status: 'A-OK'
};

// statusObject并没有继承自Quo.prototype,但我们可以在statusObject上调用get_status方法
// 尽管statusObject并没有一个名为get_status的方法
var status = Quo.prototype.get_status.apply(statusObject); // status值为'A-OK'

3. ”免费“奉送的参数:arguments数组。注意:由于语言的设计错误,arguments只是一个类似数组(array-like)的对象,拥有一个length属性,但它缺少所有的数组方法。

4. 如果函数以在前面加上new前缀的方式来调用,且返回值不是一个对象,则返回this(该新对象)。

5. JavaScript抛出异常的方法:

var add = function (a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }
    return a + b;
}

6. 一个try语句只会有一个将捕获所有异常的catch代码块。如果处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。

7. JavaScript支持给Object、prototype、函数、数组、字符串、数字、正则表达式和布尔值增加方法。一个给字符串类型添加trim方法的实例如下:

Function.prototype.method = function (name, func) {
    if(!this.prototype[name]) {
        this.prototype[name] = func;
        return this;
    }
};

String.method('trim', function () {
    return this.replace(/^\s+|\s+$/g, '');
});
console.log('"' + "  neat  ".trim() + '"')

8. JavaScript当前没有提供尾递归优化,深度递归的函数可能会因为返回堆栈溢出而运行失败。

9. JavaScript不支持块级作用域。最好的做法是在函数体的顶部声明函数中可能用到的所有变量。

10. 通过调用一个函数的形式去初始化myObject对象的一个实例如下。最后一行的()使得myObject的值是调用函数后返回的结果,而不是这个函数。这个函数返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的特权,但函数的作用域使得value对其他的程序来说是不可见的。

var myObject = function () {
    var value = 0;
    
    return {
        increment: function (inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function () {
            return value;
        }
    }
}();

11. 理解闭包比较好用的三个实际例子:

// 定义一个函数,它设置一个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 i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (e) {
            alert(i);
        }
    }
};

// 更好的例子:点击一个节点时,显示节点的序号
var add_the_handlers = function (nodes) {
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function (i) {
            return function (e) {
                alert(e);
            };
        }(i);
    }
};

 12. 使用函数和闭包构造模块的两个实际例子:

String.method('deentityify', function () {
    var entity = {
        quot: '"',
        lt: '<',
        gt: '>'
    };

    return function () {
        return this.replace(/&([^&;]+);/g,
            function (a, b) {
                var r = entity[b];
                return typeof r === 'string' ? r : a;
            }
        );
    };
}());

模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可访问到的地方。

var serial_maker = function () {
    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function (p) {
            prefix = String(p);
        },
        set_seq: function (s) {
            seq = s;
        },
        gensym: function () {
            var result = prefix + seq;
            seq += 1;
            return result;
        }
    };
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();    // unique is "Q1000"

13. 让没有返回值的方法返回this而不是undefined,就可以启用级联。、

14. 函数可以用对象去记住先前操作的结果,从而避免无谓的运算。这种优化被称为记忆。JavaScript的对象和数组要实现这种优化是非常方便的。一个利用memorizer来定义fibonacci以及factorial函数的例子如下:

var memorizer = 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 = memorizer([0, 1], function (shell, n) {
    return shell(n - 1) + shell(n - 2);
});

var factorial = memorizer([1, 1], function (shell, n) {
    return n * shell(n - 1);
});

第5章:继承

15. 下面是一个函数化构造器的伪代码模板:

var constructor = function (spec, my) {
    var that, 其他的私有实例变量;
    my = my || {};
    把共享的变量和函数添加到my中
    that = 一个新对象
    添加给that的特权方法
    return that;
}

16. 我们可以从一套部件中组合出对象来。例如,我们可以构造一个能添加简单事件处理特性到任何对象上的函数,它会给对象添加一个on方法、一个fire方法和一个私有的事件注册表对象:

var eventuality = function (that) {
    var registry = {};
    that.fire = function (event) {
        var array,
            func,
            handler,
            i,
            type = typeof event === 'string' ? event : event.type;
        if (registry.hasOwnProperty(type)) {
            array = registry[type];
            for (i = 0; i < array.length; i += 1) {
                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: parameter,
        };
        if (registry.hasOwnProperty(type)) {
            registry[type].push(handler);
        } else {
            registry[type] = [handler];
        }
        return this;
    };
    return that;
};

// 这样一来,我们可以在任何单独的对象上调用eventuality,授予它事件处理方法。我们也可以赶在that被返回前在一个构造器函数中调用它
eventuality(that);

第6章:数组

17. JavaScript应该对矩阵提供更好的支持。我们可以这样修正它:

Array.matrix = function (m, n, initial) {
    var a, i, j, mat = [];
    for (i = 0; i < m; i += 1) {
        a = [];
        for (j = 0; j < n; j += 1) {
            a[j] = 0;
        }
        mat[i] = a;
    }
    return mat;
}

// 构造一个用0填充的4x4矩阵
var myMatrix = Array.matrix(4, 4, 0);
document.writeln(myMatrix[3][3]);    // 0

// 用来构造一个恒等矩阵的方法
Array.identity = function (n) {
    var i, mat = Array.matrix(n, n, 0);
    for (i = 0; i < n; i += 1) {
        mat[i][i] = 1;
    }
    return mat;
}

myMatrix = Array.identity(4);
document.writeln(myMatrix[3][3]);    // 1

第7章:正则表达式

18. 正则表达式分组共有4种:

捕获型:一个捕获型分组是一个被包围在圆括号中的正则表达式选择。任何匹配这个分组的字符将被捕获。每个捕获型分组都被指定了一个数字。在正则表达式中第一个捕获(的是分组1,第二个捕获(的是分组2

非捕获型:非捕获型分组有一个(?:前缀。非捕获型分组仅做简单的匹配,并不会捕获所匹配文本。这会有微弱的性能优势。非捕获型分组不会干扰捕获型分组的编号

向前正向匹配(Positive lookahead):向前正向匹配分组有一个(?=前缀。它类似于非捕获分组,但在这个组匹配后,文本将倒回它开始的地方,实际上并不匹配任何东西

向前负向匹配(Negative lookahead):向前负向匹配分组有一个(?!前缀。它类似于向前正向匹配分组,但只有当它怕匹配失败时它才进行匹配

第8章:方法

19. array.shift()方法移除array中的第一个元素并返回该元素。如果这个数组array是空的,它会返回undefined。shift通常比pop慢得多

20. JavaScript的默认比较函数假定所有要被排序的元素都是字符串。幸运的是,你可以使用自己的比较函数来替换默认的比较函数。sort方法是不稳定的,如果想基于多个键值进行排序,需要再次做更多的工作:

var by = function (name, minor) {
    return function (o, p) {
        var a, b;
        if (o && p && typeof o === 'object' && typeof p === 'object') {
            a = o[name];
            b = p[name];
            if (a === b) {
                return typeof minor === 'function' ? minor(o, p) : 0;
            }
            if (typeof a === typeof b) {
                return a < b ? -1 : 1;
            }
            return typeof a < typeof b ? -1 : 1;
        } else {
            throw {
                name: 'Error',
                message: 'Expected an object when sorting by ' + name;
            };
        }
    };
};

s.sort(by('last', by('first')));    // s是[
//     {first: 'Joe', last: 'Besser'},
//     {first: 'Joe', last: 'DeRita'},
//     {first: 'Larry', last: 'Fine'},
//     {first: 'Curly', last: 'Howard'},
//     {first: 'Moe', last: 'Howard'},
//     {first: 'Shemp', last: 'Howard'}
// ]

21. slice用于字符串切片,而splice方法则是用于从array中移除元素,并用新的item替换它们

22. exec方法是使用正则表达式的最强大(和最慢)的方法,test方法是使用正则表达式的最简单(和最快)的方法,但不要对这个方法使用g标识

23. string.replace方法的replaceValue可以是一个字符串或一个函数。如果replaceValue是一个字符串,字符$拥有特别的含义

第10章:优美的特性

24. 精简的JavaScript里都是好东西。包括以下主要内容:

1. 函数是头等对象

函数是有词法作用域的闭包(lambda)

2. 基于原型继承的动态对象

对象无类别的。我们可以通过普通的赋值给任何对象增加一个新成员元素。一个对象可以从另一个对象继承成员元素

3. 对象字面量和数组字面量

这对创建新的对象和数组来说是一种非常方便的表示法。JavaScript字面量是数据交换格式JSON的灵感之源

附录

25. JavaScript没有真正的数组,typeof运算符不能辨别数组和对象。要判断一个值是否为数组,你还需要检查它的constructor属性

if (my_value && typeof my_value === 'object' &&
    typeof my_value.length === 'number' &&
    !(my_value.propertyIsEnumerable('length')) {
    // my_value确实是一个数组
}

26. JavaScript拥有令人惊讶的一大组假值,但它们是不可互换的:

类型
0 Number
NaN Number
'' String
false Boolean
null Object
undefined

Undefined

 

 

 

 

 

 

 

 

 

 

27. 打算与new结合使用的函数应该命名为首字母大写的形式,并且首字母大写的形式应该只用来命名那些构造器函数

28. 全局声明以/*global字样开头,可以用来列出所有特意用作全局变量的名字。JSLint可以用此信息去辨别拼写错误和被遗忘的var声明

29. 通过使用JSON.parse方法替代eval就能避免JSON解析过程中的危险和漏洞

30. 把服务端发送过来的一个HTML文本片段赋值给某个HTML元素的innerHTML属性是一个非常糟糕的习惯

posted @ 2020-05-09 18:52  アカツキ  阅读(133)  评论(0编辑  收藏  举报