outline

1 JS常见使用场景
2 JS的重要概念(包含IIEF、JS原型链、this(call/apply)、闭包)最后我们结合闭包和call函数完成一个类型检测
3 浏览器端的javascript(包括原生DOM操作、用模板来完成原生DOM操作、jQuery操,基于此我们完成一个简单的单页应用)
4 其他

  • JS常见使用场景
    a. 使用jQuery修改DOM

    1
    2
    3
    4
    5
    <script src="path/to/jQuery"></script>
    <script>
    $("#id").html("");
    ...
    </script>

    b. 使用jQuery插件

    1
    2
    3
    4
    5
    <script src="path/to/jQuery"></script>
    <script scr="path/to/jQuery-customPlugin.js"></script>
    <script>
    //your custom code
    </script>

    c. 使用bootstrap

    1
    2
    3
    4
    <link rel="stylesheet" href="path/to/bootstrap.min.css">
    any element
    <script src="path/to/jQuery"></script>
    <script src="path/to/Bootstrap.js"></script>

    d. 使用jQuery做动画(下拉菜单)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <ul id="mylist">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    </ul>
    <script src="path/to/jQuery"></script>
    <script>
    $("#mylist").slideToggle('normal');
    </script>

    e. 使用ajax

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $.ajax({
    url:'targetUrl',
    data:{
    key1:value1,
    key2:value2
    }

    success:function(data){

    }

    });

    f. 其他
    这里例举了一些常用的库
    echarts
    d3
    normalize.css
    fontawesome
    underscore

  • JS的重要概念
    a. IIEF

    IIEF(immedite invoke express function),常见如下形式:
    
    1
    2
    3
    (function($){

    })
    (jQuery)

    b. JS原型链
    控制台的prototype

    1
    2
    3
    4
    5
    6
    7
    8
    //假设一个对象o,自身有属性a、b
    //o的原型有属性b、c
    //o的原型的原型指向object
    var o = {a: 1, b: 2};
    o.prototype = {
    b : 3,
    c : 4,
    }

    整个原型链如下:
    o_prototype.png
    原型链在使用过程中,一层层查找,优先使用当前对象的属性。

    1
    2
    3
    4
    console.log(o.a); // 1
    console.log(o.b); // 2 "属性屏蔽 (property shadowing)"
    console.log(o.c); // 4
    console.log(o.d); // undefined

    JS里的继承、特性都是基于原型链.
    c. this(call/apply)
    JS里的this跟其他语言不太相同,JS中的this总是指向一个对象,但是具体指向那个对象在运行是决定的.JS中this的指向给我们会带来很多的苦恼.
    对象中this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    window.a = 2;
    var obj = {
    a : 1,
    getA : function(){
    console.log(this);
    console.log(this.a);
    }
    }
    obj.getA();//1

    var _getA = obj.getA;
    _getA();//2

    this_in_obj.png

    函数中的this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <html>
    <body>
    <div id="div1">我是div</div>
    </body>
    <script>
    window.id = "window";
    document.getElementById("div1").onclick = function(){
    console.log(this.id);//div1
    var callback = function(){
    console.log(this.id);//window
    }
    callback();
    }
    </script>

    </html>

    解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <html>
    <body>
    <div id="div1">我是div</div>
    </body>
    <script>
    window.id = "window";
    document.getElementById("div1").onclick = function(){
    console.log(this.id);//div1
    var _this = this;
    var callback = function(){
    console.log(_this.id);//div1
    }
    callback();
    }
    </script>

    </html>

    this的指向在运行时才会确定,控制的难度略大,但是在上面的例子中,我们通过强行保存this的指向避免this乱指的问题,但是这样的方式未免不会有点过于生硬.这里我们通过apply/call来提供另外一种解决方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <html>
    <body>
    <div id="div1">我是div</div>
    </body>
    <script>
    window.id = "window";
    document.getElementById("div1").onclick = function(){
    console.log(this.id);//div1
    var callback = function(){
    console.log(this.id);//div1
    }
    callback.call(this);
    //或者
    //callback.apply(this);
    }
    </script>

    </html>

    这里call/apply被我们用来修正this的指向.
    除了修正this的指向之外,我们还可以用call/apply做一些有意思的事情,比如”借用”方法/继承.(补充一下,apply和call方法的作用类似,apply和call的第一个参数都是一个对象,但是apply的后面的传参方式是把要传入的参数构造成一个数组,而call的话,就把参数一个一个传入就可以了.A(pply) for Array and C(all) for Comma)

    1
    2
    var arr = [1,4,3];
    arr.push(5)

    对于Array类上的push方法,Object也是可以借用的

    1
    2
    3
    var o = {};
    Array.prototype.push.call(o,1);
    Array.prototype.push.call(o,2);//Object {0: 1, 1: 2, length: 2}

    借用方法的时候要找个类似的对象上的方法借用

    1
    2
    var func = function(){}
    Array.prototype.push.call(func,"fun");

    //Uncaught TypeError: Cannot assign to read only property 'length' of function 'function (){}'
    d. 闭包
    这里讲的闭包主要是说的变量的作用域以及生存周期
    变量作用域(作用域链)
    JS中的函数可以创造作用域.函数内部可以看到外部的外部的变量,但函数外部却无法看到函数内部的变量.在JS中,如果要是用某个变量,如果在当前函数(作用域)没有找到,会自动顺着找到函数的外部,一直到到全局对象window为止.
    这和C++/JAVA很不同,如果函数中的没有找到某个局部变量,会报错.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var a = 1;
    var func1 = function(){
    var b = 2;
    var func2 = function(){
    var c = 3;
    console.log(b);//2
    console.log(a);//1
    }
    func2();
    console.log(c);//undefined;
    }
    func1();

    JS中这条作用域链也会造成很大的麻烦,尤其是很容易出现a = 100;这种代码.这样相当于window.a = 100;,作用域的问题造成了JS的代码的可维护性降低,对应的也有sea.js这样的JS模块加载器,我们只要按照commonjs规范写代码,并利用export把方法、变量暴露出去,就可以减小变量冲突的风险.提升JS组件、功能模块可维护性.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <html>
    <head>
    <meta charset="UTF-8">
    <title>closure</title>
    </head>
    <body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <script>
    var nodes = document.getElementsByTagName("div");

    for(var i = 0; i < nodes.length; i++){
    nodes[i].onclick = function(){
    console.log(i);
    }
    }
    </script>

    </body>
    </html>

    这里不管点击哪一个div,都会输出5.原因在于点击事件是异步触发的,当用户点击div的时候,for循环早就执行完了,此时i的值是5,当用户点击了某个div的时候,div顺着onclick函数的作用域链,找到i(5),并输出.解决方法,用function来构造一个闭包的环境,让i的值(1…4)传入一个立即执行函数,让i值存活在闭包的里.当异步调用的时候,可以找到这个i的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <html>
    <head>
    <meta charset="UTF-8">
    <title>closure</title>
    </head>
    <body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <script>
    var nodes = document.getElementsByTagName("div");

    for(var i = 0; i < nodes.length; i++){
    (function(i){
    nodes[i].onclick = function(){
    console.log(i);
    }
    })(i);
    }
    </script>

    </body>
    </html>

    另一种闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <html>
    <head>
    <meta charset="UTF-8">
    <title>closure</title>
    </head>
    <body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <script>
    var nodes = document.getElementsByTagName("div");

    for(var i = 0; i < nodes.length; i++){
    nodes[i].onclick = function(i){
    return function(e){
    console.log(i);
    }
    }(i);
    }
    </script>

    </body>
    </html>

    d. 应用(call+闭包):
    类型检测
    由于JS本身是一个弱类型的语言,实际使用中,尤其对于ajax请求回来的数据最好做好类型检测.
    原理:检测方法很多,比如判断一个对象是否是数组,可以判断这个对象有没有sort方法.但是这样的检测方法并不通用.我们可以借用Object.prototype.toString方法来判断,对于一个Array类型的对象,toString总是返回[object Array],对于一个String类型的对象,toString方法总是返回[object String].

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //version1:
    //这里不直接使用obj上的方法主要是obj上通常会有自己的toString方法,由于"属性屏蔽",
    //在原型链上还没有找到Object上的toString方法就会停止.换用call方法可以避开原型链查找,
    //直接调用方法Object上的toString方法
    var isString = function(obj){
    return Object.prototype.toString.call(obj) === '[object String]';
    }
    var isArray = function(obj){
    return Object.prototype.toString.call(obj) === '[object Array]';
    }
    var isNumber = function(obj){
    return Object.prototype.toString.call(obj) === '[object Number]';
    }

    在version1版本中,我们三个isType函数,但是我们发现这三个函数长得还是非常类似的,这个过程完全可以抽象出来,下面我们将在version2版本中改进:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //version2
    //这里我们需要用到JS中的高阶函数的一点知识,函数可以作为返回值返回
    //闭包
    var isType = function(type){
    return function(obj){
    return Object.prototype.toString.call(obj) === '[object ' + type + ']';
    }
    }
    var isString = isType('String');
    var isArray = isType('Array');
    var isNumber = isType('Number');

    在version2中的类型检测中,我们把类型检测的函数抽象了出来,构造三个类型检测函数.但是,构造三个类型检测的过程还是罗嗦,我们可以通过一个for循环来构造这些类型检测函数.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //version3
    var Type = {};
    var typelist = ['String','Array','Number'];
    for(var i = 0; i < typelist.length; i++){
    (function(type){
    Type['is'+type] = function(obj){
    return Object.prototype.toString.call(obj) === '[object '+ type +']'
    }
    })(typelist[i]);
    }
  • javascript (浏览器端)JS的基本语法
    a. 原生DOM操作
    很多人刚开始接触前端的开发,就直接上手JQuery这种JS工具库,或者随着前端的热潮莫名其妙的就开始使用了所谓的框架,喊着MVC、MVVM的口号做着一些知其然不知其所以然的开发.
    这里重温下原生DOM操作,希望web开发追本溯源,不要被各种各样的轮子冲晕了头.
    很多人在刚接触web开发,都知道一个概念就是DOM,DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合.说白了DOM就是沟通所谓的HTML字符串和JS的桥梁(提供document、window对象供JS操作).在HTML页面从html节点到一个<!-- -->注释节点都在DOM中有其对应的位置.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //配合对应的index.html
    //手动添加元素
    var div = document.createElement('div');
    var p = document.createElement('p');
    p.innerHTML = "hello world";
    div.appendChild(p);
    document.body.appendChild(div);

    //手动载入script脚本
    var script = document.createElement("script");
    script.src = "http://cdn.bootcss.com/jquery/2.2.1/jquery.js";
    document.body.appendChild(script);

    //TASK手动载入图片
    //应用场景,实际应用中图片并不是一次性加载完成的,而是根据当前的屏幕的滑动状态做的一个'lazy loading'
    //tips 创建一个image元素,然后设置image元素的src属性

    b. 用模板来完成原生DOM操作
    原生的DOM操作功能很强大,但是对于一些应用场景比较复杂的情况写起来比较繁琐,代码的可读性并不强,
    //比如我们要做一个动态增加表格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <html>
    <head>
    <meta charset="UTF-8">
    <title>addTable</title>
    </head>
    <body>
    <table style="margin: 0 auto;width:500px;text-align: center;" id="dataTable" border="1px solid black">
    <tbody>
    <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
    <th>height</th>
    <th>weight</th>
    </tr>
    <tr>
    <td>1</td>
    <td>dingwenjiang</td>
    <td>23</td>
    <td>175</td>
    <td>150</td>
    </tr>
    </tbody>

    </table>
    <button id="btnAddRow" style="position: absolute;right: 300px;">增加一行</button>
    <script>
    var table = document.getElementById("dataTable");
    var tbody = table.childNodes[1];
    var parser = new DOMParser();

    document.getElementById("btnAddRow").onclick = (function(){
    var counter = 2;
    return function(){
    var str = '<tr>'
    +'<td>'+counter+'</td>'
    +'<td>dingwenjiang</td>'
    +'<td>23</td>'
    +'<td>175</td>'
    +'<td>150</td>'
    +'</tr>';
    tbody.innerHTML = tbody.innerHTML + str;
    counter++;
    }
    })();

    </script>

    </body>
    </html>

    这种情况下,直接拼接字符串,把值追加到tbodyinnerHTML中的复杂度我们还是可以忍受的,但是随着拼接的难度越来越大,尤其是碰到大量字符转义的问题的时候,直接拼接的做法开发效率越来越低.
    这个时候我们可以采用模板(这里指的是前端渲染,而不是freemarker、jsp这样的后端模板)的方式来完成. 这样我们用了一个叫做underscore中提供的模板功能.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <html>
    <head>
    <meta charset="UTF-8">
    <title>addTable</title>
    </head>
    <body>
    <table style="margin: 0 auto;width:500px;text-align: center;" id="dataTable" border="1px solid black">
    <tbody>
    <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
    <th>height</th>
    <th>weight</th>
    </tr>
    <tr>
    <td>1</td>
    <td>dingwenjiang</td>
    <td>23</td>
    <td>175</td>
    <td>150</td>
    </tr>
    </tbody>

    </table>
    <button id="btnAddRow" style="position: absolute;right: 300px;">增加一行</button>
    <script>
    var table = document.getElementById("dataTable");
    var tbody = table.childNodes[1];

    document.getElementById("btnAddRow").onclick = (function(){
    var counter = 2;
    return function(){
    var str = '<tr>'
    +'<td>'+counter+'</td>'
    +'<td>dingwenjiang</td>'
    +'<td>23</td>'
    +'<td>175</td>'
    +'<td>150</td>'
    +'</tr>';
    tbody.innerHTML = tbody.innerHTML + str;
    counter++;
    }
    })();

    </script>

    </body>
    </html>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //改进后的click函数
    //对于字符串的拼接部分,使用了模板的方式,只需要往里面传入一个对象,template方法就会生成对应的字符串
    document.getElementById("btnAddRow").onclick = (function(){
    var counter = 2;
    var template = _.template('<tr>\
    <td><%= counter %></td>\
    <td>dingwenjiang</td>\
    <td>23</td>\
    <td>175</td>\
    <td>150</td>\
    </tr>');
    return function(){
    tbody.innerHTML = tbody.innerHTML + template({counter: counter});
    counter++;
    }
    })();

    c. jQuery(write less,do more)
    不管是原生DOM操作还是加上了模板的DOM操作,过程还是有点麻烦,用jq可以快速实现功能.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    document.getElementById("btnAddRow").onclick = (function(){
    var counter = 2;
    var template = _.template('<tr>\
    <td><%= counter %></td>\
    <td>dingwenjiang</td>\
    <td>23</td>\
    <td>175</td>\
    <td>150</td>\
    </tr>');
    return function(){
    $(tbody).append(template({counter: counter}));
    counter++;
    }
    })();

    JQuery本质上就是通过封装API,便捷了JS操作DOM.一个DOM元素转换为jQuery元素的转换规则为直接在DOM元素外面用括号包裹一个$;一个JQuery元素转换成DOM元素的规则为访问jQuery元素的第0个属性

    1
    2
    3
    4
    5
    6
    //比如
    $('table')//是一个jQuery对象
    $('table')[0]//是一个DOM对象

    document.getElementById('dataTable')//是一个DOM对象
    $(document.getElementById('dataTable'))//是一个jQuery对象

    JQuery类数组的表现形式:
    jquery_dom.png
    d. 应用
    前端开发中单页应用现在谈到很多,单页应用使得前后端分离、把服务器的渲染压力放在客户端、用户体验相对较好.下面我们来实现一个简单的单页应用.这个简单的单页应用大体结构如下:
    SPA_schema.png
    下面我们用sea.js + ajax实现一个简单的单页应用.这里sea.js主要为了模块化.单页应用的layout如下所示:
    simple_spa_layout.png
    在开始单页应用之前还需要提一下web项目中的包管理:bower(bower是一个基于nodejs的工具,需要预先安装nodejs环境)
    在web项目中使用bower的好处在于:1.开发者可以快速、准确的找到需要的包,比如bootstrap3需要1.9.1-2版本的jQuery,单独去找依赖容易出问题,用bower更加方便、准确2.别人在使用我们的项目的时候,git clone项目后,执行bower install,bower会根据bower.json的配置文件去自动下载指定版本的依赖项.
    比如bootstrap3的bower.json
    bootstrap3_bower_json.png
    同时我们使用gulp配置了一个livereload
    项目目录如下所示:
    simple-spa-dir.png

  • 其他
    zencoding/emmet
    typewriter.js
    promise优化异步回调
    socket.io