JavaScript 基础优化(《JavaScript 高级程序设计》读书笔记)

1、带有 src 属性的<script>元素不应该在其<script>和</script>标签之间再包含额外的 JavaScript 代码。如果包含了嵌入的代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略。一般都把全部 JavaScript 引用放在<body>元素中页面内容的后面。

2、循环引用:对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用:

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;

IE8及以前版本中有一部分对象并不是原生 JavaScript 对象,而是使用 C++以 COM(Component Object Model,组件对象模型)对象的形式实现的的,而 COM 对象的垃圾收集机制由于采用了引用计数策略,所以会有循环引用的问题,而循环引用会导致即使将例子中的 DOM 从页面中移除,它也永远不会被回收,因此会导致内存泄露。所以一旦数据不再有用,最好通过将其值设置为 null 来释放其引用:

myObject.element = null;
element.someObject = null;

3、未初始化的变量会自动被赋予 undefined 值,但显式地初始化变量依然是明智的选择,当 typeof 操作符返回"undefined"值时,我们就知道被检测的变量还没有被声明,而不是尚未初始化。

4、创建对象推荐组合使用构造函数模式和原型模式:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

组合使用构造函数模式和原型模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

其他模式的缺点:

工厂模式:虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型);

构造函数模式:每个方法都要在每个实例上重新创建一遍;

原型模式:原型中所有属性是被很多实例共享的,这种共享对于函数非常合适,然而对于包含引用类型值的属性来说问题比较大。

5、JavaScript中的继承可以使用组合继承(也叫伪经典继承):

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    //继承属性
    SuperType.call(this, name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

组合继承使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,从而避免了原型链和借用构造函数的缺陷。但是组合继承也有不足,即无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候(new SuperType()),另一次是在子类型构造函数内部(SuperType.call(this, name)),寄生组合式继承克服了这个缺点,基本模式如下:

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。我们就可以用调用 inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句,例如:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
};

6、由于耦合的问题,在编写递归调用时,使用 arguments.callee 总比使用函数名更保险:

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1); //不要使用return num * factorial(num-1);
    }
}

7、由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多。
8、由于 JavaScript 没有块级作用域,因此可以使用匿名函数来模仿块级作用域:

(function(){
    //这里是块级���用域
})();

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。这种技术也经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

9、尽量不要使用间歇调用,因为在不加干涉的情况下,间歇调用将会一直执行到页面卸载,而且后一个间歇调用可能会在前一个间歇调用结束之前启动,最好使用超时调用来模拟间歇调用,对比如下两段代码:

间歇调用:
var num = 0;
var max = 10;
var intervalId = null;
function incrementNumber() {
    num++;
    //如果执行次数达到了 max 设定的值,则取消后续尚未执行的调用
    if (num == max) {
        clearInterval(intervalId);
        alert("Done");
    }
}
intervalId = setInterval(incrementNumber, 500);
 
 
超时调用模拟间歇调用:
var num = 0;
var max = 10;
function incrementNumber() {
    num++;
    //如果执行次数未达到 max 设定的值,则设置另一次超时调用
    if (num < max) {
        setTimeout(incrementNumber, 500);
    } else {
        alert("Done");
    }
}
setTimeout(incrementNumber, 500);

10、为了确保跨浏览器兼容,最好还是将 nodeType 属性与数字值进行比较,因为IE无法访问 Node 类型。

11、使用cloneNode()方法时在复制之前最好先移除事件处理程序,因为IE 在此存在一个 bug,它会复制事件处理程序。

12、尽量减少访问 NodeList 的次数。因为每次访问 NodeList,都会运行一次基于文档的查询。

13、由于老版本的浏览器不支持,因此在有特殊需要时再使用事件捕获,可以放心地使用事件冒泡。

14、因为HTML 与 JavaScript 代码紧密耦合,因此不要使用 HTML 事件处理程序,可以使用 JavaScript 指定事件处理程序。

15、使用事件委托,在DOM 树中尽量最高的层次上添加一个事件处理程序,不必给每个可单击的元素分别添加事件处理程序,因为事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。

16、调用 submit()方法的形式提交表单时,不会触发 submit 事件,因此要记得在调用此方法之前先验证表单数据。与调用 submit()方法不同,调用 reset()方法会像单击重置按钮一样触发 reset 事件。

17、读取或设置文本框的值时不建议使用标准的 DOM 方法,而是使用 value 属性:

var textbox = document.forms[0].elements["textbox1"];
textbox.value = "Some new value";

换句话说,不要使用 setAttribute()设置<input>元素的 value 特性,也不要去修改<textarea>元素的第一个子节点。原因很简单:对 value 属性所作的修改,不一定会反映在 DOM 中。因此,在处理文本框的值时,最好不要使用 DOM 方法。

18、不建议使用常规的 DOM 功能来访问 option 元素的数据,因为效率比较低,最好是使用特定于选项的属性,因为所有浏览器都支持这些属性:

var selectbox = document.forms[0].elements["location"];
//不推荐
var text = selectbox.options[0].firstChild.nodeValue; //选项的文本
var value = selectbox.options[0].getAttribute("value"); //选项的值

//推荐
var text = selectbox.options[0].text; //选项的文本
var value = selectbox.options[0].value; //选项的值

猜你喜欢

转载自www.linuxidc.com/Linux/2015-05/118053.htm