分享

JS学习

 BlazerOfIT 2011-12-29

语法和语义
可选的分号
标识符(Identifier)
基础数据类型和字面量(Literal)
变量的作用域
关于 ==, !=, ===, !==
关于 this, new, apply, call
关于 arguments
成员访问, prototype链, 继承
变量访问, call object, Closure
总结
惯用法和技巧
编码规范和JS书写模板
页面功能的普通功能
模块需要提供给其他模块使用(单例)
模块需要提供给其他模块使用(多实例)
为继承而设计的类
继承其他类
BOM和DOM简介
BOM
DOM
jQuery学习
设计理念
Utility
选择 与 CSS3选择器
构造jQuery对象
jquery对象结构
基本操作
事件
操作
数据
检测
前端基础库 fdevlib
style目录结构简要说明
fdev-v4现有组件
JS学习
本文目的在于为Java或有语言基础的同学参于前端开发提供指导
语法和语义
可选的分号
语句如果它们在单独一行,那么结尾的分号可以省略。
注:为了一致性和避免错误(如压缩或merge带来的错误),我们要求所有语句都要加分号(块语句和函数申明语句后不需要分号)
var a = 1;
a += 1;

var callback = function() {
    return 'hi';
};

// 函数申明语句
function other() {

}

for (var i = 0; i < 10; i++) {

}

if (a === 123) {

}
[注] 我们不能写出这样的句子
function abc() {
    return // 返回123
        123;
}
abc();  // -> undefined
标识符(Identifier)
和Java相比,Javascript允许在标识符中使用$,所以很多库都用它来定义特殊作用的全局对象
像jQuery, Prototype, Mootools等框架使用$操作页面节点, 它仅是一个普通的函数
// 在jQuery中
$('#mydiv a').css('color', '#f00');  // 设置链接颜色

// 在旧版本的Mootools中
if ($type(abc) === 'string') { // 新版本使用typeOf代替

}

// 在中文站JS基础库FDEV3中
var elm = $('mydiv');       // 相当于document.getElementById('mydiv');
所以$可以用作有"特殊"含义的变量名
// 在有些同学的代码中, 用于在内部函数中访问外围对象
var Test = {

    hi: function() {
        var $this = this;   // 我见过的常用的名称还有 that, self, me 等,
                            // 使用self有一个隐患,就是忘记了定义self时,
                            // 会造成难以排错,因为有一个全局的self会产生干忧

        $.ajax(url, {
            success: function(o) {
                o.success && $this.render(o.data)
            }
        });
    },

    render: function() {

    }
};
基础数据类型和字面量(Literal)
Number
123, 0xf000, 0377(8进制)
3.14, .222, 1.2e12, 1.4e-12
可以使用Math对象进行常见的科学计算,见 asp">http:///js/js_obj_math.asp
此外Javascript也定义了一些特殊的Number用于合适的需要, 如NaN, Number.MAX_VALUE, Number.MIN_VALUE,
参考《Javascript权威指页》3.1.6
注:当使用parseInt将字符串转成整型时, 请带上进制, 否则如果有前缀是0,会被当成8进制处理可能不符合预期需求
var num = parseInt(inputText, 10); // 如在文本框输入页码等
String
可以使用单引号或双引号两种形式

var str1 = '我是一个字符串,包含着双引号"'; // 单引号中的双引号可以不用转义,当然使用转义也木有问题

var str2 = "我是另一个字符串, 包含着单引号'";
var template =
'<div>\
    <dl>\
        <dt>示例1</dt>\
        <dd></dd>\
        <dt></dt>\
        <dd></dd>\
    </dl>\
</div>';
String和Number的转化技巧
parseInt('123', 10);    // 注: 请加上第二个参数

'123' - 0   // 注: 不能使用+

456 + ''
123456 + '元' // -> '123456元';
[String对象的方法请参考] 
http:///jsref/jsref_obj_string.asp
Boolean
false, null, undefined, 0, NaN, '' 在逻辑判断中都被当成 false 处理
var keyword = $('#keyword').val();
if (!$.trim(keyword)) {
    // 用户输入为空
}

var num = $('#num').val();
num = parseInt(num, 10);

if (!num) { // NaN
    // 用户输入非法数字
}
Function
// 申明式
function a() {

}
console.debug(a.name)   // -> 'a'


// 匿名函数
var b = function() {

};
console.debug(b.name) // -> ''
申明式和语句式区别
fun(123);   // 在这里就可以使用

function fun(value) {
    console.debug(value);
}


b(123);  // b is undefined;

var b = function() {
};
Object Literal
var album = {
    name: 'pipixiuxiu', // 注,这里的name不是变量, 只是省略了单引号
    ‘count’: 100,

    'private': true,        // 当属性名称是保留字时,必须使用这种方式

    234: '较少用',
    12.34: '更少用'
};

album.name;
album['count'];

album[234];
jQuery对象是怎么造就的?
var o = {
    0: elm0,
    1: elm1,
    2: elm2,
    3: elm3,

    length: 4,
    splice: function() {}   // firebug看到这个会以为它是个数组,所以显示方式和数组一样
    ...
}
注: 使用Literal语法构造对象,而不是new Object()
var o = {}; // 而不是 new Object();
Array Literal
var ary = [];
ary[0] = 'one';


var list = [99, 'some string', function() {}];
[Array的方法请参考]
http://www./jsref/jsref_obj_array.asp
注: **总是使用Literal语法构造数组, 而不是使用 new Array() **
var array = []; // 而不是new Array();
拼接字符串, 在Java中使用StringBuilder或StringBuffer拼接字符串,以减少临时对象的产生提高GC效率。
在JS中使用数据组达到这一目的。
var html = [];

elms.each(function() {
    html.push('some string ...');
});

html = html.join('');
[注] 在Object/Array最后一个元素后面不能加逗号,尽管这在标准上允许,但是在IE6 7 浏览器下会报语法错误
所以,如果发现在FF下工作正常,在IE下工作不正常,请检查是否object或array最后多了个逗号
var o = {
    name: 'pipi',
    desc: 'er er er',
    // price: 12    // 在调试中把这句注释了,造成上面一句最后多了个逗号
};
所以可能在某些代码中发现以下写法以避免上述错误
var o = {
    name: 'pipi',
    desc: 'er er er',
    // price: 12,
    _: 0    // 多了一个这样的元素
};
不过我不推荐这样的形式,注意点就好,因为上述形式可能会带来其他隐患,并且可读性也不好。
可以借助工具来扫描这类错误, 如JSLint: http://www./
变量的作用域
变量在JS中是函数作用域的,而不是块作用域(C, Java, C#等是块作用域)
function hello() {
    var a = 123;

    for (var i = 0; i < 10; i++) {
        var a = 234;
        //...
    }

    console.debug(a);   // -> 234
}
在函数作用域内定义的var都相当于在一开始定义一样
即一个函数执行时,引擎分两步操作
1. 处理所有var语句(和函数申明语句), 准备好执行环境, 相当于这些var和function申明语句写在函数的最前面
2. 执行函数内的语句
所以上述代码相当于这样:
function hello() {
    var a,
        i;

    a = 123;

    for (i = 0; i < 10; i++) {
        a = 234;
        //...
    }

    console.debug(a);   // -> 234
}
这会让javascript虚拟机实现closure更方便,让内存模型更简洁
function hello() {
    alert(a);   // alert(undefined);

    if (false) {
        var a;
    }
}

等效于

function hello() {
    var a;

    alert(a);

    if (false) {
    }
}
function hello() {
    alert(a);   // ReferenceError: a is not defined
}
[最佳实践]
一个函数内最好只有一个var, 并且尽可能靠前(最好是在函数一开始) 但这与块作用域的实践, 变量最好在用到的时候定义不一致
所以 函数尽可能得短小, 以满足前面两个条件
关于 ==, !=, ===, !==
一句话: 总是使用 === 和 !==进行比较
因为 == 和 != 在比较时如有必要,会进行类型转换
null == undefined   // -> true
'0' == false        // -> true
'1' == true         // -> true

null == false       // -> false
undefined == false  // -> false
[具体的比较规则请参考] ECMA262 11.9.3
如果不想看, 可能参考一条规则: null == undefined, 字符串和boolean都转成数字进行比较
关于 this, new, apply, call
当调用一个方法(函数时),函数内部的this指向一个对象, 在规范中被称为: ThisBinding, 我喜欢叫它BindObject
一般情况下, this指向调用它的那个对象或者 GlobalObject, 在浏览器中为window
var A = {
    hello: function() {
        console.debug(this);
    }
};

A.hello();  // ->  A

var b = A.hello;
b();    //-> Global Object, 在浏览器中即是window
所以函数中的this指向和如何调用它有关系,和在哪里定义这个函数关系不大
我们可以使用call或apply方法参数化this
var A = {
    name: 'A',

    function hello() {
        console.debug(this.anme);
    }
};

A.hello();  // this === A


var B = { name: 'B' };

A.hello.call(B);    // this === B
至于call和apply的区别, 只是方法支持参数的区别, 功能一样
fun.apply(o, [arg1, arg2, arg3]);

function.call(o, arg1, arg2, arg3);
new 操作符会构造一个对象, 并且让this指向它

function A() {
    console.debug(this);
}

A();    // -> window

new A(); // -> object, object !== window
function People(name) {
    this.name = name
}

var p1 = new People('p1');

var p2 = new People('p2');

assert(p1 !== p2);
assert(p1.name === 'p1');
这里我们似乎看到了js面向对象的影子
关于new的返回值
如果function返回object, 则new 返回那个object, 否则返回引擎构造的那个object

function A() {

}
new A(); //-> object


function B() {
    var o = {

    };
    return o;
}
new B();    //-> o


function C() {
    return 1;   // typeof 1 === 'number', 1 is not an object
}
new C();    // -> object, not 1
关于 arguments
函数内部除this外,还有一个对象就是arguments, 它是一个类数组,保存着函数的调用参数
function a() {
    var args = arguments,
        a = args[0],
        b = args[3],
        len = args.length;
}
我们可以对arguments进行迭代, 但如果我们要使用数组的方法操作它,就需要把它转化成数组
var ary = $.makeArray(arguments);  // $ is jQuery

var ary = [].slice.call(arguments, 0); // 如果没有库支持
成员访问, prototype链, 继承

var A = {

    name: 'A',

    hello: function() {
        console.debug('hello ' + this.name);
    }

};

A.name;     // 'A'
A.hello();  // 'hello A'

 

var CA = function() {
};
CA.prototype = A;

var o = new CA();

// 读取时, 如果必要, 则访问 prototype
o.name;     // 'A'
o.hello();  // 'hello A'


// 设置
o.name = 'o';
o.name;     // 'o'          // 找到了, 就不需要访问 prototype了
o.hello();  // 'hello o'

// A未曾改变
A.name  // 'A'
A.hello // 'hello A'
读取一个对象成员时, 会走prototype链, 设置一个对象成员时, 对prototype无影响
比如像这样:
var A = function() {}; 
A.prototype = { propPA: 'PA' }; 

var a = new A(); 
a.propA = 'A'; 

var B = function() {}; 
B.prototype = a; 

var b = new B(); 
b.propB = 'B'; 

var C = function() {}; 
C.prototype = b; 

var c = new C(); 
c.propC = 'C'; 

console.debug(c.propC);     // C 
console.debug(c.propB);     // B 
console.debug(c.propA);     // A 
console.debug(c.propPA);    // PA 
console.debug(c.toString());    // [object Object] 

所以我们可以用prototype对象来实现类, 以及用prototype链来实现继承
我们使用如下模板书写一个简单的类

var A = function() {
    this.init.apply(this, arguments); // 代理给prototype.init
};
A.prototype = {

    /**
     * 构造函数
     */
    init: function(name) {
        this.name = name; // 对象属性
    },

    /**
     * 实例方法
     */
    hello: function() {
        console.debug('hello ' + this.name);
    }
};
JS是动态的语言, 可以很方面地实现对象的功能扩展, 所以比较少用继承
如果使用继承, 我会使用以下方法来实现, 因为代码较原型链方式来得更简单
var B = function() {
    this.init.apply(this, arguments);
};
B.prototype = $.extendIf({
    init: function(name, age) {
        A.prototype.init.call(this, name); // super(name)
        this.age = age;
    },

    say: function() {

    }
}, A.prototype);
变量访问, call object, Closure
在js中,变量是函数作用域的,这一点上面已讲过
这里的访问主要考虑嵌套函数的调用, 即一个非常重要的特性Closure

var welcome = 'welcome ~~~';

function hello(name) {

    var prefix = 'hello';

    alert(welcome);

    function say(postfix) {
        var my = 'string my';

        alert(prefix + name);
        alert(my)
        alert(postfix)
    }

    return say;
}

var say = hello('alibaba');
say('good bye');
每次调用一个函数时, 执行函数代码前, Js执行引擎要做些准备工作, 其中会创建一个称为 Call Object的对象
而调用方法的形式参数, 局部变量, arguments等就成为这个对象的属性.
所以访问形式参数, 局部变量, arguments就相当于访问这个对象的属性

总结
JS的语言本身非常精简, 整个ECMA262规范只有200页, 其中有一半用来介绍了核心库的内容, 
语言语法,语义,引擎的实现全部加起来也才100页. 所以真的是非常地精巧, 但描述能力却很强大.
引擎核心内容总结如下:
this会动态地指向BindObject, 所以又称为动态作用域. 我们对this属性的访问就是访问这个对象的属性.
变量的访问,其实是访问的是Call Object的属性, CallObject链根据词法结构就已确定, 所以又称为静态作用域,或“词法作用域”
由于这个链在js中是不能访问的, 所以又称"暗链"
对象属性(成员)的访问,遵循的是prototype链, 由于这个链在js中是可以直接访问的,所以又称为"明链"
[如果对上述内容详细内容感兴趣可以参看]
* 《Javascript权威指》: 4.6 4.7
* ECMA 262 10.3 ~ 10.6
* http:///faq/notes/closures/
* 我的文章: http://b2b-doc./pages/viewpage.action?pageId=47748204
惯用法和技巧
// 检测空串
if (!$.trim(str)) {

}

// 字符串/数字类型转换
var str = '100';

var num = str - 0;

var str2 = num + '';


// 空检则
if (!value) { // undefined, null, false, 0, '', NaN 等为 '空'

}

// 逻辑或 ||

if (!a) {
    a = 100;
}

a = a || 100;   // 注意 不能写成 a ||= 100, 虽然ruby支持

 

if (!this.elm) {
    this.elm = $('#mydiv');
}

this.elm = this.elm || $('#mydiv');


var pageSize = 10;
if (data && data.pageSize) {
    pageSize = data.pageSize;
}

pageSize = (data || {}).pageSize || 10;


// 逻辑与&&

function(success) {
    if (success) {
        success('abc');
    }

    success && success('abc'); 
}

注意: 不能写成 options.delay && this.delay = true; 赋值运算附优先级低
可以是  options.delay && (this.delay = true);


// 默认参数

function hello(options) {
    options = $.extend({
        time: 1000,
        color: '#f00',
        'font-size': '12px'
    }, options);

    ...
}


// 字符串拼接

var html = [];
for (...) {
    html.push('...');
}
html = html.join('');

 

// 字符串模板
var template =
'<div>\
    <dl>\
        <dt>示例1</dt>\
        <dd></dd>\
        <dt></dt>\
        <dd></dd>\
    </dl>\
</div>';
编码规范和JS书写模板
JS是一门动态灵活的语言, 不同的同学可能写出风格完全不同的代码.
所以参考或使用此模板目的在于提高js代码一致性和可读性.
首先需要参考: 前端CodeReview列表
页面功能的普通功能
如 Diy皮肤功能, Js文件名为 diy-skins.js
我们把文件中模块代码包含在一个自执行匿名函数中, 以避免全局名字空间冲突
原则上一个文件只包含一个模块(一般是一个主类)
因为我们的merge脚本不够强, 为了按需加载和减少请求数, 有可能会把一个组件所有代码放在一个文件中 
但是也需要把每个模块放在各自的自执行匿名函数中
/**
 * 旺铺Diy后台皮肤选择
 * @author qijun.weiqj
 */
(function($, WP) {


[1]
var Util = WP.Util,
    UI = WP.UI,
    PageSwitcher = WP.widget.PageSwitcher
    ...

[2]
var DiySkins = {

    [3]
    init: function() {

    },

    initCatsPanel: function() {

    },

    initSkinPanel: function() {

    }

};


[4]
WP.PageContext.register(...)

// 相当于

// 页面载入后就执行
$(function() {
    Diy.Skin.init();
})

})(jQuery, Platform.winport);
[1]. 这个区域相当于java的import区, 根据需要可以alias一些用到的类
当然现在我们还需要在merge文件中包含需要的库文件.
后续我们会期望引入脚本来自动merge导入的类
像这样:
var Util = require('widget/util'),
    UI = require('widget/ui'),
    ModBox = require('diy/mod-box');
[2]. 主模块(类/对象)名要求和js文件名尽量保持一致
[3]. 初始化方法或构造函数名称为init
[4]. 最后当页面domready时应该执行初始化方法, 所以调用jquery.ready方法 (在旺铺中进行一些封装,所以调用PageContext.register进入注册)
模块需要提供给其他模块使用(单例)
(function($, WP) {

var UI = {

    [1]
    /**
     * 缩放图片
     * @param {jquery} selector ...
     * @param {int} size ...
     */
    resizeImage: function(selector, size) {

    },

    [2]
    _resize: function() {

    }

    /**
     * IE6 position fixed
     */
    positionFixed: function(selector) {

    }

};

[3]
WP.widget.UI = UI;

})(jQuery, Platform.winport);
相当于方法, 可由其他模块调用
下划线带头的方法, 不可由其他模块调用
挂接到相应的名字空间下, 以便由外界可以使用
模块需要提供给其他模块使用(多实例)
(function($, WP) {


var Tabs = function() {
    this.init.apply(this, arguments);
};

Tabs.prototype = {

    init: function(tabs, bodies) {

    },

    _create: function() {

    }
};

WP.widget.Tabs = Tabs;


})(jQuery, Platform.winport);
为继承而设计的类
(function($, WP) {


var Dialog = function() {
    this.init.apply(this, arguments);  
};

Dialog.prototype = {

    [1]
    init: function(options) {
        ..
        render(this);
    },

    [2]
    close: function() {

    },

    [3]
    _createButtons: function() {

    }
};

[4]
function render(self) {
    self._createButtons();
}


WP.widget.Dialog = Dialog;


})(jQuery, Platform.winport);
[1]. 构造函数
[2]. public方法
[3]. protected方法, 可由子类重写
[4]. private方法
继承其他类
(function($, WP) {

var Dialog = WP.widget.Dialog;

var FormDialog = function() {
    this.init.apply(this, arguments);
};
FormDialog.prototype = $.extendIf({

    init: function(options) {
        [1]
        Dialog.prototype.init.call(this, options);
    },

    [2]
    _createButtons: function() {
        Dialog.prototype._createButtons.call(this, ...);
        ...
    },

    [3]
    __createForm: function() {
    },

    [4]
    getData: function() {

    }

}, Dialog.prototype);


[5]
function createForm2(self) {

}


WP.diy.FormDialog = FormDialog;


})(jQuery, Platform.winport);
[1]. 调用父类构造函数
[2]. 重写父类方法, 调用父类方法
[3]. private方法
[4]. public方法
[5]. private(和3选择一种形式即可)
关于文档注释可参考: Google Javascript Gude
BOM和DOM简介
BOM
BOM就是浏览器提供给我们可以操作浏览器的API
我们常用的有:
打开新窗口, 查看当前URL信息, 检测浏览器类型和版本号, 返回前一页, 查询屏幕分辨率等
[参考]
http:///jsref/
《Javascript高级程序设计》第8章,第9章
DOM
DOM提供用于访问HTML/XML文档的API
浏览器之间有差异性,所以一般使用js类库来间接访问DOM API
有时候页面较简单,或者没有JS库引入, 那可以直接使用
一般文档节点操作: http:///jsref/
样式/样式表处理(如旺铺DIY): 《Javascript高级程序设计》10.3.2, 11.2
jQuery学习
设计理念
// jQuery是一个function, 返回一个对象(称为jQuery对象)
var jQuery = function() {
    return new jQuery.fn.init(...);
};

// jQuery对象的实例方法由 jQuery.fn决定
jQuery.prototype = jQuery.fn = { ... };
jQuery.fn.init.prototype = jQuery.fn;


// 采用这种方法扩展jQuery
jQuery.fn.bind = function() { ... };


// 采用这种方法扩展静态方法
jQuery.extend = function() {...};
jQuery.each = function() {...};
所以jQuery包含两种操作:
一种是和DOM结点无关, 直接调用静态方法即可, 如$.ajax, $.each等
一种是和DOM结点相关, 需要首先构造jQuery对象, 再使用它的实例方法, 如事件绑定jQuery#bind, 样式设置jQuery#css等
Utility
<script src="http://style.china.alibaba.com/js/lib/fdev-v4/core/fdev-min.js%22%3E%3C/script>
(function($) {

// 这里$就是jQuery

})(jQuery);
$.ready 初始化
dom ready后,表示可以使用js操作页面dom节点, 它比onload要早, 因为onload要等图片全部加载完
$.ready(function() {

});

// 可以简写

$(function() {


});
$.each 迭代
var array = ['one', 'two', 'three'];
$.each(array, function(index, item) {
    // 当item类型为object时, 可使用this访问item, 否则请使用参数方式访问item
});

var o = { name: ..., value: ... };
$.each(o, function(key, value) {

});
$.extend 扩展

// 默认参数
function hello(options) {
    options = $.extend({
        title: '测试',
        delay: 1000
    }, options);
    ...
}

// 扩展功能
var A = {
    a: function() {

    }
};

$.extend(A, {
    b: function() {

    }
});


// clone
var o = {...};

var other = $.extend({}, o);
我们可以扩展jQuery功能, 但在应用中我们不允许这么做, 维护fdevlib的同学可以根据需要扩展库

$.extend({
    add: function() {

    },
    use: function() {

    }
});

$.fn.extend({
    tabs: function() {

    }
});
$.extendIf (由fdevlib实现), 类似于extend, 但如果存在相同key的项则不添加, 我常常使用它来实现"继承"
var Base = {
    a: function() {
    },

    b: function() {
    },

    c: function() {

    }
};

var A = $.extendIf({

    b: function() {

    },

    d: function() {

    }

}, Base);

var Base = function() {
    this.init.apply(this, arguments);
};
Base.prototype = {

};


var A = function() {
    this.init.apply(this, arguments);
};
A.prototype = $.extendIf({

}, Base.prototype);
ajax
跨域jsonp请求

$.ajax(url, {
    dateType: 'jsonp',
    success: function(o) {
        o.success && self.render(o.data);
    }
});
浏览器会产生一次url类似 url?callback=jQuery23522552_24342344的调用
期望浏览器返回这样的文本
jQuery23522552_24342344({"success": true, data: ...})
此段代码将会作为js代码在客户端执行,即调用一个由jquery随机生成的方法, 这个方法会调用我们的success方法,
于是我们就得到了数据
跨域调用要求使用jsonp方式,从避免造成全局名字空间污染和覆盖, 也方便浏览器对数据进行垃圾回收
返回的数据格式要求如下:
{ "success": true | false, data: ... }
动态载入script文件

$.ajax(url, {
    dateType: 'script',
    success: function() {
        // 在这里script文件就载入完毕了
    }
});
注, 由于IE实现上的原因,造成造成script文件返回302也会进入success逻辑
所以如果如果采用古老的get script方式来跨域请求数据的话, 需要在success里进行判断.

$.ajax(url, {
    dateType: 'script',
    success: function() {
        if (window.myData) {
            ...
        }
    }
});

// 以上服务器反回这样的串串: var myData = ... 
当然我们也可以使用这个方法发起xhr请求
$.ajax(url, {
    type: 'post',
    data: { ... }
    success: function(html) {
        div.html(html);
    }
});
注: 在IE下,xhr请求, 服务器如果返回302跳转(如另一页面退出后,旺铺DIY后台发起xhr post请求,服务端会返回302), 将会进入success逻辑
默认情况下, 如果你ajax请求(包括xhr, jsonp, getScript), 如果有参数data, 并且类型为object, 则jQuery使用
$.param来编码你的数据, 内部会使用 encodeURIComponent, 这将导致中文编码成utf8.
如果后端木有按要求解码,这可能会产生问题. 有两个解决方法: 
1.前端不编码
自己使用$.paramSpecial-- 这个方法由fdev-v4提供, 用于object -> string, 并且不编码, 只转义几个uri中不允许的字符
2. 要求后端解码(可能需要加参数input_charset参数), 这一条优先采用
关于ajax或编辑相关的jquery api有:
$.param, $.paramSpecial, $.serialize, $.serializeArray
额外阅读:
关于跨域请求数据,除了使用getscript jsonp还可以使用window.name机制, work平台就使用了这个机制
具体请参考: http://www./2008/09/01/window_name_transport/
关于script执行和浏览器渲染阻塞问题,请参看
《高性能网站建设进阶指南》第4章或《高性能Javascript》第一章
动态加载 $.add, $.use
这两个方法由fdev-v4提供, 使得我们可以按需加载组件
动态加载的方式使用我们的组件
$.use('web-alitalk', function() {
    FE.util.alitalk($('a[data-alitalk]'));
});
在我们的应用中使用这种机制

// 在应用配置文件中注册
$.add('wp-dialog', {
    js: ['http://....dialog.js']
});

// 在需要的地方使用
$.use('wp-dialog', function() {
    new WP.widget.Dialog({
        ...
    });
});
类型检测
根据我的经验,最有用的只有两个
$.isArray - 判断是否为数组
$.isPlainObject - 判断是否为"简单"对象

if (typeof a === 'string') {

}

if (typeof a === 'function') {

}

if ($.isArray(a)) {

}
$.trim
 如果引入了fdev-min.js, 则可以使用String#trimg() 或 $.trim()
 如果仅仅使用jQuery, 则可以使用$.trim(), 因为有些浏览器(例IE6) 没有实现trim方法
$.namespace
此方法由fdev-v4提供
namespace用于方便创建一个多层嵌套的对象结构用于代表名字空间(类似于java的package)
在应用中,我们不允许污染全局名字空间,一个应用往往只分配到一个名字空间
比如旺铺是 Platform.winport, offer发布是在 Platform.postoffer
那我们旺铺中的其他类都在这个名字空间下, 初始化时会分配了如下几个名字空间
前后台都需要,在global/base.js中
jQuery.namespace(
    'Platform.winport',
    'Platform.winport.widget',      // 业务无关组件
    'Platform.winport.unit'         // TODO 后续去掉mod.unit名字空间
    'Platform.winport.mod',         // 板块
);
后台, 在page/diy/diy.js中
jQuery.namespace(
    'Platform.winport.diy',
    'Platform.winport.diy.form'     // 板块编辑
);
注: 上述为何有的文件在global中,有的文件在 page/diy中, 请参看 style目录结构规划, 
直接看文档 http://fd./doc/page/regulations/dir-structure
$.proxy

var Page {

    init: function(config) {
        var self = this;

        this.config = config;

        $.use('ui-dialog', function() {
            self.initDialog();
        });
    },

    initDialog: function() {
        // assert(this === Page);

        node.dialog({
            center: this.config.center 
        });
    }
};

// 使用proxy

var Page = {

    init: function() {
        $.use('ui-dialog', $.proxy(this, 'initDialog'));
    },

    initDialog: function() {

    }
};
下面这样写达不到目的
var Page = {
    init: function(config) {
        this.config = this.config;

        $.use('ui-dialog', this.initDialog);
    },

    initDialog: functoin() {
        assert(this !== Page);

        this.config // undefined
    }
};
所以proxy用于方便创建一个代理function, 让被代理的function具有指定的context
1.6版本的proxy还可以包装额外的参数,但文档还没有更新,具体可参看源码 1.6.2 第804行
提外话:
一个看源码技巧: 在页面中引入jquery非压缩版.
在firebug script tab的watch中输入jQuery, 展开对象找到相应的方法单击即可看到相应的源码

var Page = {

    init: function() {
        var arg1 = 'arg1',
            arg2 = 'arg2';

        $.ajax(url, {
            dateType: 'jsonp',
            success: $.proxy(this, 'ajaxSuccess', arg1, arg2);
        });
    },

    ajaxSuccess: function(arg1, arg2, ret) {
        assert(this === Page);
        assert(arg1 === 'arg1');
        assert(arg2 === 'arg2');

        if (ret.success) {
            ...
        }
    }

};
为了提高JS代码可读性, 我曾做过分享, 其中有一条是:
一个功能性方法,代码行数不超过40行(正常情况下会在一屏之内)
如果要达到上述要求,方法里面将不可能包含很深的嵌套, 即代码嵌套层次不超过3
如:
var Page = {
    initPartA: function() {
        var self = this;

        // level 1
        $.ajax(url, {
            dateType: 'jsonp',
            // level 2
            success: function(ret) {
                // level 3
                ret.success && self.render(ret.data);
            };
        });
    },

    render: function(data) {

    }
};
其他常用方法
$.map, $.makeArray
function test() {
    var args = $.makeArray(arguments);
}

// 参数可以是数组或非数组
function test(args) {
    args = $.makeArray(args);
}
$.map 相当于 CollectionUtils.transform
var links = $('a'),
    urls = null;

urls = $.map(links, function(index, link) {
    return $(link).attr('href');
});
选择 与 CSS3选择器
认识选择器
简单选择器(simple selecotr)
$('*');             // universal selector   星号选择器     选择所有节点

$('div');           // type selector/element selector 类型选择器(我习惯叫它tag选择器)

$('#header');       // id selector          id选择器

$('.input-text');   // class selector       class选择器

$(':input');        // pseudo selector      伪类选择器     选择所有表单输入域
// a:hover, a:linked, a:visited 两个冒号带头的就是 伪类选择器
$('li:first')       //                                      选择第一个li节点

$('[name="alibaba"]')   // attribute selector   属性选择器 选择所有name属性=alibaba的节点
$('[name]')             //                                  存在...


combinator

~~~js
$('#header a')      // descendant selector  后代选择器
$('#header ul li')

$('#header>div')    // child selector       子代选择器


// 下面两个邻代选择器应该不常用, 我从来没有用过
$(':input+span')        // next adjacent selector       在input后面的, 紧挨着的那个span
$(':input~span')        // next sibling selector        在input后面的, 所有同代span
$('div,a,:input')           // multiple Selector
$('a.close:acitve')         // and
使用选择器
尽量使用简单的选择器, 如
$('#id')
$('a.close', container) // class需要带tag限制, 
                        // 由于在ie6等浏览器对class没有原生api支持,使得单纯的class selector比较慢
$('ul>li', container)   // 有context限制
优先级
非常简单, 一句话:
id > 非(id,tag) > tag, 相同等级看数量
参看 http://www./TR/css3-selectors/ 第9节
[额外阅读]
CSS3选择器详细内容请参考W3C文档: http://www./TR/css3-selectors/
jQuery支持的选择器请参考: http://api./category/selectors/
构造jQuery对象
// from 选择器
$('#publish-dialog a.close');
// from dom节点
var dom = $('#doc')[0];
$(dom);
// from jquery对象
var chooser = $('div.offer-chooser');
var panel = chooser.find('ul.chooser-panel');
或者
var panel = $('ul.chooser-panel', chooser); // 内部调用 find来完成
jquery对象结构
如果针对本文档,在firefox script watch中输入 jQuery('ul'), 将创建一个jQuery对象,
用于操作文档中所有ul节点, 展开它会看到如下对象结构
{
    0: ul, // HTMLUListElement, 即原生dom节点对象
    1: ul,
    2: ul,
    ...
    7: ul,

    length: 8,

    selector: 'ul',
    context: document,
    prevObject: document
    ...
}
jQuery对象是一个普通对象,有数组相似的接口,所以可以像数组一样参于迭代
构造一个jQuery对象成本不大,因为只包含一些引用字段,没有大数据字段
了解这个是为了让我们更好地使用jQuery,而不是把$(美元符号)当作一种奇怪的语法来用.
见下面这段jQuery初学者常见的代码
$('a.close', div).click(function() {
    $(this).addClass('abc');
    if (...) {
        $(this).attr(...)
    } else {
        $(this).data(...);
    }

    $(this)...
});
为了增加可读性,提高效率和减少不必要的内存消耗, 请记住 $(elm) 等于 new jQuery(elm);
只构造必要的jQuery对象, 上述代码可以这么写
elms.click(function() {
    var elm = $(this);  // 保存引用
    ...
});
基本操作
var lis = $('ul.offer-list li');
if (!lislength) { // 判断节点是否存在
    return;
}

lis.eq(0).addClass('first');    // 只对第一个节点处理, 相当于  $(lis[0]).addClass('first');

var firstLi = lis[0];   // 取得第1个节点(html dom元素)
lis.each(function(index) {
    var li = $(this);   // each中的this是原生的dom对象
    li.data('offer', offers[index]);
});
事件
普通事件

$('a.open', panel).click(function(e) {
    e.preventDefault();

    new Dialog({
        header: '设置'
        width: '700',
        height: '350',
        confirm: function() {
            ...
        }
    });
});
上述事件挂接,也可以使用bind
button.bind('click', function(e) {
    ...
});
click方法只是bind方法的一个包装, 相似的还有其他事件: change, resize, dblclick, keypress 等
几乎所有常用的浏览器事件都有直接对应的方法用于挂接或触发事件
关于其他事件请参考: http://api./category/events/
关于事件函数有几点说明:
关于this
this指向触发事件的元素(html dom节点)
$('a.delete', list).click(function(e) {
    e.prevent();

    var link = $(this),         // here, is a html element
        li = link.closest('li'),

        postId = li.data('postId');

    Post.delete(postId);
});
关于参数event
我们可以从中获取一些关于事件的参数, 如鼠标位置,键盘输入等等, 
或者取消浏览器默认事件, 阻止冒泡
$('a.close', dialog).click(function(e) {
    e.preventDefault();     // 阻止链接正常行为, 
                            // 因为这个链接是作为功能按扭来使用的, 而不需要跳转或重新定位瞄点
    ...
});


$('div.canvas').mousemove(function(e) {
    var x = e.pageX,    // 鼠标位置
        y = e.pageY;

});

$('input.username').keydown(function(e) {
    var self = this;

    if (e.keyCode === 13) { // 回车
        self.submitForm();
    }
});
关于event object请参考: http://api./category/events/event-object/
触发事件
有时候需要人为地触发一个浏览器事件, 如提交表单
或者打开浮层登录框框后需要默认选择第二个tab, 就相当于"按"一下那个tab
form.submit();
tabs.eq(2).click();
或者
form.trigger('submit');
tabs.eq(2).trigger('click');
同bind, 无参数的click或submit只是trigger的一个包装
trigger它会执行浏览器默认行为, 比如当你click一个checkbox的时候, 界面中的checkbox也会勾选或取消勾选
同时trigger还会冒泡(关于事件冒泡,下面会有)
如果只想执行事件,而不触发默认行为, 请使用triggerHandler
同时trigger和triggerHandler还支持额外的参数,具体可参考文档
http://api./trigger/
http://api./triggerHandler/
事件冒泡
body
    div#doc
        div#header
            div.search-bar
                input.search-btn[type=button]
当我们点击input.search-btn时, input.search-btn, div.search-bar, div.#header, div#doc, body 依次会触发click事件.
这个过程叫事件冒泡
关于事件冒泡更细致的内容可参考: http://api./category/events/
所以如果我们所事件函数挂接在外围节点, 将能接收到里面子节点的事件.
容器内部节点动态创建的事件绑定
div.click(function(e) {
    if ($(e.target).is('a.delete')) {
        // ..  
    }
});

以上代码可以这样写:

div.delegate('a.delete', 'click', function() {

});
如果有很多个节点需要绑定事件, 此时可以使用冒泡绑定在外围容器上, 很大地提高效率

// bigtable为大表格
$('table.bigtable td').dblclick(function() {
});

// 请使用事件代理绑定事件
$('table.bigtable').delegate('td', 'dblclick', function() {

});
自定义事件
// move.js
saveLayout: function() {
    ...
    $('window').trigger('diychanged', { type: 'layout' });
}

// diy-skins
saveSkin: function() {
    $('window').trigger('diychanged', { type: 'diyskins' });
}


// header.js
showTips: function() {
    $('window').bind('diychanged', function(e, data) {
        new Tips({
            //...
            message: '点击发布可应用您的修改到旺铺'
        });
    });
}
namespace事件
$('a.mylink').bind('click', function() {

});

$('a.mylink').bind('click.mylink', function() {

});

// 移除事件
$('a.mylink').unbind('click.mylink');


// 触发指定namespace事件
$('a.mylink').triggerHandler('click.mylink');
关于namespace的参考链接, 官网上我一时找不到了, 不好意思,以前记得有的,现在只有一点点:
http://api./bind/
http://api./unbind/
或者直接参看源码可能会更清晰: jquery 1.6.2, 第2619行, 第2726行, 第2810行 
操作
节点操作的api可直接参考 http://api./category/manipulation/
有几点需要说明, dom节点操作是所有操作里最慢的, 往往页面的效率瓶颈就在dom操作,所以要特别注意
改变属性的操作 如: addClass, removeClass, css等需要浏览器重绘节点
而html, append, prepand,等节点操作需要浏览器重排节点并且重绘
具体请参考: 《High Performance Javascript》chapter 3
可以像这样:
render: function(offers) {
    var self = this,
        html = [];

    $.each(offers, function(index, offer) {
        html.push(self.createItemHtml(offer));
    });

    div.html('<ul>' + html.join('') + '</ul>');
}

或者

render: function(offers) {
    var self = this,
        ul = $('<ul>');

    $.each(offers, function(index, offer) {
        ul.append(self.createItem(offer));
    });

    div.append(ul);
}
数据
jQuery#data 用于在节点上保存节点相关的数据, 这样在需要的时候就可以根据节点取得相应数据

render: function(offers) {
    var self = this,
        ul = $('<ul>');

    $.each(offers, function(index, offer) {
        var li = self.createItem(offer);
        li.data('offer', offer);    // here
        ...
    });
    ...
},

handleP4p: function() {
    var self = this;

    div.delegate('a.title', 'click', function() {
        var li = $(this).closest('li'),
            offer = li.data('offer');

        self.p4pclick(offer);
    });
}
html5data标签和data方法
<div class="mod wp-supplier-info" data-mod-config='{"requestUrl":"http..."}'>...</div>
我们使用html5 data标签代替以往的input:hidden域,来保存后端需要传递给前端的页面参数
var mod = $('div.wp-supplier-info'),
    config = mod.data('modConfig');     // 省略前缀data, 后面的转化成camel形式,
                                        // 返回的是一个json object(如果确实是的话)
<div class="offers-container" data-request-url="http://....">
</div>
var container = $(...),
    url = container.data('requestUrl');     // typeof url === 'string'
jQuery会足够智能地解析出需要的结构,大概规则是:
1. 如果是true/false/null/number就转化成相应的数据
2. 如果是json串,就转化成object
3. 当作string处理
感兴趣的可以查看源码: 1.6.2版 1689行,dataAttr方法
检测
可以使用 $.util.ua (由fdev-v4包装) 来检测是否IE浏览器和版本号 
可以使用 $.browser 来检测浏览器的信息
可以使用 $.support 进行浏览器特性检测 

// 浏览器检测
if ($.util.ua.ie6) {

}

// 浏览器检测
if ($.browser.webkit) {

}

// 特性检测
if ($.support.opacity) { // 是否支持透明, 现在IE不支持

}
使用浏览器兼容的解决方法
如果办不到1, 请先使用特性检测
最后一条路,才使用浏览器检测
具体请参考: http://api./jQuery.support/
其他api可以直接看文档就可以,而且官方文档都有相关例子,不需要再做介绍
前端基础库 fdevlib
style目录结构简要说明
lib - 基础库
    fdev-v4
        core
            fdev-min.js
        widget
            ui
                dialog.js  
            util
                cookie.js
                json2.js
            web
                alitalk.js

sys - 公共业务组件
    head
    ibank
    popsigin


app
    platform
        company
        member
        postoffer
        work
        winport
            global
                winport-merge.js
            module
            page
                diy
                    diy-merge.js
前端基础库请参考: http://fd./fdevlib/#home 
应用stle目录结构说明请参考: http://fd./doc/page/regulations/dir-structure
fdev-v4现有组件
fdev-v4
    core
        fdev-min.js     我们使用的时候引入这个文件
            config.js           动态加载的组件配置信息
            gears.js            定义了一些基本方法,如String#trim,
                                还有一些方法移自fdev3, 还有就是动态加载$.add, $.use等方法
            jquery1.6.2.js
            web.js              可以使用FE.util.lastLoginId获得用户登录信息
                                所以用户登录等cookie信息的函数由这个文件定义

    widget
        ui 
            autocomplete        自动补全,像我们的搜索
            colorbox           
            colorpicker         颜色选取
            combobox            自定义样式下拉框(原生下拉框比较丑)
            datapicker          日历选择
            dialog              对话框

            draggable           拖动
            droppable           拖放
            sortable            拖动排序(来自jquery ui)
            portlets            拖动排序,目前用于旺铺diy后台

            flash-chart         flash图片组件(YID)
            flash-storage       flash本地存储
            flash-uploader      上传文件
            flash-websocket     flash长链接
            flash               flash显示

            menu
            progressbar         进度条
            scrollto            页面滚动

            tab                 tab组件
            timer              

        util
            cookie              cookie操作(写cookie时需要引入)
            date       
            debug               调试
            history             历史记录 work平台有使用
            json2               json
            storage             本地存储

        web
            alitalk             阿里旺旺
            browser        
            datalazyload        延迟加载
            pca
            stylesheet          操作样式表
            sweet               js模板
            valid               前端验证
            websocket           长链接

作者“梦工厂”

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多