<<编写可维护的javascript>> part2: 编程实践


chapter5 UI层的松耦合

- css 去 javascript 
        > 不要用css 的expression 表达式
javascript 去 css
    1
2
3
4
5
6
element.style.color = "red"// 不好的写法
element.style.cssText= "color: red; left: 10px;"// 不好的写法
  
element.className += " msg-color"// 好的写法
element.classList.add("msg-color"); // 好的写法 html5
$(id).addClass("msg-color"); // 好的写法

- html 去 javascript 
    1
2
3
4
<a onclick="doSth()" /> // 不好的写法, DOM0 模型
<script>
    ... // 不好的写法, 但是这样写可以减少http请求
</script> 
javascript 去 html
        > 服务器加载: 从服务器端获取, 容易造成XSS 漏洞
        > 简单模版: js奇淫技巧,
        > 复杂模版: 用模版引擎如 Handlebars.js, [注意]获取模版时用jquery.html()方法而不是text()方法, 否则ie8会有问题


chapter6 避免全局变量

- 全局变量的问题
        > 命名冲突
        > 难以测试
- 单全局变量模式: 参考<<javscipt 设计模式>>
        > 命名空间
                >> 在一个全局变量的基础上, 增加命名空间
            1
2
3
var haili = {};
haili.mySpace1 = {}; // 命名空间1, 以后属于命名空间1的模块都写在这里
haili.mySpace2 = {}; // 命名空间2, 以后属于命名空间2的模块都写在这里
        > 模块
- 零全局变量, 在一个匿名函数内执行, 不常用


chapter7 事件处理

- 典型用法, 不太好
    1
2
3
4
5
function handleClick(event) { // 不好的用法
    var popup = document,getElementById("popup"); // 应用逻辑
    popup.style.left = event.clientX + "px"// event.clentX 为事件处理
}
addListener(element, "click", handleClick);
好的用法
        > [优点]
                >> 隔离了应用逻辑, 提高了可重用性
                >> 阻止了分发event对象, 提高了便于测试, 方法可重用性, 代码可读性
    1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myApp = {
 
    handleClick: funtion(event) { // 事件处理
         
        // 阻止冒泡, 组织默认行为
        event.preventDefault();
        event.stopPropagation();
 
        // 传入应用逻辑
        this.showPopup(event.clientX);
         
    },
    showPopup: functon(x) { // 应用逻辑
        var popup = document,getElementById("popup");
        popup.style.left = x + "px";
    }
};
 
addListener(element, "click"function(event) {
    myApp.handleClick(event);
});


chapter8 避免空比较

- 监测原始值
        > 用 typeof 来检查原始类型, 来确保安全, [字符串, 数字, 布尔值, undefined]
        > 不要用typeof 来检测null! 不要用typeof 来检测null! 不要用typeof 来检测null! 用 === | !==
        1
typeof null// "Object"
- 监测引用值
        > 用 instanceof来检查引用类型
        > instanceof 不能跨帧(frame)
         1
2
3
4
5
6
7
8
9
10
11
12
// frame1 和frame2 是浏览器的两个帧, 拥有相同的构造函数Persion
// frame1:
    function Persion() {}
    var man1 = new Persion();
 
// frame2:
    function Persion() {}
    var man2 = new Persion();
 
// 则以下成立
man1 instanceof frame1Persion // true , man1 属于 frame1的 Persion
man1 instanceof frame2Persion // false , man1 不属于 frame2的 Persion
- 检测函数
        > typeof可以跨帧(frame)
        1
2
3
4
5
// frame1
function func() {}
 
// frame 2
typeof func = "function"
        > 然而ie8 - 浏览器中, typeof 检测dom节点的函数时返回的是object
        1
2
// ie8-
typeof document.getElementById = "object"
- 监测数组
        > 用 Array.isArray() 方法
- 检测属性在不在对象中
        > 用 in, in大法好
        > 用hasOwnProperty(),但是在ie8-中,DOM对象并非继承Object. 所以不包含这个方法


chapter9 配置数据从代码分离

- 抽离配置数据: [URL, 重复的值, 设置...]
        > 用对象保存
        1
2
3
4
5
6
7
8
var config = {
    URL_INVALID: "baidu.com",
    CSS_SELECTED: "sleected"
};
 
function select() {
     var css = config.CSS_SELECTED; // 应用配置数据
}


chapter10 抛出自定义异常

- 抛异常的意义
        > 正确的抛异常可以帮助我们找到错误的地方
        > 对旧的ie 找到错误有较好的帮助
        > 只在必要的地方抛
        > 错误类型:  [Error, EvalError, TypeError, RangeError, ...]
- thorw 
    1
throw new Error("this is error message"); // 抛出错误
- try-catch
    1
2
3
4
5
try {
    ....
catch(e) {
    // 不要留空, 要写点东西
}


chapter11 不是你的对象不要动

- 如果你的代码没有创建这些对象, 不要修改他们, 不要修改他们, 不要修改他们, 包括: 
        > 原生对象(Object, Array...)
        > DOM 对象 (document)
        > BOM 对象 (window)
        > 类库对象
- 原则
        > 不覆盖方法
        1
document.getElementById = function() {}; // 不好的写法, 什么鬼, 其他人怎么知道这个改了, 错都不知道怎么找
        > 不新增方法,  [缺点]如下
                >> 命名冲突
                >> 如果将来原生的方法和你的方法行为不一致, 后果很悲催 (血淋淋的例子-> prototype.js 的getElementByClassName 方法)
        1
2
3
4
5
6
7
8
9
document.sayHi = function() { // 不好的写法, 在DOM 上新增了方法
    alert("hi");
};
Array.prototype.sayHi = function() { // 不好的写法, 在原生对象上新增了方法
    alert("hi");
};
EasyUI.sayHi = function() { // 不好的写法, 在EasyUI库对象上新增了方法
    alert("hi");
}; 
        > 不删除方法
        1
2
document.getElemetnById = null// 不好的写法, 删掉了DOM方法
detete document.getElementById; // 不影响, 仍能工作, 因为getElementById是原型链上的一个方法
- 更好的解决办法, 用最受欢迎的继承来扩充对象
        > 基于对象的继承(原型继承)
                    >> 原理是创建一个属于你的对象, 就可以随意修改了
        1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Person = { // 这是原来的别人的对象
    name: "",
    age: "", 
    sayName: function() {
        console.log("my name is " this.name + ", i'm " this.age);
    }
};
     
// 对Person 进行原型继承, 创建haili这个新对象, 属于我, 可以随意修改
// 第二个参数对原来对象的属性进行覆盖
var haili = Object.create(Person, { // ECMAScript 5 才有 create() 方法
    name: {
        value: "xiaoming"
    }
});
haili.sayName(); // my name is xiaoming, i'm
        > 基于类型的继承
        1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    function Person(name) { // 别人家的对象
        this.name = name;
    }
 
    function MyPerson(name, age) { // 我的对象
        this.age =age;
        Person.call(this, name, age);
    }
 
    MyPerson.prototype.sayName = function() { // 我给我的对象加了个方法, 不影响别人的对象
        console.log("my name is " this.name + ", i'm " this.age);
    };
 
    var haili = new MyPerson("haili", 10); // 成功
    haili.sayName();
        > 门面模式 (复制一份)
1
        2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // 原本的 dom 元素会保留下来
    function DOMWrapper(domEle) {
        this.domEle = domEle; // 用构造函数创建一个副本, 保留了旧的对象
    }
 
    // 新增新的方法给dom元素
    DOMWrapper.prototype.addClass = function(className) {
        this.domEle.className += " " + className;
    };
 
    // 新增新的方法给dom元素
    DOMWrapper.prototype.remove = function() {
        this.domEle.parentNode.removeChild(this.domEle);
    };
 
    var wrapper = new DOMWrapper(document.getElementById("test"));
    warpper.addClass("class1"); // 调用自定义的方法而不会影响dom的原生方法
    wrapper.remove(); // 调用自定义的方法而不会影响dom的原生方法

        > [shim概念]: 一个shim是一个,它将一个新的API引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现.(一种模仿未来API并为旧浏览器提供后备功能的“衬垫”)
        > [polyfill概念]: 一个polyfill是一段代码(或者插件),提供了那些开发者们希望浏览器原生提供支持的功能.因此,一个polyfill就是一个用在浏览器API上的shim.我们通常的做法是先检查当前浏览器是否支持某个API,如果不支持的话就加载对应的polyfill.然后新旧浏览器就都可以使用这个API了.
        [例如] shim: 比如 jQuery 的 $.ajax 封装了 XMLHttpRequest 和 IE 用 ActiveXObject 方式创建 xhr 对象;polyfill 特指 shim 成的 api 是遵循标准的,其典型做法是在IE浏览器中增加 window.XMLHttpRequest ,内部实现使用 ActiveXObject。在实际中为了方便做对比,会特指 shim 的 api 不是遵循标准的,而是自己设计的。

        >为了提高可维护性, 还是尽量避免使用polyfill(自行脑补的血淋淋的例子-> prototype.js 的getElementByClassName 方法?)

- 阻止修改
        > 防止扩展: 禁止添加属性和方法, 但是已经属性存在的方法和属性可以修改和删除
        1
2
Object.preventExtension(persion); // 防止扩展, 禁止添加属性和方法, 但是已经属性存在的方法和属性可以修改
Object.isExtensible(persion); // 检测是否是可扩展, 这里是fals
        > 密封: 子集是"防止扩展", 而且增加了对象可以删除
        1
2
Object.seal(persion); // 子集是"防止扩展", 而且增加了对象可以删除
Object.isSealed(persion); // 检测是否是密封, 这里是true
        > 冻结: 子集是"密封", 而且禁止对象"修改"已经存在的属性和方法
        1
2
Object.frezz(persion); // 子集是"密封", 而且禁止对象"修改"已经存在的属性和方法
Object.isFrozon(persion); // 检测是否是冻结, 这里是true


chapter12 浏览器嗅探

- user-agent 监测
        > 浏览器的user-agent不总是正确的, 有可能呗其他工具修改   
    1
2
3
if (navigator.userAgent.indexOf("MSIE") > -1) { // 不好的写法
    // is ie
}
- 特性检测: 特性检测大法好
        > 针对浏览器特有的功能特性来监测浏览器
    1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不好的写法, 先判断浏览器类型
if (navigator.userAgent.indexOf("MSIE") > -1) { // is ie
    element = document.all[id];
}
 
// 好的写法, 只针对某特定的功能在各个浏览器下有没有, 有就执行它, 木有就执行相应的备用方法
function getById(id) {
    var element = null;
    if (document.getElementById) { // DOM ie5+ netscape 6+ 之后都支持了, 所以这个写在最前面
        element = document.getElementById(id);
    else if(document.all) { // ie, 只是作为备用
        element = document.all[id];
    else if(document.layer) { // netscape <=4, 只是作为备用
        element = document.layer[id];
    }
    return element;
}
- 特性推断: 不恰当的使用特性检测, 应该避免
    1
2
3
4
5
6
7
8
9
10
11
12
13
// 不好的写法
function getById(id) {
    var element = null;
 
    if (document.getElementByTagName) { // 用getElementByTagName来推断getElementById
        element = document.getElementById(id);
    else if(window.ActiveXObject) { // ie
        element = document.all[id];
    else // netscape <=4, 
        element = document.layer[id];
    }
    return element;
}
- 浏览器推断: 不恰当的使用特性检测和特性检测应该避免
    1
2
3
4
// 不好的写法, 呐, 就是先判断是哪个浏览器再去执行相应动作
if (navigator.userAgent.indexOf("MSIE") > -1) { // is ie
    element = document.all[id];
}






posted @ 2016-01-24 20:23  haili042  阅读(124)  评论(0编辑  收藏  举报