• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • wiki

    標簽: html  javascript

    gulp(注:mac系統中用到了管理員權限,故命令行開頭要加上sudo)


    ps:以下屬于偽代碼


    1.常用步驟:

    (1)因為gulp是基于node開發的,所以要先全局安裝node,若已安裝,則下一步:

    全局安裝gulp
    npm install -g gulp
    
    gulp -h
    查看幫助
    

    (2)在桌面新建文件夾gulp-jinge,并在該文件里打開終端,

    gulp init
    生成package.json文件
    

    接著按要求填寫項目信息(version:0.0.1),再確認

    npm insatll gulp --save-dev
    生成node_modules(含義:將gulp添加到pakage.json中,并作為項目的依賴)
    
    rm -rf node——modules
    刪除node_moduls(因為以后可以通過命令行來直接安裝插件)
    
    npm install
    下載項目所需的所有模塊(因為package.json有gulp依賴,所以安裝node_modules時,自動將gulp安裝在node_modules下面)
    

    (3)在項目根目錄下創建gulpfile.js文件,在gulpfile.js中寫入:

    var gulp=require("gulp");
    
    gulp.task("hello",function () {
        console.log("hello");
    });
    

    再在終端敲入gulp hello(格式:gulp+任務名),即在終端可看到打印了hello

    (4)添加默認任務

    若接著在gulpfile.js敲入 gulp.task(“default”,[‘hello’]);

    則在終端敲gulp,即可執行默認任務hello

    (5)gulp.src找出想要處理的文件,.pipe通過管道來找到對應功能,來處理文件,gulp.dest()將處理好的文件放到指定的地方

    (6)將項目中的文件(index.html)復制到dest中

    在gulpfile.js中敲入:

    var gulp=require("gulp");
    
    gulp.task("copy-index",function () {
        return gulp.src("index.html")
            .pipe(gulp.dest("dist"))
    });
    

    則在終端敲gulp copy-index,即可執行copy-index任務

    (7)依賴任務(格式:gulp.task(任務名,[所依賴的任務列表],執行的任務))

    gulp.task("build",["copy-index","images","data"],function () {
       console.log("編譯成功!");
    });
    

    則在終端敲gulp build,即先執行”copy-index”,”images”,”data”任務,在輸出“編譯成功”。

    (8)監視

    //監視
    gulp.task("watch",function () {
        gulp.watch("index.html",["copy-index"]);//index.html變化時,執行(實現了index.html和dest中的index.html保持同步)
        gulp.watch("images/**/*.{jpg,png}",["images"]);
        gulp.watch(["xml/*.xml","json/*.json","!json/secret-*.json"],["data"]);
    });
    

    2.sass

    (1)

    npm install gulp-sass --save-dev
    安裝gulp-sass文件(安裝好后,在node_modules中可以看到)
    

    在guipfile.js中敲入:

    var sass=require("gulp-sass");
    gulp.task("sass",function () {
        return gulp.src("stylesheets/**/*.sass")
            .pipe(sass())
            .pipe(minifyCSS())
            .pipe(gulp.dest("dist/css"));
    });
    

    則在終端敲gulp sass,即可將sass自動轉化為css,并輸出到dist/style.css中。

    3.將項目放到本地服務器運行

    (1)

    npm install gulp-connect --save-dev
    將connect放到項目開發的依賴中
    

    (2)在gulpfile.js中

    var connect=require("gulp-connect");
    gulp.task("server",function(){
        //服務器配置
        connect.server({
            //服務器根目錄
            root:"dist",
        })
    });
    

    再在終端敲gulp server,即可將dist中文件在瀏覽器打開(在瀏覽器搜localhosst:8080)。

    (3)實時刷新

    gulpfile.js中,在server任務中添加

    //啟動實時刷新
    livereload:true
    

    在copy-index任務中,添加

    .pipe(connect.reload());
    

    在尾部添加默認任務

    gulp.task("default",["server","watch"]);
    

    此時在終端先ctrl+c結束監視功能,再敲入gulp,便可實現開啟服務器、監視、實時刷新功能。

    4.文件合并

    (1)

    npm install gulp-concat --save dev\
    安裝gulp-concat,并保存到依賴中
    

    (2)在gulpfile.js中,敲入

    var concat=require("gulp-concat");
    gulp.task("scripts",function () {
        return gulp.src(["javascripts/jquery.min.js","javascripts/modernizr.js"])
            //合并
            .pipe(concat("vendor.js"))
            .pipe(gulp.dest("dist/js"))
    });
    

    終端輸入gulp scripts

    5。文件壓縮

    uglify:壓縮js

    (1)

    npm install gulp-uglify --save-dev
    安裝gulp-uglify,并保存到依賴中
    

    (2)在gulpfile.js中,在scripts任務里添加

    .pipe(uglify())
    

    在頭部添加

    var uglify=require("gulp-uglify");
    

    (3)終端輸入gulp scripts

    minify:壓縮css

    (1)

    npm install gulp-minify-css --save-dev
    安裝gulp-minifyCSS,并保存到依賴中
    

    (2)在gulpfile.js中,在less任務里添加

    .pipe(minifyCSS())
    

    在頭部添加

    var minifyCSS=require("gulp-minify-css");
    

    (3)終端輸入gulp less,即可在dist得到編譯后且最小化(如:去注釋)的css.

    imagemin:圖片壓縮

    (1)

    npm install gulp-imagemin --save-dev
    安裝gulp-minifyCSS,并保存到依賴中
    

    (2)在gulpfile.js中,在images任務里添加

     .pipe(imagemin())   
    

    在頭部添加

    var imagemin=require("gulp-imagemin");
    

    (3)終端輸入gulp images,即可實現圖片壓縮.

    所遇到的問題:

    (1)安裝pakage.json,卻出現package-lock.json

    解決:package-lock.json是保證你的package.json文件中所有的安裝包版本和安裝包版本所依賴的二級安裝包的版本不會發生變化.我暫時刪除了它

    (2)ls無法查看文件列表

    解決:可能原因權限有問題windows自帶防火墻攔截。暫時手動查看

    (3)cnpm install gulp-sass時出現build error

    解決:刪除node_modules文件,刪除package.json中gulp-sass和node-sass的信息,再重新執行npm install gulp-sass –save-dev


    BFC+兩欄布局(左固定,右自適應)

    什么是BFC

    BFC(Block formatting context)直譯為”BFC”。它是一個獨立的渲染區域,只有Block-level box參與, 它規定了內部的Block-level Box如何布局,并且與這個區域外部毫不相干。
    通俗一點的理解就是——不論內部元素怎么鬧騰,都不會影響到外部元素。

    觸發BFC的條件:

    標題

    • float的值不為none。
    • overflow的值為auto,scroll或hidden。
    • display的值為table-cell,table-caption,inline-block中任意一個。
    • position的值不為relative和static。

    BFC的規則:

    • 內部的Box會在垂直方向,一個接一個地放置。
    • Box垂直方向的距離由margin決定。屬于同一個BFC的兩個相鄰Box的margin會發生重疊。
    • 每個元素的margin box的左邊, 與包含塊border box的左邊相接觸(對于從左往右的格式化,否則相反)。即使存在浮動也是如此。
    • BFC的區域不會與float box重疊。
    • BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素。反之也如此。
    • 計算BFC的高度時,浮動元素也參與計算。

    兩欄布局—左邊固定寬度,右邊自適應

    方法一:

    假如左邊部分寬度為x,設置樣式時給左邊部分float:left,給右邊部分設置一個值為x的margin-left。

    html處的代碼為:

    <div id="left"></div>
    <div id="right"></div>
    

    css樣式為:

    html,body{
            height: 100%;
            padding: 0;
            margin: 0;
        }
        #left{
            width: 300px;
            height: 100%;
            background-color: red;
            float: left;
        }
        #right{
            height: 100%;
            background-color: blue;
            margin-left:300px;
    
        }
    

    注意清除瀏覽器默認樣式。

    方法二:

    利用浮動,給左邊float:left,右邊不設置寬度,只設置overflow:auto。
    html處的代碼不變,css樣式為:

    html,body{
            height: 100%;
            padding: 0;
            margin: 0;
        }
        #left{
            width: 300px;
            height: 100%;
            background-color: red;
            float: left;
        }
        #right{
            height: 100%;
            background-color: blue;
            overflow: hidden;
    
        }
    

    我發現此處給right的overflow設置auto也可以實現兩欄布局的效果。兩者同樣都是利用了BFC特性,給右邊部分設置一個觸發BFC的條件,使右邊部分不會與float的左邊部分發生重疊,從而達到我們想要的效果。

    方法三

    設左邊部分的寬度為x,利用絕對定位。將左邊部分使用絕對定位固定在左邊,然后給右邊部分設置一個值為x的margin-left。

    需要注意的是這種方法當左邊部分的高度大于右邊部分的高度時,無法將父元素撐開,如果有底部元素,就會出現底部元素與左邊元素重疊的現象。

    html部分的代碼為:

    <div id="container">
        <div id="left">
            左欄
        </div>
        <div id="right">
            右欄
        </div>
    </div>
    

    css部分的代碼為:

    body{
        margin: 0;
    }
    #left{
        top: 0;
        left: 0;
        width: 230px;
        height: 600px;
        background: #ccc;
        position: absolute;
    }
    #right{
        height: 600px;
        margin-left: 230px;
        background: #0099ff;
    }
    

    方法四

    我打算把右邊部分的寬度設為100%,然后設置float:right,最后把他向右移動310,以便于左邊部分能擠上來。
    但這么一來右邊部分里面的內容也會跟著左移310,導致被遮住了,所以我們要把他重新擠出來。為了好擠,我用了一個額外的div包裹住內容,通過margin-right將內容擠到屏幕相應位置。

    html部分的代碼為:

    <div id="wrap">
        <div id="content">主體內容(左)</div>
        <div id="sidebar_wrap">
            <div id="sidebar">邊欄(右)</div>
        </div>
    </div>
    <div id="footer">底部</div>
    

    css部分的代碼為:

    #content {
                width: 310px;
                float: left;
                background-color: red;
            }
            #sidebar_wrap{
                margin-right: -310px;
                float:right;
                width: 100%;
            }
            #sidebar{
                margin-right: 310px;
                background-color: blue;
            }
            #footer{
                background-color: indianred;
            }
    

    方法五

    通過table和table-cell實現。

    html部分的代碼為:

    <div id="wrap">
        <div id="content" style="height:40px;">左固定區con</div>
        <div id="sidebar" style="height:100px;">右自適應區bar</div>
    </div>
    <div id="footer">后面的一個DIV,以確保前面的定位不會導致后面的變形</div>
    

    css部分的代碼為:

    #wrap{
                display: table;
                width: 100%;
            }
            #content,#sidebar{
                display:table-cell;
            }
            #content{
                width: 500px;
                background-color: red;
            }
            #sidebar{
                background-color: blue;
            }
            #footer{
                background-color: blueviolet;
            }
    

    注意:1.ie7-不適應 2.不論如何設置左右部分各自高度,最終效果:左邊高度一定等于右邊高度


    圣杯+雙飛翼(三欄布局)

    1.margin設為負值的作用

    首先把文檔流想象成水流可能更方便我們理解一些。
    負邊距對文檔流控制的元素的作用是,會使它們在文檔流中的位置發生偏移。這種偏移和因為相對定位而導致的偏移是不一樣的,相對定位后,元素依舊占據它原來的空間,文檔流后面的內容不會流上來,但因為負邊距而導致的偏移會放棄之前所占據的空間,文檔流后面的元素就會像水流一樣隨之流上來填充。
    在文檔流中,元素的最終邊界是由margin決定的。
    需要注意的是,文檔流只能向左或向上流,即后面流向前面,不能逆向流。

    個別情況下margin為負值可以增加元素寬度
    這里說的個別情況是指該元素沒有設定固定寬度的前提。我們清楚,當一個元素沒有設定寬度的時候,父元素設定了寬度,它就會默認占滿父元素,這個時候如果給該元素設定一個負的左右margin,設了多少,就會增加多少。

    2.共同目的

    三列布局,要求中間寬度自適應,左右寬度為定值的布局,且都是讓瀏覽器將中間區塊優先渲染。(定-自-定)
    

    3.圣杯布局
    實例

    #contain{
        padding-right: 120px;
        padding-left: 120px;
    }
    .main,.left,.right{
        float: left;
    }
    .left,.right{
        width: 120px;
    }
    .main{
        width:100%;
    }
    .left{
        position: relative;
        left: -120px;
        margin-left: -100%;
    }
    .right{
        position: relative;
        left: 120px;
        margin-left: -120px;
    }
    <div id="contain">
        <div class="main">我是中間</div>
        <div class="left">我是左邊</div>
        <div class="right">我是右邊</div>
    </div>

    4.雙飛翼布局

    實例:

    .box{
        float: left;
    }
    .main-wrap{
        width: 100%;
    }
    .main{
        margin:0 300px 0 200px;
        height:400px;
        background: blue;
    }
    .left{
        width: 200px;
        height: 400px;
        background: pink;
        margin-left: -100%;
    }
    .right{
        width: 300px;
        height: 400px;
        background: grey;
        margin-left: -300px;
    }
    .box{
        text-align: center;
        line-height: 400px;
        color: #fff;
    }
    <div class="container">
        <div class="main-wrap box">
            <div class="main">我是中間</div>
        </div>
        <div class="left box">左邊</div>
        <div class="right box">右邊</div>
    </div>

    5.比較

    :兩者解決問題的方案在前一半是相同的,也就是三欄全部float浮動,但是左右兩欄加上負margin讓它跟中間div并排,以形成三欄布局。

    異:

    圣杯布局: 將左右兩個div采用相對定位配合left、right屬性,以便左右兩欄div移出后不遮擋中間div。
    再將父元素設置了padding-left和padding-right,使左右兩列分別流入到窗口中。
    (注意:左列寬度<中間列寬度,如不滿足,則在父元素padding-left時,三列無法處于同一行。)

    雙飛翼布局: 為了中間div內容不被遮擋,直接在中間列div內部創建一個子div,用于放置內容,在子div里用margin-left,margin-right為左右兩欄div留出位置。


    三欄布局@定-自-定

    方法一:絕對定位法

    原理:將左右兩邊使用absolute定位,因為絕對定位使其脫離文檔流,后面的center會自然流動到他們下面,然后使用margin屬性,留出左右元素的寬度,就可以使中間元素自適應屏幕寬度。

    點評:該法布局的好處,三個div順序可以任意改變。不足是因為絕對定位,所以如果頁面上還有其他內容,top的值需要小心處理

    css:

    #left{
            position: absolute;
            left: 0;
            top: 0;
            width: 200px;
        }
        #right{
            position: absolute;
            right: 0;
            top: 0;
            width: 200px;
        }
        #center{
            padding-left: 200px;
            padding-right: 200px;
        }
    

    html:

    <div id="left">左邊內容</div>
    <div id="right">右邊內容</div>
    <div id="center">中間內容</div>
    

    方法二:自身浮動法

    原理:對左右使用分別使用float:left和float:right,float使左右兩個元素脫離文檔流,中間元素正常在正常文檔流中,使用margin指定左右外邊距對其進行一個定位。

    點評:好處是受外界影響小,但是不足是 三個元素的順序,center一定要放在最后(原因:center占據文檔流位置,所以一定要放在最后,左右兩個元素位置沒有關系。否則左右元素將會被擠到下一行)。當瀏覽器窗口很小的時候,右邊元素會被擠到下一行。

    css:

    #left,#right{ width: 200px;height: 200px; background-color: #ccc; }
    #left{float: left;}
    #right{float: right;}
    #center{margin: 0 200px; height: 200px; background-color:pink;}
    

    html:

    <div id="left">左邊內容</div>
    <div id="right">右邊內容</div>
    <div id="center">中間內容</div>
    

    方法三:flex

    原理:在外圍包裹一層div,設置為display:flex;中間設置flex:1;但是盒模型默認緊緊挨著,可以使用margin控制外邊距。

     #wrap{
            width: 100%;
            display: flex;
        }
        #left,#right{
            width: 200px;
        }
        #center{
            flex:1;
        }
    

    負margin的詳細整理

    給沒有設置float的元素使用負margin時

    • 給元素設置margin-left和margin-top上的負值x時,設置的哪個方向,就會使元素向那個方向偏移x的距離。
    • 給元素設置margin-right/bottom上的負值x時,會將后續元素向自己這邊拉動x的距離,即讓文檔流后面的內容向自己流x距離。并且在沒有設定元素的寬度時,會增加元素寬度。利用負margin增加寬度就是這個原理。

    負margin的作用

    • 實現圣杯雙飛翼布局
    • 增加未設置寬度的元素的自身寬度
    • 去掉浮動列表的右邊框
    • 和絕對定位一起實現水平垂直居中
    • 去除列表最后一個li元素的border-bottom

    因為圣杯雙飛翼布局和增加元素自身寬度昨天已經整理過了。所以今天整理剩余的部分。

    去掉浮動列表的右邊框

    這里有時候會采用給最右邊的元素添加class,然后margin-right設置為0的方法,但是缺點是需要動態判斷哪些元素是最右邊的元素,列表里的內容是采用動態循環的方式顯示,利用class使用margin-right:0的方式就不行了。這個時候如果給父元素添加一個負的margin-right就會好辦很多啦,不需要動態判斷到底哪些元素是最右邊的。
    代碼如下:
    html部分的代碼為:

    <div id="wrap">
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
        </ul>
    </div>
    

    css樣式為:

    *{
        margin: 0;
        padding: 0;
    }
    #wrap{
        background-color: black;
        width: 320px;
        height: 210px;
        overflow: hidden;
    
    }
    ul{
        zoom:1;
        margin-right: -10px; 
    }
    li{
        float: left;
        width: 100px;
        height: 100px;
        margin-right: 10px;
        margin-bottom: 10px;
        list-style: none;
        background-color: red;
    }
    

    和定位一起實現水平垂直居中

    這個比較簡單好理解了。id為box的div樣式如下:

    #box{
        width: 100px;
        height: 100px;
        background-color: brown;
        position: absolute;
        top: 50%;
        left: 50%;
        margin-top: -50px;
        margin-left: -50px;
    }
    

    去除列表最后一個li元素的border-bottom

    適用情況:當每個li都有boder-bottom,ul也有border的時候,最后一個li的border-bottom就會和ul的底邊邊框發生重疊,導致看上去不美觀,這個時候給li設置一個等于border寬度的margin-bottom,就可以消除這種不美觀的影響了。
    當然這里也可以利用css3的選擇器選擇最后一個li,將它的border-bottom設置為0px來解決這種問題。兩種方法都很簡便。

    html代碼如下:

    <ul id="box">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
    </ul>
    

    css 部分代碼如下:

    #box{
        padding: 0;
        border: 5px solid grey;
        background-color: black;
        overflow:hidden;
    }   
    #box li{
        color: white;
        border-bottom: 5px solid grey;
        list-style: none;
        margin-bottom: -5px;
    }
    

    由于第一個li的margin-bottom導致第二個li被上拉5px,第二個li最上面的5px高度覆蓋在第一個li之上,但由于li的background-color:transparent,第一個元素的boder-bottom能夠正常顯示出來

    由于最后一個li -5px的margin-bottom,所以它將覆蓋在ul的border-bottom之上

    適當調整border,便可以看出來,最后一個li的灰色border覆蓋在ul的灰色border之上,為達到去除最后一個li的邊框效果,必須保證二者顏色相近,或者給ul寫個overflow:hidden


    清除浮動


    1. 清除浮動與閉合浮動

    • 清除浮動:清除對應的單詞是 clear,對應CSS中的屬性是 clear:left | right | both | none;
      例子:

    .main{
        float: left;
        width: 200px;
        height: 100px;
    }
    .side{
        float: right;
        width: 300px;
        height: 120px;
    }
    .footer{
        clear: both;
        height: 30px;
    }
    <div class="main"></div>
    <div class="side"></div>
    <div class="footer"></div>
    • 閉合浮動:更確切的含義是使浮動元素閉合,從而減少浮動帶來的影響。

      點評:其實我們想要達到的效果更確切地說是閉合浮動,而不是單純的清除浮動,在footer上設置clear:both清除浮動并不能解決wrap高度塌陷的問題。

      結論:用閉合浮動比清除浮動更加嚴謹,所以后文中統一稱之為:閉合浮動。

    2. 閉合浮動的原因

    由于浮動元素脫離文檔流的特性,導致本屬于普通流中的元素浮動之后,包含框內部由于不存在其他普通流元素了,也就表現出高度為0(高度塌陷)。在實際布局中,往往這并不是我們所希望的,所以需要閉合浮動元素,使其包含框表現出正常的高度。
    

    普通流

    文檔流(document flow),普通文檔流,標準中叫做普通流或者常規流(normal flow)。

    浮動

    浮動的框可以左右移動,直至它的外邊緣遇到包含框或者另一個浮動框的邊緣。

    浮動框不屬于文檔中的普通流。

    當一個元素浮動之后,不會影響到塊級框的布局而只會影響內聯框(通常是文本)的排列,文檔中的普通流就會表現得和浮動框不存在一樣,當浮動框高度超出包含框的時候,也就會出現包含框不會自動伸高來閉合浮動元素(“高度塌陷”現象)。

    顧名思義,就是漂浮于普通流之上,像浮云一樣,但是只能左右浮動

    3.閉合浮動的原理——了解 hasLayout 和 Block formatting contexts

    1)添加額外標簽
    –通過在浮動元素末尾添加一個空的標簽,例如

    ,其他標簽br等亦可。
    實例:

    <div class="wrap">
        <div class="main"></div>
        <div class="side"></div>
        <div style="clear:both;"></div>
    </div>
    .main{ float:left;}
    .side{float:right;}

    點評:
    優點:通俗易懂,容易掌握
    缺點:可以想象通過此方法,會添加多少無意義的空標簽,有違結構與表現的分離,在后期維護中將是噩夢,這是堅決不能忍受的,所以你看了這篇文章之后還是建議不要用了吧。

    2)使用 br標簽和其自身的 html屬性
    –這個方法有些小眾,br 有 clear=“all | left | right | none” 屬性
    實例:

    <div class="wrap" id="float2">
        <div class="main left">.main{float:left;}</div>
        <div class="side left">.side{float:right;}</div>
        <br clear="all" />
    </div>
    <div class="footer">.footer</div>

    點評:
    優點:比空標簽方式語義稍強,代碼量較少
    缺點:同樣有違結構與表現的分離,不推薦使用

    3)父元素設置 overflow:hidden

    通過設置父元素overflow值設置為hidden;在IE6中還需要觸發 hasLayout ,例如 zoom:1;

    <div class="wrap" id="float3" style="overflow:hidden; *zoom:1;">
        <div class="main left">.main{float:left;}</div>
        <div class="side left">.side{float:right;}</div>
    </div>
    <div class="footer">.footer</div>

    點評:

    優點:不存在結構和語義化問題,代碼量極少
    缺點:

    • 內容增多時候容易造成不會自動換行導致內容被隱藏掉,無法顯示需要溢出的元素;
    • 04年POPO就發現overflow:hidden會導致中鍵失效,這是我作為一個多標簽瀏覽控所不能接受的。所以還是不要使用了

    4)父元素設置 overflow:auto 屬性;

    同樣IE6需要觸發hasLayout,演示和3差不多

    優點:
    不存在結構和語義化問題,代碼量極少
    缺點:
    多個嵌套后,firefox某些情況會造成內容全選;
    IE中 mouseover造成寬度改變時會出現最外層模塊有滾動條等,firefox早期版本會無故產生focus等,不要使用

    5)父元素也設置浮動
    優點:
    不存在結構和語義化問題,代碼量極少
    缺點:
    使得與父元素相鄰的元素的布局會受到影響,不可能一直浮動到body,不推薦使用

    6)父元素設置display:table
    優點:結構語義化完全正確,代碼量極少
    缺點:盒模型屬性已經改變,由此造成的一系列問題,得不償失,不推薦使用

    7)使用:after 偽元素
    需要注意的是 :after是偽元素(Pseudo-Element),不是偽類(某些CSS手冊里面稱之為“偽對象”),很多閉合浮動大全之類的文章都稱之為偽類,不過csser要嚴謹一點,這是一種態度。

    由于IE6-7不支持:after,所以使用 zoom:1觸發 hasLayout來兼容。

    實例:

    <div class="wrap">
        <div class="left">我在左浮動</div>
        <div class="right">我在右浮動</div>  
    </div>
    .wrap{
        border: 1px solid #000;
    }
    .left{
        float: left;
        margin-left: -20px;
        background: red;
    }
    .right{
        float: right;;
        background: blue;
    }
    .wrap:after{
        content:".";//生成內容作為最后一個元素
        /*至于content里面是點還是其他都是可以的,例如oocss里面就有經典的 content:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",有些版本可能content 里面內容為空,并不推薦這樣做,firefox直到7.0 content:”"時,仍然會產生額外的空隙;*/
        display: block;//使生成的元素以塊級元素顯示,占滿剩余空間
        height: 0;//避免生成內容破壞原有布局的高度
        visibility: hidden;//使生成的內容不可見,目的是:讓可能被生成內容蓋住的部分,能夠進行點擊和交互。
        clear: both;
    }
    .clearfix{
        *zoom: 1;//觸發IE hasLayout。
    }

    優點:結構和語義化完全正確,代碼量居中
    缺點:復用方式不當會造成代碼量增加

    小結

    通過對比,我們不難發現,其實以上列舉的方法,無非有兩類:

    其一,通過在浮動元素的末尾添加一個空元素,設置 clear:both屬性,after偽元素其實也是通過 content 在元素的后面生成了內容為一個點的塊級元素;

    其二,通過設置父元素 overflow 或者display:table 屬性來閉合浮動
    原理:

    • 觸發BFC的條件

      float 除了none以外的值

      overflow 除了visible 以外的值(hidden,auto,scroll )

      display (table-cell,table-caption,inline-block)

      position(absolute,fixed)

      fieldset元素

    • 注意

      display:table 本身并不會創建BFC,但是它會產生匿名框(anonymous boxes),而匿名框中的display:table-cell可以創建新的BFC。

      觸發塊級格式化上下文的是匿名框,而不是display:table。

      通過display:table和display:table-cell創建的BFC效果是不一樣的。

    回顧:

    1.overflow:hidden/auto

    清浮動原理:因為父元素創建了新的BFC.

    2.關于ie

    • IE6-7的顯示引擎使用的是一個稱為布局(layout)的內部概念,由于這個顯示引擎自身存在很多的缺陷,直接導致了IE6-7的很多顯示bug

    • 當我們說一個元素“得到 layout”,或者說一個元素“擁有 layout” 的時候,我們的意思是指它的微軟專有屬性 hasLayout.
      為此被設為了 true 。

    • IE6-7使用布局的概念來控制元素的尺寸和定位,那些擁有布局(haslayout)的元素負責本身及其子元素的尺寸設置和定位。

      如果一個元素的 hasLayout為false,那么它的尺寸和位置由最近擁有布局的祖先元素控制。

    3.觸發hasLayout的條件:

    position: absolute 
    float: left|right 
    display: inline-block 
    width: 除 “auto” 外的任意值 
    height: 除 “auto” 外的任意值 (例如很多人閉合浮動會用到 height: 1%  )
    zoom: 除 “normal” 外的任意值 (MSDN) 

    在 IE7 中,overflow 也變成了一個 layout 觸發器:
    overflow: hidden|scroll|auto ( 這個屬性在ie7-沒有觸發 haslayout 的功能。 )

    4.overflow-x|-y: hidden|scroll|auto 在ie7-中同樣沒有觸發 haslayout 的功能)

    綜上:
    在支持BFC的瀏覽器(IE8+,firefox,chrome,safari)通過創建新的BFC閉合浮動;
    在不支持 BFC的瀏覽器 (IE6-7),通過觸發 hasLayout 閉合浮動。

    推薦閉合浮動方法

    方法一
    Unicode字符里有一個“零寬度空格”,也就是U+200B,這個字符本身是不可見的,所以我們完全可以省略掉

    visibility:hidden了.clearfix:after {content:"200B"; display:block; height:0; clear:both; }
    .clearfix { *zoom:1; }.
    

    方法二
    該方法也不存在firefox中空隙的問題。

    /* For modern browsers */
    .cf:before,.cf:after {
    content:"";
    display:table;
    }
    .cf:after { clear:both; }/* For IE 6/7 (trigger hasLayout) */
    .cf { zoom:1; }

    塊元素居中總結:

    水平:

    1.相對居中

    2.絕對

    3.給需要居中的元素添加:

    display: inline-block;
    vertical-align: middle;
    

    垂直:

    1.絕對

    水平+垂直:

    1.絕對+margin負值

    2.絕對+auto

    3.絕對+transform反向偏移

    #box{
        width: 100px;
        height: 100px;
        background-color: red;
        position: absolute;
        top: 50%;
        left: 50%;
        transform:translate(-50%,-50%);
        -webkit-transform: translate(-50%, -50%);
        -ms-transform: translate(-50%, -50%);
    }
    

    4.flex

    justify-content: center;
    align-items: center
    

    css3特性


    1. 實現圓角border-radius

    border-radius: 1-4個數字

    參數

    各種長度單位都可以:px,%,…
    %有時很方便
    但寬高不一致時不太好
    

    1個:都一樣
    border-radius: 一樣

    2個:對角
    border-radius: 左上&右下 右上&左下

    3個:斜對角
    border-radius: 左上 右上&左下 右下

    4個:全部,順時針
    border-radius: 左上 右上 右下 左
    border-radius: 1-4個數字 / 1-4個數字

    說明:
    前面代表水平,后面代表垂直。

    不給“/”則水平和垂直一樣,例如:border-radius: 10px/5px;
    “/”前面以及后面的四個數字,分別按照TRBL排布,來對矩形的四個角單獨設置其水平、垂直圓角半徑。

    兼容性

    border-radius只有在以下版本的瀏覽器:Firefox4.0+、Safari5.0+、Google Chrome10.0+、Opera10.5+、IE9+支持border-radius標準語法格式,對于老版的瀏覽器,border-radius需要根據不同的瀏覽器內核添加不同的前綴,比說Mozilla內核需要加上“-moz”,而Webkit內核需要加上“-webkit”等。

    例如:

    moz-border-radius:Mozilla //(Firefox, Flock等瀏覽器)
    -webkit-border-radius://WebKit (Safari, Chrome等瀏覽器)
    border-radius://Opera瀏覽器,不支持ie<9

    冷門使用

    1.對于border-radius還有一個內半徑和外半徑的區別,它主要是元素 邊框值較大時,效果就很明顯,當我們border-radius半徑值小于或等于border的厚度時,我們邊框內部就不具有圓角效果。

    .border-big {
        border: 15px solid green;
        border-radius: 15px;
    }

    原因:border-radius的內徑值是等于外徑值減去邊框厚度值,當他們的值為負時,內徑默認為0,最前面講border-radius取值時就說過其值不能為負值。同時也說明border-radius的內外曲線的圓心并不一定是一致的。只有當邊框厚度為0時,我們內外曲線的圓心才會在同一位置。

    2.如果角的兩個相鄰邊有不同的寬度,那么這個角將會從寬的邊平滑過度到窄的邊。其中一條邊甚至可以是0。相鄰轉角是由大向小轉。

    .demo {
      border-width: 10px 5px 20px 3px;
      border-radius: 30px;
    }

    3.相鄰兩條邊顏色和線條樣式不同時,那么兩條相鄰邊顏色和樣式轉變的中心點是在一個和兩邊寬度成正比的角上。比如,兩條邊寬度相同,這個點就是一個45°的角上,如果一條邊是另外一條邊的兩倍,那么這個點就在一個30°的角上。界定這個轉變的線就是連接在內外曲線上的兩個點的直線。

    下面是一個四邊顏色不一樣,寬度不一樣的實例:

    .demo {
      border-color: red green blue orange;
      border-width: 15px 30px 30px 80px;
      border-radius: 50px;
    }

    4.table的樣式屬性border-collapse是collapse時,border-radius不能正常顯示,只有border-collapse: separate;時才能正常顯示。

    table {
      border-collapse: collapse;
      border: 2px solid red;
      background: green;
      border-radius: 15px;
    }

    盒陰影(box-shadow)

    box-shadow:[inset] x y blur [spread] color

    參數

    [inset]:投影方式

    inset:內投影

    不寫:外投影

    x、y:陰影偏移(右下正)

    blur:模糊半徑

    [spread]:擴展陰影半徑

    先擴展原有形狀,再開始畫陰影

    Color:顏色


    文字陰影(text-shadow)

    text-shadow:x y blur color, …

    參數

    x 橫向偏移(右下正,左上負)

    y 縱向偏移

    blur 模糊距離(顏色漸變邊界線模糊)

    color 陰影顏色

    文本陰影如果加很多層,會很卡很卡很卡

    文字陰影應用(1)

    • 最簡單用法

    text-shadow:2px 2px 4px black

    • 陰影疊加

    text-shadow:2px 2px 0px red, 2px 2px 4px green;
    先渲染后面的,再渲染前面的(前面覆蓋后面)

    • 幾個好玩的例子

    層疊:color:red; font-size:100px; font-weight:bold; text-shadow:2px 2px 0px white, 4px 4px 0px red;
    光暈:color:white; font-size:100px; text-shadow:0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;

    • 火焰文字:

    text-shadow: 0 0 20px #fefcc9, 10px -10px 30px #feec85, -20px -20px 40px #ffae34, 20px -40px 50px #ec760c, -20px -60px 60px #cd4606, 0 -80px 70px #973716, 10px -90px 80px #451b0e; font-family:Verdana, Geneva, sans-serif; font-size:100px; font-weight:bold; color:white;


    線性漸變(gradient)

    兼容性

    IE10+
    chrome 26+,10.0 -webkit-
    firefox 16+,3.6 -moz
    safari 6.1+,5.1 -webkit
    opera 12.1+,11.6 -o-

    常見寫法

    • 寫法1

    background: linear-gradient(color-stop1, color-stop2, …);
    默認to bottom
    示例代碼:

    div {
        width: 800px; height: 500px;
        background: -webkit-linear-gradient(red, blue);
        background:    -moz-linear-gradient(red, blue);
        background:      -o-linear-gradient(red, blue);
        background:         linear-gradient(red, blue);
    }
    • 寫法2
    background: -webkit-linear-gradient( begin-direction, color-stop1,color-stop2, ...);
    background: -moz-linear-gradient(end-direction, color-stop1, color-stop2, ...);
    background: -o-linear-gradient(end-direction, color-stop1, color-stop2, ...);
    background: linear-gradient(to end-direction, color-stop1, color-stop2,...);

    ★【備注】:

    瀏覽器為webkit內核,寫開始點方向
    瀏覽器為moz和o內核,寫結束點方向
    標準瀏覽器是to結束位置//標準瀏覽器:尊重w3c的web瀏覽器
    

    示例代碼:

    div {
        width: 800px; height: 500px;
        background: -webkit-linear-gradient(left, red , blue);
        background:    -moz-linear-gradient(right, red, blue);
        background:      -o-linear-gradient(right, red, blue);
        background:         linear-gradient(to right, red , blue);
    }
    • 寫法3
    background:-webkit-linear-gradient(begin-level begin-vertical,color-stop1,color-stop2,...);
    background:-moz-linear-gradient(end-level end-vertical,color-stop1,color-stop2,...);
    background:-o-linear-gradient(end-level end-vertical,color-stop1,color-stop2,...);
    background:linear-gradient(to end-level end-vertical,color-stop1,color-stop2,...);

    示例代碼

    div {
        width: 800px; height: 500px;
        background: -webkit-linear-gradient(left top, red, yellow, blue);
        background:    -moz-linear-gradient(right bottom, red, yellow, blue);
        background:      -o-linear-gradient(right bottom, red, yellow, blue);
        background:         linear-gradient(to right bottom, red, yellow, blue);
    }
    • 寫法4(線性漸變,使用角度)

    1

    漸變方向與角度關系見上圖(注意區分漸變方向、漸變界線)–漸變界限指產生漸變效果的某兩種顏色的交線

    示例代碼:

    div {
        width: 800px; height: 500px;
        background: -webkit-linear-gradient(135deg, red, yellow, blue);
        background:    -moz-linear-gradient(135deg, red, yellow, blue);
        background:      -o-linear-gradient(135deg, red, yellow, blue);
        background:         linear-gradient(135deg, red, yellow, blue);
    }
    • 寫法5

    background:linear-gradient(角度? color1 length|percentage,color2 length|percentage,…);

    【備注】:顏色結點中
    如果最后一個不寫值,默認100%,
    如果第一個不寫值,默認0%

    • 寫法6(透明漸變)
    div {
        width: 800px; height: 500px;
        background: -webkit-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1));
        background:    -moz-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1));
        background:      -o-linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1));
        background:         linear-gradient(90deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1));
    }
    • 重復漸變
     background:repeating-linear-gradient(角度?,color1 length|percentage,color2 length|percentage,...);

    示例代碼:

    background: -webkit-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);
    background:    -moz-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);
    background:      -o-repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);
    background:         repeating-linear-gradient(90deg, red 0%, blue 10%, red 20%);

    徑向漸變


    從起點到終點,顏色從內向外進行圓形漸變(從中間向外拉)
    語法

     background:radial-gradient(center,shape size,start-color,...,last-color);
    
    • 寫法1

    徑向漸變-顏色結點均勻分布(默認)

    background:radial-gradient(color-stop1,color-stop2,...);
    
    實例:
    
    background: -webkit-radial-gradient(red, blue);
    background:    -moz-radial-gradient(red, blue);
    background:      -o-radial-gradient(red, blue);
    background:         radial-gradient(red, blue);
    • 寫法2

      顏色結點不均勻分布

    background:radial-gradient(color1 length|percentage,color2 length|percentage,...);
    background: -webkit-radial-gradient(red 50%, blue 70%);
    background:    -moz-radial-gradient(red 50%, blue 70%);
    background:      -o-radial-gradient(red 50%, blue 70%);
    background:         radial-gradient(red 50%, blue 70%);
    • 寫法3

    徑向漸變——設置形狀

    【語法】

    background:radial-gradient(shape,color-stop1,color-stop2,...);

    形狀說明

    circle——圓形
    ellipse——橢圓(默認)
    background: -webkit-radial-gradient(circle, red, blue);
    background:    -moz-radial-gradient(circle, red, blue);
    background:      -o-radial-gradient(circle, red, blue);
    background:         radial-gradient(circle, red, blue);
    • 寫法4

      徑向漸變——尺寸大小關鍵字

      【語法】

      background:radial-gradient(size,color-stop,color-stop2,…);

      【關鍵字說明】

    closest-side:最近邊//指定:徑向漸變的半徑長度為從圓心到離圓心最近的邊
    
    farthest-side:最遠邊//指定:徑向漸變的半徑長度為從圓心到離圓心最遠的邊。
    
    closest-corner:最近角
    
    farthest-corner:最遠角

    transform

    1.Transform:(css3 2D 轉換)

    注意:這些效果疊加時,中間用空格隔開

    作用:能夠對元素進行移動、縮放、轉動、拉長、拉伸

    轉換:使元素改變形狀、尺寸、位置的一種效果

    2.Transform:

    2D的轉換方法:

    2

    • rotate

    設置元素順時針旋轉的角度,用法是:transform: rotate(x);
    參數x必須是以deg結尾的角度數或0,可為負數表示反向。
    說明:
    圍繞x或y軸旋轉,屬于3d變換;圍繞z軸,屬于2d變換.

    圍繞x、y軸,正值:向屏幕里

    圍繞z軸,正順負逆。

    • scale

    設置元素放大或縮小的倍數,用法包括:

    transform: scale(a);                  元素x和y方向均縮放a倍
    transform: scale(a, b);              元素x方向縮放a倍,y方向縮放b倍
    transform: scaleX(a);                元素x方向縮放a倍,y方向不變
    transform: scaleY(b);                元素y方向縮放b倍,x方向不變
    • translate()
      通過translate()方法,元素從其當前位置移動,根據給定的left(x坐標)和top(y坐標)位置.(參數為負數時,反方向移動物體,其基點默認為元素中心點,也可以根據transform-origin進行改變基點)
    translate 設置元素的位移,用法為:
    transform: translate(a, b);                元素x方向位移a,y方向位移b
    transform: translateX(a);                  元素x方向位移a,y方向不變
    transform: translateY(b);                  元素y方向位移b,x方向不變
    • skew()
      設置元素傾斜的角度,用法包括:

    transform: skew(a, b);元素x方向逆時針傾斜角度a,y方向順時針傾斜角度b
    transform: skewX(a); 元素x方向逆時針傾斜角度a,y方向不變
    transform: skewY(b); 元素y方向順時針傾斜角度b,x方向不變
    以上的參數均必須是以deg結尾的角度數或0,可為負數表示反向。
    • matrix
      設置元素的變形矩陣,因為矩陣變形過于復雜,暫略。

    HTML5新特性

    • Javacript 客戶端保存數據

    WebStorage

    1.Cookie

    cookie 是存儲于訪問者的計算機中的變量。每當同一臺計算機通過瀏覽器請求某個頁面時,就會發送這個 cookie。你可以使用 JavaScript 來創建和取回 cookie 的值。當瀏覽器從web的回應到頁面請求中接收到一個 Set-Cookie 頭部時Cookies便創建了:瀏覽器接收到表明回應成功的 HTTP 200 代碼,以及回應的內容類型。同時也接收到了Set-Cookie頭部,并創建了一個的cookie。

    cookie在瀏覽器和服務器間來回傳遞,主要應用場景:

    保持登錄
    保持上次查看的頁面
    瀏覽計數
    廣告追蹤
    購物車的狀態保持
    

    2.Cookie的缺陷

    數據大小:作為存儲容器,cookie的大小限制在4KB左右這是非常糟糕的,尤其對于現在復雜的業務邏輯需求,4KB的容量除了存儲一些配置字段和簡單單值信息,對于絕大部分開發者來說真的不知指望什么了。

    安全性問題:由于在HTTP請求中的cookie是明文傳遞的(HTTPS不是),帶來的安全性問題還是很大的。除非在你給定的時間前刷新,否則cookie將在這以后無效并被瀏覽器移除。如果它沒有被終止,在將來所有的該網站的請求中都將攜帶類似的信息頭部,所以他們很容易受到安全問題攻擊影響,例如關鍵攻擊載體的CSRF(Cross Site Request Forgery),XSS(Cross Site Scripting Attacks) 以及 Session Hijacking 。一個用功且專業的開發者也許不會把很多安全細節信息放在cookie中,或者實現一系列的方法來減輕可能的這些形式的攻擊。

    網絡負擔:我們知道cookie會被附加在每個HTTP請求中,在HttpRequest 和HttpResponse的header中都是要被傳輸的,所以無形中增加了一些不必要的流量損失。

    3.webstorage是什么?

    WebStorage是HTML5中本地存儲的解決方案之一,在HTML5的WebStorage概念引入之前除去IE User Data、Flash Cookie、Google Gears等看名字就不懂的解決方案,瀏覽器兼容的本地存儲方案只有使用cookie。

    但WebStorage并不是為了取代cookie而制定的標準,cookie作為HTTP協議的一部分用來處理客戶端和服務器之間的通信是不可或缺的。WebStorage的意圖在于解決本來不應該cookie做,卻不得不用cookie的本地存儲。早些時候,本地存儲使用的是cookies。

    但是Web 存儲需要更加的安全與快速.這些數據不會被保存在服務器上,但是這些數據只用于用戶請求網站數據上.它也可以存儲大量的數據,而不影響網站的性能。數據以 鍵/值 對存在, web網頁的數據只允許該網頁訪問使用.

    4.分類

    WebStorage提供兩種類型的API:localStorage和sessionStorage,兩者的區別看名字就有大概了解,localStorage在本地永久性存儲數據,除非顯式將其刪除或清空,sessionStorage存儲的數據只在會話期間有效,關閉瀏覽器則自動刪除。兩個對象都有共同的API。

    5.方法

    interface Storage {
      readonly attribute unsigned long length;
      DOMString? key(unsigned long index);
      getter DOMString getItem(DOMString key);
      setter creator void setItem(DOMString key, DOMString value);
      deleter void removeItem(DOMString key);
      void clear();
    };
    ls.length:唯一的屬性,只讀,用來獲取storage內的鍵值對數量。
    ls.key(i):根據index獲取storage的鍵名。
    ls.getItem(key):根據key獲取storage內的對應value。
    ls.setItem(‘name’,’bryon’):為storage內添加鍵值對。
    ls.removeItem(‘name’):根據鍵名,刪除鍵值對。
    ls.clear():清空storage對象
    
    PS:ls是localstorage對象名。
    

    鍵值對通常以字符串存儲,你可以按自己的需要轉換該格式。
    以localStorage為例,

    <script type="text/javascript">
    var ls=localStorage;
    console.log(ls.length);
    //0
    ls.setItem('name','Byron');
    ls.setItem('age','24');
    console.log(ls.length);
    //2
    for(var i=0;i<ls.length;i++){  //遍歷localStorage        
        var key=ls.key(i); //獲取鍵
        console.log(key+' : '+ls.getItem(key));
    } 
    /* name : Byron 
       age : 24      */
    ls.removeItem('age');  //刪除age鍵值對
    for(var i=0;i<ls.length;i++){
    var key=ls.key(i);
    console.log(key+' : '+ls.getItem(key));
    }
    /*  name : Byron  */
    ls.clear();  //清空storage對象
    console.log(ls.length);   //0
    </script>
     var person = {'name': 'rainman', 'age': 24};
    
    localStorage.setItem("me", JSON.stringify(person));
    
    console.log(JSON.parse(localStorage.getItem('me')).name);  // 'rainman'
    
    /**
    
     * JSON.stringify,將JSON數據轉化為字符串
    
     *     JSON.stringify({'name': 'fred', 'age': 24});   // '{"name":"fred","age":24}'
    
     *     JSON.stringify(['a', 'b', 'c']);               // '["a","b","c"]'
    
     * JSON.parse,反解JSON.stringify
    
     *     JSON.parse('["a","b","c"]')                    // ["a","b","c"]
    
     */
    

    事件:

    同時HTML5規定了一個storage事件,在WebStorage發生變化的時候觸發,可以用此監視不同頁面對storage的修改

    interface StorageEvent : Event {
      readonly attribute DOMString key;
      readonly attribute DOMString? oldValue;
      readonly attribute DOMString? newValue;
      readonly attribute DOMString url;
      readonly attribute Storage? storageArea;
    };
    

    以下均為屬性:

    key:鍵值對的鍵
    oldValue:修改之前的value
    newValue:修改之后的value
    url:觸發改動的頁面url
    StorageArea:發生改變的Storage
    在使用 web 存儲前,應檢查瀏覽器是否支持 localStorage 和sessionStorage
    if(typeof(Storage)!=="undefined")
      {
      // 是的! 支持 localStorage  sessionStorage 對象!
      // 一些代碼.....
      }
    else
      {
      // 抱歉! 不支持 web 存儲。
      }
    

    localStorage實例

    <script>
        function clickCounter()
        {
            if(typeof(Storage)!=="undefined")
            {
                if (localStorage.clickcount)
                {
                    localStorage.clickcount=Number(localStorage.clickcount)+1;
                }
                else
                {
                    localStorage.clickcount=1;
                }
                document.getElementById("result").innerHTML=" 你已經點擊了按鈕 " + localStorage.clickcount + " 次 ";
            }
            else
            {
                document.getElementById("result").innerHTML="對不起,您的瀏覽器不支持 web 存儲。";
            }
        }
        </script>
        </head>
        <body>
        <p><button onclick="clickCounter()" type="button">點我!</button></p>
        <div id="result"></div>
        <p>點擊該按鈕查看計數器的增加。</p>
        <p>關閉瀏覽器選項卡(或窗口),重新打開此頁面,計數器將繼續計數(不是重置)。</p>
        </body>
        </html>
    

    sessionStorage實例

     <script>
        function clickCounter()
        {
            if(typeof(Storage)!=="undefined")
            {
                if (sessionStorage.clickcount)
                {
                    sessionStorage.clickcount=Number(sessionStorage.clickcount)+1;
                }
                else
                {
                    sessionStorage.clickcount=1;
                }
                document.getElementById("result").innerHTML="在這個會話中你已經點擊了該按鈕 " + sessionStorage.clickcount + " 次 ";
            }
            else
            {
                document.getElementById("result").innerHTML="抱歉,您的瀏覽器不支持 web 存儲";
            }
        }
        </script>
        </head>
        <body>
        <p><button onclick="clickCounter()" type="button">點我!</button></p>
        <div id="result"></div>
        <p>點擊該按鈕查看計數器的增加。</p>
        <p>關閉瀏覽器選項卡(或窗口),重新打開此頁面,計數器將繼續計數(不是重置)。</p>
        </body>
        </html>
    

    sessionstorage生命周期以頁面session為界(不能自己設置過期時間)。只要瀏覽器保持打開,頁面刷新和重載都會保持 sessionStorage 內容,關閉的時候清除。新建標簽頁和新建窗口則屬于新的 session。

    與cookie的異同

    cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞。而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。cookie數據還有路徑(path)的概念,可以限制cookie只屬于某個路徑下;

    存儲大小限制也不同,cookie數據不能超過4k,同時因為每次http請求都會攜帶cookie,所以cookie只適合保存很小的數據,如會話標識。sessionStorage和localStorage雖然也有存儲大小的限制,但比cookie大得多,可以達到5M;

    數據有效期不同,sessionStorage:僅在當前瀏覽器窗口關閉前有效,自然也就不可能持久保持;localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據永久有效;cookie只在設置的cookie過期時間之前一直有效,在這以后無效并被瀏覽器移除。

    作用域不同,sessionStorage不在不同的瀏覽器窗口中共享,即使是同一個頁面;localStorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的;

    WebStorage支持事件通知機制,可以將數據更新的通知發送給監聽者,即storage事件。WebStorage 的 api 接口使用更方便。

    需要注意的地方

    1.瀏覽器兼容性,這個幾乎是所有HTML5新特性中最容易實施的了,因為IE8+的瀏覽器都支持,在IE7、IE6中可以使用IE User Data實現。

    63

    2.由于localStorage和sessionStorage都是對象,所以我們也可以通過”.key”或”[key]”的方式獲取、修改鍵值對,但不推薦這么做 。

    localStorage.userName='Frank';
    console.log(localStorage['userName']);
    

    3.雖然localStorage存儲在本地,但不同的瀏覽器存儲存儲數據是獨立的,所以在Chrome上存儲的localStorage在FireFox上是獲取不到的。

    4.localStorage和sessionStorage只能存儲字符串類型,對于復雜的對象可以使用ECMAScript提供的JSON對象的stringify和parse來處理,低版本IE可以使用json2.js

    5.除了控制臺,Chrome還為本地存儲提供了非常直觀的顯示方式,調試的時候很方便

    64

    localstorage過期設置

    我們都知道localStorage不主動刪除,永遠不會銷毀,那么如何設置localStorage的過期時間呢,今天我們來一起嘗試一下!

     <script type="text/javascript">
        //封裝過期控制代碼
        function set(key,value){
            var curTime = new Date().getTime();
            localStorage.setItem(key,JSON.stringify({data:value,time:curTime}));
        }
        function get(key,exp){
            var data = localStorage.getItem(key);
            var dataObj = JSON.parse(data);
            if (new Date().getTime() - dataObj.time>exp) {
                console.log('信息已過期');
                //alert("信息已過期")
            }else{
                //console.log("data="+dataObj.data);
                //console.log(JSON.parse(dataObj.data));
                var dataObjDatatoJson = JSON.parse(dataObj.data)
                return dataObjDatatoJson;
            }
        }
     </script>
    

    使用場景:
    1.利用本地數據,減少網絡傳輸
    2.弱網絡環境下,高延遲,低帶寬,盡量把數據本地化

    使用方法:

    <script>
    window.onload = function(){
        var Ipt = document.getElementById('input1');
        var value = '{"name":"和派孔明","Age":"18","address":"陸家嘴金融城"}';
        set('information',value);
    
        Ipt.onclick = function(){
            //var dataObjData=get('information',1000);//過期時間為1秒,正常情況下,你點擊的時候已經過期
            //var dataObjData=get('information',1000*60);//過期時間為1分鐘
            //var dataObjData=get('information',1000*60*60);//過期時間為1小時
            //var Obj=get('information',1000*60*60*24);//過期時間為24小時
            var dataObjData=get('information',1000*60*60*24*7);//過期時間為1周
            console.log(dataObjData || null);
                if (dataObjData!="" && dataObjData!=null) {
                        console.log("姓名:"+dataObjData.name);
                        console.log("年齡:"+dataObjData.Age );
                        console.log("地址:"+dataObjData.Age );
                }else{
                    alert("獲取的信息已經過期");
                }   
        }
    }
    </script>
    

    65

    常見效果

    1.自制單選框:label標簽是和input配合使用的,運行代碼,點擊label中內容,使得input獲得焦點

    2.小球慣性運動

    利用拖拽原理
    當鼠標在小球上落下時,記錄舊的事件位置old
    當鼠標在窗口不斷被拖動時,不斷記錄新的事件位置new
    當鼠標抬起時,計算old、new差值(即為小球運動方向),然后不斷利用差值給小球賦值

    3.圖片拉近效果
    鼠標移入transform:scale(1.2);

    4.立體盒子

    7.11記

    7.13記 瀑布流(圖片等寬不等高)


    1. JS實現
      單行排列不下時,接著最矮的那一列向下排列

      檢測是否具有滾動條加載事件,
      最后元素.offsetTop+最后元素.offsetHeight/2<頁面滾走距離+窗口高度

      注意:開始時,頁面的圖片必須足夠,才能觸發onscroll加載

    2. JQuery
      注意:

    jQuery方法,只給jQuery對象用
    .get(i)/[i]    //把jq對象轉化為原生
    width//僅僅獲取所定義的寬度
    outerwidth//獲取width+padding+border
    jQuery獲取寬度,對象.width(無單位的數值)
    **jQuery支持連綴,隱式迭代**

    理解:對象后可跟多個方法 query選擇器,得到的是這個對象的集合,所以不用再循環去找了

    jQuqry遍歷:

    類數組.each(function(index,value){
    
    })
    注:value指每一個參與遍歷的dom對象
    jQuery.inArray( value, array [, fromIndex ] )
    

    描述: 在數組中查找指定值并返回它的索引(如果沒有找到,則返回-1)。

    JS入門

    標簽(空格分隔): 自定義屬性+數組,字符串方法+ajax


    1. 入門須知:

      js概念:JavaScript原名叫LiveScript,是一種基于對象和事件驅動的客戶端腳本語言,最初的設計是為了檢驗HTML表單輸入的正確性。

      完整的JavaScript是由3部分組成的:
      - ECMAScript (當前學習版本5.1) 規定了JS的基本語法
      - BOM 瀏覽器對象模型
      - DOM 文檔對象模型

      元素對象的屬性操作是指操作元素的行間屬性

      操作元素的class不能用class,需要用className

      使用cssText會先清空之前的所有樣式,然后添加它的值

      獲取元素的顏色的屬性值是不準確的

        console.log(box.style.background);

    獲取圖片的src屬性的時候獲取到的是絕對路徑

        console.log(img.src);

    直接修改是可以寫相對路徑的

        img.src = './img/2.jpg';

    數組里面可以儲存任意類型的數據

    length 屬性 代表數組的長度, 是可讀可寫的

    模擬單選的思路:

    (1) 清除所有,給當前添加

    (2) 清除上一個,給當前添加(見.選項卡的應用)

    (3) 使用開關效果

    如果多個元素同時需要切換某種狀態,需要為每個元素添加自己的用來控制狀態的屬性,不能共用一個。

    2.字符串方法

    str.length    //獲取它的長度
    
    str.charAt(2)  //獲取傳入下標處的字符,如果不傳默認為”0“,超出時返回空
    
    str.charCodeAt(2)  //獲取傳入下標處的Unicode 編碼,不寫默認為'0'
    
    String.fromCharCode()//傳入的編碼值返回相應的字符,可以傳多個編碼,用','分割(靜態方法,String不能少)
    
    str.indexOf()  //獲取()中在字符串中第一次出現的位置(可以一次找多個字符),未找到返回-1,第二個參數為開始位置(負數和不寫默認是從0開始)
    
    lastIndexOf() //獲取()中在字符串中最后出現的位置(可以一次找多個字符),未找到返回-1,第二個參數為開始位置(負數是從0開始,不寫默認從最后開始找)
    
    str.substring()//截取從第一個參數到第二個參數(不包括第二個參數)的字符串,一個參數時截取到最后,會自動檢測兩個參數大小,按照從小到大的順序截取,不寫和負數默認從0開始      左閉右開區間
    
    str.slice()  //與上面方法類似,但是不會自動檢測參數大小,負數從后向前找(最后一個為-1)    必須從左到右的截取
    
    str.split('.') //以.分割整個字符串并輸出一個數組,即使什么參數都不傳也會變成一個數據的數組,因此如果想把一個字符串每個字符分割,就傳一個空字符串'',(注意:如果分隔符在左右,也會生成個空在數組里),第二個可選參數限制生成數組的長度(剩下的會被舍去)   替換或者刪除子串
    
    str.toUpperCase()  //把內容轉換成大寫
    
    str.toLowerCase()  //把內容轉換成小寫
    

    數組方法

    • arr.join(‘’)
      //split的反義詞,把arr數組里的內容用‘’連接成字符串,生成一個所有數組組成的字符串,‘’里寫什么每個數據中間就會用什么連接

    • arr.push()/arr.unshift()//給數組向后/前添加數據,這兩個方法的返回值是新數組的長度,ie8以下不支持unshift的返回值

    • arr.pop()/arr.shift()//把數組最后/最前的數據刪除,這兩個方法的返回值是被刪除的數據

    • splice(a,b,c)//作用:刪除、替換、添加
      刪除:splice(a,b)a是刪除第一項序號,b表示刪除的數量
      替換:splice(a,b,c)a是替換的起點項,b表示刪除的數量,c及c后面表示要加什么數據或多個數據
      添加:splice(a,0,c)a是添加的起點項,0表示不刪除,c及c后面表示要加什么數據或多個數據 加在下標a之前
      只有刪除的時候會有返回值(是被刪除的數據)

    • 數組sort
      作用:排序
      默認機制按照字符串把內容按照順序排列(Unicode 編碼),因此排序數字時會出錯
      sort()的比較函數,就是把一個寫好的函數作為一個參數

    • 數組concat
      作用:連接數組
      arr1.concat(arr2);和之前的兩個數組沒有關系,可以傳多個參數,把多個數組連接一起

    • 數組reverse
      arr1.reverse();把數組變成倒序

    AJAX(注意簡寫)


    例子:見分頁效果


    1.異步請求的核心:XHR(進行數據的異步交換)
    2.HTTP是無狀態協議,即不建立持久的連接。–無記憶
    3.一個完整的HTTP請求過程的七大步驟:

    1. 建立TCP連接
    2. 瀏覽器向服務器發送請求命令
    3. 瀏覽器發送請求頭信息
    4. 服務器應答
    5. 服務器發送應答頭信息
    6. 服務器給瀏覽器發送數據
    7. 服務器關閉TCP連接

    4.HTTP請求有以下組成:

    1. 方法、動作。例如:get/post
    2. url:請求地址
    3. 請求頭(包含客戶端環境信息,身份驗證信息等)
    4. 請求體(請求正文,包含客戶提交的查詢字符串、表單信息等)

    5.概念

    1. get:獲取或查詢信息;通過url傳遞參數;限制發送的信息數量在2000個左右
    –安全(發送的信息對任何人可見,通過鍵值對顯示在url中 優點:通過書簽url保存頁面)

    冪等(查詢內容,不會因為查詢次數被改變)

    2. post:給服務器發送信息;修改服務器上的資源;一般用于提交表單或修改刪除;對發送信息數量無限制
    –安全(發送的內容鍵值對,通常被嵌于HTTP請求體中)

    6.HTTP響應由以下組成

    • 狀態碼(文字和數字組成),顯示請求成功或失敗
    • 響應頭(例如:服務器類型,日期時間,內容類型,長度等)
    • 響應體(響應正文)//字符串或html代碼等

    7.狀態碼(首位數字,定義了狀態碼類型) —方便調試

    • 1xx:信息類,表示接受到瀏覽器請求,正在進一步的處理中
    • 2xx:成功。表示請求被成功接收、理解和處理 例如:200(ok)
    • 3xx:重定向。表示請求沒有成功,客戶必須采取進一步的動作
    • 4xx:客戶端(提交的請求)錯誤。 例如:404 not found(請求所引用的文檔不存在)
    • 5xx:服務器錯誤。表示服務器不能完成對請求的處理。 如:500

    8.XHR發送請求

    • open(method,url,async)

    method:一般大寫
    url:相對文檔的地址,或絕對地址
    默認true(異步請求)

    若將async設置為false,則相當于return false.(注:return false作用: 一.阻止瀏覽器默認行為 二.阻止冒泡)

    • send(string)

    若采用get,則string可以不寫或null,因為它將拼在url中
    若post,則一定寫str(否則無意義)

    • 思路
    request.open("POST","create.php",true);
    request.setRequsetHeader("content-type","application/x-www-form-urlencoded");//聲明文檔類型
    request.send("name=王達&sex=男")

    注意setHR位置,否則引起異常

    • resonseText獲得字符串形式的響應數據
      statue和statusText:以數字和文本形式返回HTTP狀態碼
      getALLRH():獲取所有的響應報頭
      getRH():查詢相應中某個字段的值

    • readyState

    0:請求未初始化,open還未調用
    1:服務器連接已建立,open已經調用了
    2:請求已接收,即接收到頭信息
    3:請求處理中,即接收到響應主體
    4:請求已完成,且響應已就緒,即響應完成

    // JavaScript Document
    function ajax(url,fnSucc,fnFaild){
        //參數:1、連接服務器的地址;2、成功時函數;3、失敗時函數
        //1、創建Ajax對象(創建XHR對象)
        var oAjax = null;
        if(window.XMLHttpRequest){
            oAjax = new XMLHttpRequest(); //ie6及以下不兼容
        }else{
            oAjax = new ActiveXObjext("Microsoft.XMLHTTP");
        }
    
        //2、連接服務器
        oAjax.open('GET',url,true);
    
        //3、發送請求
        oAjax.send();
    
        //4、接收服務器的返回
        oAjax.onreadystatechange = function(){
            if(oAjax.readyState ==4){  //完成
                if(oAjax.status ==200){
                    //alert('成功: '+oAjax.responseText);
                    fnSucc(oAjax.responseText);
                }else{
                    //alert('失敗');
                    if(fnFaild){
                        fnFaild();
                    }
                }
            }
        }
    }

    9.PHP測試頁面

    • PHP腳本以

    跨域(擴展知識)

    1. 一個域名的組成:

      協議、域名、端口、虛擬目錄、文件目錄

    2. 當協議、子域名、主域名、端口號中任意一個不同時,都算作不同域。

    3. 跨域:不同域之間互相請求資源

    4. js出于安全考慮,不允許跨域調用其他頁面的對象。

    5. 端口號默認80

    6. 注意區分HTTP和HTTPS

    7. 主域名確定,則它下面的子域名可以有多個,也可以多級。

      例如:

    主域名:abc.com

    一級子域名:www.abc.com

    二級子域名:xiantai.bbs.abc.com

    三級子域名:haidian.beijing.bbs.abc.com

    處理跨域方式(一):代理

    所謂反向代理服務器,它是代理服務器中的一種。客戶端直接發送請求給代理服務器,然后代理服務器會根據客戶端的請求,從真實的資源服務器中獲取資源返回給客戶端。所以反向代理就隱藏了真實的服務器。利用這種特性,我們可以通過將其他域名的資源映射成自己的域名來規避開跨域問題。

    處理跨域方式(二):JSONP

    1.原理:ajax無法跨域是因為同源政策,但是帶有src的標簽(例如img、iframe、script)是不受該政策限制,故我們可以利用向頁面動態添加script標簽,來完成對跨域的訪問

    2.假設,我們源頁面是在a.com,想要獲取b.com的數據,我們可以動態插入

    script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'http://www.b.com/getdata?callback=demo';
    

    這里,我們利用動態腳本的src屬性,變相地發送了一個http://www.b.com/getdata?callback=demo的GET請求。這時候,b.com頁面接受到這個請求時,如果沒有JSONP,會正常返回json的數據結果,像這樣:

    { msg: 'helloworld' }
    

    而利用JSONP,服務端會接受這個callback參數,然后用這個參數值包裝要返回的數據:

    demo({msg: 'helloworld'});
    

    這時候,如果a.com的頁面上正好有一個demo的函數:

    function demo(data) {
      console.log(data.msg);
    }
    

    當遠程數據一返回的時候,隨著動態腳本的執行,這個demo函數就會被執行。

    到這里,你應該能明白這個技術為什么叫JSONP了吧?就是因為使用這種技術服務器會接受回調函數名作為請求參數,并將JSON數據填充進回調函數中去。

    3.特點:

    優點:兼容性好

    缺點:(1)只能get不能post,因為script引入的資源,參數全都顯式地url里 (2)安全隱患,因為動態插入<sciprt>本質是腳本注入

    4.封裝

    jQuery

    $.ajax({
      url: "http://tonghuashuo.github.io/test/jsonp.txt",
      dataType: 'jsonp',
      jsonp: "callback",
      jsonpCallback: "dosomething"
    })
    .done(function(res) {
      console.log("success");
      console.log(res);
    })
    .fail(function(res) {
      console.log("error");
      console.log(res);
    });
    

    實際發送出來的完整請求長這樣:
    http://tonghuashuo.github.io/test/jsonp.txt?callback=dosomething&_=1471419449018。,后面的隨機字符串是jQuery加上的。

    這里真正需要關心的參數有以下 3 個:

    dataType: 'jsonp',用于表示這是一個 JSONP 請求。
    jsonp: 'callback',用于告知服務器根據這個參數獲取回調函數的名稱,通常約定就叫 callback。
    jsonpCallback: 'dosomething',回調函數的名稱,也是前面callback參數的值。
    

    其中jsonpCallback參數是可以省略的,jQuery 會自動生成一個隨機字符串作為函數名,推薦這么做,以減少不必要的命名工作,同時排除潛在的安全隱患。

    注意:省略jsonpCallback的同時,jsonp參數必須指明,不能為false。

    js

    <script>
    
      // 實現回調函數,這里沒有了 jQuery 的封裝,必須手動指定并實現
      var dosomething = function(data){
          console.log(data);
      };
      // 提供 JSONP 服務的 URL 地址,查詢字符串中加入 callback 指定回調函數
      var url = "tonghuashuo.github.io/test/jsonp.txt?callback=docomething";
      // 創建 <script> 標簽,設置其 src 屬性
      var script = document.createElement('script');
      script.setAttribute('src', url);
      // 把 <script> 標簽加入 <body> 尾部,此時調用開始。
      document.getElementsByTagName('body')[0].appendChild(script);
      // 因為目標 URL 是一個后臺腳本,訪問后會被執行,返回的 JSON 被包裹在回調函數中以字符串的形式被返回。
      // 返回的字符串放入 <script> 中就成為了一個普通的函數調用,執行回調函數,返回的 JSON 數據作為實參被傳給了回調函數。
    
    </script>
    

    是不是覺得沒看夠,來點干貨,2333…

    (1)資源類型文件可以實現跨域:當前網頁可以查看其他域的image標簽、script腳本文件、css等,其中原理是利用src屬性或link的href屬性。(image標簽、script腳本文件、css屬于資源類型文件。)
    舉個例子:
    <script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js"></script>
    常見的利用cdn引入庫文件,便屬于利用script標簽實現跨域。

    (2)利用高德地圖舉例:

    77

    (3)為何jsonp只能gt,不能post?

    i>script跨域請求靜態資源cdn,只能為get,不能為post
    ii>當用戶輸入完url來訪問網頁時,便會對服務器發送請求,該請求屬于通過get獲取資源。若將資源替換成API(或說是回調),即可實現跨域。
    

    處理跨域方式(三):XHR2

    1.HTML5提供的XHR Level2已經實現了跨域訪問,以及其他的新功能

    2.IE10-獨不支持XHR 2

    3.在服務器端添加:

    header('Access-Control-Origin:*');
    header('Access-Control-Allow-Methods:POST,GET');
    

    4.并非所有瀏覽器都實現了XHR2規范,但所有瀏覽器都實現了它規定的部分內容。

    處理跨域方式(四):web sockets

    WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊。

    原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。

    1.)前端代碼:

    <div>user input:<input type="text"></div>
    <script src="./socket.io.js"></script>
    <script>
    var socket = io('http://www.domain2.com:8080');
    
    // 連接成功處理
    socket.on('connect', function() {
        // 監聽服務端消息
        socket.on('message', function(msg) {
            console.log('data from server: ---> ' + msg); 
        });
    
        // 監聽服務端關閉
        socket.on('disconnect', function() { 
            console.log('Server socket has closed.'); 
        });
    });
    
    document.getElementsByTagName('input')[0].onblur = function() {
        socket.send(this.value);
    };
    </script>
    

    2.)Nodejs socket后臺:

    var http = require('http');
    var socket = require('socket.io');
    
    // 啟http服務
    var server = http.createServer(function(req, res) {
        res.writeHead(200, {
            'Content-type': 'text/html'
        });
        res.end();
    });
    
    server.listen('8080');
    console.log('Server is running at port 8080...');
    
    // 監聽socket連接
    socket.listen(server).on('connection', function(client) {
        // 接收信息
        client.on('message', function(msg) {
            client.send('hello:' + msg);
            console.log('data from client: ---> ' + msg);
        });
    
        // 斷開處理
        client.on('disconnect', function() {
            console.log('Client socket has closed.'); 
        });
    });
    

    處理跨域方式(五):CORS

    阮一峰

    1.在CORS中,瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。

    2.瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

    必須同時滿足以下兩大條件,才屬于簡單請求。否則屬于非簡單請求。

    (1) 請求方法是以下三種方法之一:

    HEAD
    GET
    POST
    

    (2)HTTP的頭信息必須屬于以下幾種字段:

    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
    

    3.對于簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。下面是一個例子,瀏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin字段。

    GET /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...
    

    4.非簡單請求

    (1)非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。

    下面是這個”預檢”請求的HTTP頭信息。

    OPTIONS /cors HTTP/1.1
    Origin: http://api.bob.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...
    

    “預檢”請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息里面,關鍵字段是Origin,表示請求來自哪個源。

    (2)服務器收到”預檢”請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認允許跨源請求,就可以做出回應。

    (3)一旦服務器通過了”預檢”請求,以后每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin頭信息字段。

    5.CORS比JSONP更強大JSONP只支持GET請求,CORS支持所有類型的HTTP請求;JSONP的優勢在于支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。

    處理跨域方式(六):圖像ping

    圖像ping是與服務器進行簡單,單項的跨域通信的一種方式

    var img=new Image();
    img.onload=img.onerror=function(){
         alert("done")
    }
    img.src="http://www.example.com/test?name=sl";
    

    處理跨域方式(七):jquery

    只不過我們不需要手動的插入script標簽以及定義回掉函數。jquery會自動生成一個全局函數來替換callback=?中的問號,之后獲取到數據后又會自動銷毀,實際上就是起一個臨時代理函數的作用。

    $.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。

    $.getJSON(
       "http://example.com/data.php?callback=?",
       function (jsondata){
           // 回調事件
    });
    

    處理跨域方式(八):iframe

    只有在主域相同時,才能使用該方法

    處理跨域方式(九):window.name+iframe

    window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的,并不會因新頁面的載入而進行重置。

    處理跨域方式(十):window.postMesssage+iframe

    postMessage是HTML5新增在window對象上的方法,目的是為了解決在父子頁面上通信的問題。該技術有個專有名詞:跨文檔消息(cross-document messaging)。利用postMessage的特性可以實現較為安全可信的跨域通信。

    postMessage方法接受兩個參數:

    message: 要傳遞的對象,只支持字符串信息,因此如果需要發送對象,可以使用JSON.stringify和JSON.parse做處理
    targetOrigin: 目標域,需要注意的是協議,端口和主機名必須與要發送的消息的窗口一致。如果不想限定域,可以使用通配符“*”,但是從安全上考慮,不推薦這樣做。

    處理跨域方式(十一):Window.loacationhhash+iframe

    location.href可以獲取url中“#”后面的值,并且改變hash值不會導致頁面刷新,所以可以利用hash值來進行數據的傳遞,當然數據量有限。

    缺點:(1)數據直接暴漏在url中 (2)數據容量和類型都有限

    處理跨域方式(十二):document.domain

    如,來自www.a.com想要獲取document.a.com中的數據。只要基礎域名相同,便可以通過修改document.domain為基礎域名的方式來進行通信,但是需要注意的是協議和端口也必須相同。


    json和jsonp


    1.AJAX以何種格式來交換數據?

    解決方案:通過xml或自定義字符串來描述數據
    

    跨域問題?

    通過服務器端代理
    

    2.用JSON來傳數據,靠JSONP來跨域

    json

    1.一種數據描述格式,也是一種基于文本的數據交換方式

    2.優點:
      

       1、基于純文本,跨平臺傳遞極其簡單;
      2、Javascript原生支持,后臺語言幾乎全部支持;
      3、輕量級數據格式,占用字符數量極少,特別適合互聯網傳遞;
      4、可讀性較強,雖然比不上XML那么一目了然,但在合理的依次縮進之后還是很容易識別的;
      5、容易編寫和解析,當然前提是你要知道數據結構;
      JSON的缺點當然也有,但在作者看來實在是無關緊要的東西,所以不再單獨說明。
    

    3.規則:

    • JSON只有兩種數據類型描述符,大括號{}和方括號[],其余英文冒號:是映射符,英文逗號,是分隔符,英文雙引號”“是定義符。

    • 大括號{}用來描述一組“不同類型的無序鍵值對集合”(每個鍵值對可以理解為OOP的屬性描述),方括號[]用來描述一組“相同類型的有序數據集合”(可對應OOP的數組)。
        

    • 上述兩種集合中若有多個子項,則通過英文逗號,進行分隔。
        
    • 鍵值對以英文冒號:進行分隔,并且建議鍵名都加上英文雙引號””,以便于不同語言的解析。
        
    • JSON內部常用數據類型無非就是字符串、數字、布爾、日期、null 這么幾個,字符串必須用雙引號引起來,其余的都不用

    • 建議:如果客戶端沒有按日期排序功能需求的話,那么把日期時間直接作為字符串傳遞就好,可以省去很多麻煩

    4.jsonp的產生

    (1)ajax直接請求普通文件存在“跨域無權限訪問”的問題,即只要是跨域請求,一律不準,然而,Web頁面上調用js文件時則不受是否跨域的影響(凡是擁有src屬性的標簽都擁有跨域能力,例如script、iframe);利用以上特點可判斷:若想通過純web端(除了ActiveX控件、服務端代理、屬于HTML5之Websocket)實現跨域訪問數據,可以在遠程服務器上將數據裝進js格式的文件里,供客戶端調用和處理。

    (2)解決方案:web客戶端先調用服務器上動態生成的js文件(一般以json為后綴),當調用成功后,就可獲得需要的數據,然后即可按照自己需求來處理數據。

    注:服務器動態生成json文件,目地:將客戶端需要的數據裝進去

    (3)為了便于客戶端使用數據,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP

    特點:

    允許用戶先傳遞一個callback參數給服務端,然后服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。
    

    5.JSONP的客戶端具體實現:

    案例一:web頁面無條件得執行js文件中的安全代碼,即使跨域

    遠程服務器remoteserver.com根目錄下有個remote.js文件,代碼如下:

    alert('我是遠程文件');
    

    本地服務器localserver.com下有個jsonp.html頁面代碼如下:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
    </head>
    <body>
    
    </body>
    </html>
    

    毫無疑問,頁面將會彈出一個提示窗體,顯示跨域調用成功。

    案例二:在jsonp.html頁面定義一個函數,然后在遠程remote.js中傳入數據進行調用

    jsonp.html頁面代碼如下:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <script type="text/javascript">
        var localHandler = function(data){
            alert('我是本地函數,可以被跨域的remote.js文件調用,遠程js帶來的數據是:' + data.result);
        };
        </script>
        <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
    </head>
    <body>
    
    </body>
    </html>
    

    remote.js文件代碼如下:

    localHandler({"result":"我是遠程js帶來的數據"});
    

    結果:頁面成功彈出提示窗口,顯示本地函數被跨域的遠程js調用成功,并且還接收到了遠程js帶來的數據。

    方案三:調用者可以傳一個參數過去告訴服務端“我想要一段調用XXX函數的js代碼,請你返回給我”,于是服務器就可以按照客戶端的需求來生成js腳本并響應了。

    看jsonp.html頁面的代碼:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <script type="text/javascript">
        // 得到航班信息查詢結果后的回調函數
        //服務端返回數據時會將flightHandler參數作為函數名來包裹住JSON數據
        var flightHandler = function(data){
            alert('你查詢的航班結果是:票價 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 張。');
        };
        // 提供jsonp服務的url地址(不管是什么類型的地址,最終生成的返回值都是一段javascript代碼)
        var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
        // 創建script標簽,設置其屬性
        var script = document.createElement('script');
        script.setAttribute('src', url);
        // 把script標簽加入head,此時調用開始
        document.getElementsByTagName('head')[0].appendChild(script);
        </script>
    </head>
    <body>
    
    </body>
    </html>
    

    結果:不再直接把遠程js文件寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分

    分析:

       調用的url中傳遞了一個code參數,告訴服務器我要查的是CA1998次航班的信息,而callback參數則告訴服務器,我的本地回調函數叫做flightHandler,所以請把查詢結果傳入這個函數中進行調用。
      OK,服務器很聰明,這個叫做flightResult.aspx的頁面生成了一段這樣的代碼提供給jsonp.html
      
     
    

    方案四:jQuery如何實現jsonp調用

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html xmlns="http://www.w3.org/1999/xhtml" >
     <head>
         <title>Untitled Page</title>
          <script type="text/javascript" src=jquery.min.js"></script>
          <script type="text/javascript">
         jQuery(document).ready(function(){
            $.ajax({
                 type: "get",
                 async: false,
                 url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
                 dataType: "jsonp",
                 jsonp: "callback",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認為:callback)
                 jsonpCallback:"flightHandler",//自定義的jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動為你處理數據
                 success: function(json){
                     alert('您查詢到航班信息:票價: ' + json.price + ' 元,余票: ' + json.tickets + ' 張。');
                 },
                 error: function(){
                     alert('fail');
                 }
             });
         });
         </script>
         </head>
      <body>
      </body>
     </html>
    

    分析:(jqury把jsonp歸入了ajax,其實它們真的不是一回事兒),它自動幫你生成回調函數,并把數據取出來,供success屬性方法來調用

    服務器端實現原理:   

    類似于拼接字符串

    flightHandler({
        "code": "CA1998",
        "price": 1780,
        "tickets": 5
    });

    我們看到,傳遞給flightHandler函數的是一個json,它描述了航班的基本信息。運行一下頁面,成功彈出提示窗口,jsonp的執行全過程順利完成!

    6.補充:

    • ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Ajax</title>
    </head>
    <body>
        <script src="./js/$Ajax.js"></script>
        <script type="application/javascript">
            window.onload = function () {
    
                // 該字段用于定義請求數據的條數
                var data = {
                    pageSize: 4
                };
    
                //初始化
                $Ajax.init({
                    url: 'http://v2.sohu.com/public-api/headLines/home/hotAuthors',
                    type: "GET",
                    data: data,
                    //用JSON來傳數據,靠JSONP來跨域
                    dataType:"jsonp",
                    success: function (res) {
                        console.log(res);
                    },
                    error: function () {
                        console.log('請求數據失敗!');
                    }
                });
            };
        </script>
    </body>
    </html>

    $Ajax.js文件:

    ;(function () {
        var $Ajax = function (options) {
            this.config = {
                url: '',
                type: 'get',
                async: true,
                dataType: 'json',
                contentType: 'application/json',
                data: {}
            };
            this.start(options);
        };
    
        var xhr = null;
    
        $Ajax.init = function (options) {
            new $Ajax(options);
        };
    
        $Ajax.prototype = {
            constructor: $Ajax,
            createXHR: function () {
                if(typeof XMLHttpRequest != 'undefined'){
                    return new XMLHttpRequest();
                } else {
                    throw new Error('No XHR object available!');
                }
            },
            start: function (options) {
                xhr = this.createXHR();
                if(options.url){
                    this.config.url = options.url;
                } else {
                    throw new Error("url cannot be null!");
                }
                if(options.type){
                    this.config.type = options.type;
                }
                if(options.async){
                    this.config.async = options.async;
                }
                if(options.dataType){
                    this.config.dataType = options.dataType;
                }
                if(options.data){
                    this.config.data = options.data;
                }
                if(options.success){
                    this.config.success = options.success;
                }
                if(options.error){
                    this.config.error = options.error;
                }
                if(options.beforeSend){
                    options.beforeSend();
                }
    
                var complete = function () {
                    return new Promise(function (resolve, reject) {
                        if(xhr.readyState == 4){
                            if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                                resolve(options.success(xhr.responseText));
                            }
                        } else {
                            if(options.error){
                                resolve(options.error());
                            } else {
                                throw new Error("Request was unsucessful:"+xhr.status);
                            }
                        }
                    });
                }
    
                if(this.config.dataType == 'json' || this.config.dataType == 'JSON'){   //  非跨域處理
                    if((this.config.type == 'get') || (this.config.type == 'GET')){
                        for(var item in this.config.data){
                            this.config.url = addURLParam(this.config.url, item, this.config.data[item]);
                        }
    
                        xhr.onreadystatechange = complete;
                        xhr.open(this.config.type, this.config.url, this.config.async);
                        xhr.send(null);
                    }
    
                    if((this.config.type == 'post') || (this.config.type == 'POST')){
                        xhr.addEventListener('readystatechange', complete, false);
                        xhr.open(this.config.type, this.config.url, this.config.async);
    
                        if(options.contentType){
                            this.config.contentType = options.contentType;
                        }
    
                        xhr.setRequestHeader('Content-Type', this.config.contentType);
                        xhr.send(serialize(this.config.data));
                    }
    
                } else if (this.config.dataType == 'jsonp' || this.config.dataType == 'JSONP'){   //  跨域處理
                    if((this.config.type == 'get') || (this.config.type == 'GET')){ //  jsonp只能進行get請求跨域
    
                        //  創建script標簽
                        var cbName = 'callback';
                        var timer = null;
                        var head = document.getElementsByTagName('head')[0];
                        var scriptTag = document.createElement('script');
    
                        this.config.callback = cbName;
                        head.appendChild(scriptTag);
    
                        //  創建jsonp的回調函數
                        window[cbName] = function (json) {
                            head.removeChild(scriptTag);
                            clearTimeout(timer);
                            window[cbName] = null;
                            options.success && options.success(json);
                        };
    
                        //  超時處理
                        if(options.time){
                            timer = setTimeout(function () {
                                head.removeChild(scriptTag);
                                options.fail && options.fail({message: "Over time!"});
                                window[cbName] = null;
                            }, options.time);
                        }
    
                        this.config.url = this.config.url + "?callback=" + cbName;
    
                        for(var item in this.config.data){
                            this.config.url = addURLParam(this.config.url,item,this.config.data[item]);
                        }
    
                        scriptTag.src = this.config.url;
                    }
                } else {
                    throw new Error('dataType is error!');
                }
            }
        };
    
        function addURLParam(url, name, value) {
            url += (url.indexOf('?') == -1 ? '?' : '&');
            url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
            return url;
        }
    
        //  序列化函數
        function serialize(data) {
            var str = '';
    
            for(var item in data){
                str += item + '=' + data[item] + '&';
            }
            return str.slice(0, str.length - 1);
        }
    
        window.$Ajax = $Ajax;
    })();

    jQuery選擇器

    1. 基礎選擇器

    id選擇器 –其返回值為Array。

    $(“#div1”).css(“background”,”red”);

    標簽名選擇器 –其返回值為Array。

    $(“div”).css(“background”,”red”);

    class選擇器 –其返回值為Array。

    (“.box”).css(“background”,”red”);
    //不需遍歷,取到所有.box元素,生成類數組

    *選擇器 –其返回值為Array。

    匹配所有元素

    selector1,selector2,selectorN選擇器 –其返回值為Array。

    將每一個選擇器匹配到的元素合并后一起返回

    2.層級選擇器

    • ancestor descendant選擇器(其返回值為:Array
    //第一個li內容
    jQuery(“li:first”).text();
    
    //最后一個li內容
    jQuery(“li:last”).text();
    
    //input未被選中的值 
    jQuery(“li input:not(:checked)”).val();
    
    //索引為偶數的li
    jQuery(“li:even”).text();
    
    //索引為奇數的li 
    jQuery(“li:odd”).text();
    
    //索引大于2的li的內容 
    jQuery(“li:gt(2)”).text();
    
    //索引小于1的li的內容 
    jQuery(“li:lt(1)”).text();

    4.內容過濾選擇器

    (1):contains選擇器。

    (2):empty選擇器。

    (3):has選擇器。

    (4):parent選擇器。

    //選中內容包含hyip的li
    jQuery(“li:contains(‘hyip’)”)
    
    //選中內容為空的li的后一個li
    jQuery(“li:empty+li”)
    
    //選中包含(嵌套)a標簽的li
    jQuery(“li:has(a)”)

    5.可見性過濾選擇器

    (1):hidden選擇器。

    (2):visible選擇器。

    //選中不可見的li
    jQuery(“li:hidden”).text();
    
    //選中可見的li
    jQuery(“li:visible”).text();

    6.屬性過濾選擇器

    (1) [attribute]選擇器。

    (2)[attribute=value]、[attribute!=value]選擇器(此處包含兩種)。

    (3)[attribute^=value]、[attribute$=value]、[attribute*=value]選擇器(此處包含三種)。

    (4)[selector][selector2]選擇器。

    //name為hyipinvest
    jQuery(“input[name=’hyipinvest’]”)
    
    //name以hyip開始
    jQuery(“input[name^=’hyip’]”)
    
    //name以hyip結束
    jQuery(“input[name$=’hyip’]”)
    
    //name包含oo
    jQuery(“input[name*=’oo’]”

    7.子元素過濾選擇器

    (1):nth-child選擇器。

    (2):first-child、:last-child選擇器(兩種)。

    (3):only-child選擇器。

    8.表單對象屬性過濾選擇器

    表單屬性過濾選擇器
    (1):enabled、:disabled選擇器。
    (2):checked選擇器。
    (3):selected選擇器。
    
    表單過濾選擇器
    (1):input選擇器。
    (2):text、:password選擇器。
    (3):radio、:checkbox選擇器。
    (4):submit、:image、:reset、:button、:file選擇器。
    (5):hidden選擇器。
    

    ———– 7.16

    scss


    CSS預處理

    1.CSS 預處理器用一種專門的編程語言,進行 Web 頁面樣式設計,然后再編譯成正常的 CSS 文件,以供項目使用。CSS 預處理器為 CSS 增加一些編程的特性,無需考慮瀏覽器的兼容性問題”

    2.可以在 CSS 中使用變量、簡單的邏輯程序、函數(如變量$color)等等在編程語言中的一些基本特性,可以讓你的 CSS 更加簡潔、適應性更強、可讀性更佳,更易于代碼的維護等

    3.CSS 預處理器語言,比如說:

    Sass(SCSS)

    LESS

    Stylus

    Turbine

    Swithch CSS

    CSS Cacheer

    DT CSS

    什么是 Sass?

    1.官方定義:Sass 是一門高于 CSS 的元語言,它能用來清晰地、結構化地描述文件樣式,有著比普通 CSS 更加強大的功能。
    Sass 能夠提供更簡潔、更優雅的語法,同時提供多種功能來創建可維護和管理的樣式表。

    2.Sass 是采用 Ruby 語言編寫的一款 CSS 預處理語言,它誕生于2007年,是最大的成熟的 CSS 預處理語言。

    3.為什么早期不如 LESS 普及?

    scss的縮進式風格可以有效縮減代碼量,強制規范編碼風格;
    但它一方面并不為大多數程序接受,另一方面無法兼容已有的 CSS 代碼。
    這也是 Sass 雖然出現得最早,但遠不如 LESS 普及的原因
    

    Sass 和 SCSS 有什么區別?

    Sass 和 SCSS 其實是同一種東西,我們平時都稱之為Sass,兩者之間不同之處有以下兩點:

    文件擴展名不同,Sass是以“.sass”后綴為擴展名,而SCSS是以“.scss”后綴為擴展名

    語法書寫方式不同,Sass是以嚴格的縮進式語法規則來書寫,`不帶大括號({})和分號(;) 而 SCSS 的語法書寫和我們的 CSS語法書寫方式非常類似。

    sass語法

    SCSS 是 Sass 的新語法格式,從外形上來判斷他和 CSS 長得幾乎是一模一樣,代碼都包裹在一對大括號里,并且末尾結束處都有一個分號。其文件名格式常常以“.scss”為擴展名。

    提示:

    • 不管是 Sass 的語法格式還是 SCSS 的語法格式,他們的功能都是一樣的,不同的是其書寫格式和文件擴展名不同
    • “.sass”只能使用 Sass 老語法規則(縮進規則),“.scss”使用的是 Sass 的新語法規則,也就是 SCSS 語法規則(類似 CSS 語法格式)。
    • 后面的語法,都是scss語法

    scss的編譯

    • 命令編譯

    • GUI工具編譯

    • 自動化編譯

    scss命令編譯

    • 單文件編譯:

    sass <要編譯的Sass文件路徑>/style.scss:<要輸出CSS文件路徑>/style.css

    • 多文件編譯

      (對整個項目所有 Sass 文件編譯成 CSS 文件)

    sass sass/:css/
    表示將項目中“sass”文件夾中所有“.scss”(“.sass”)文件編譯成“.css”文件,并且將這些 CSS 文件都放在項目中“css”文件夾中。

    上述命令的缺點:編譯Sass時,開啟“watch”功能,這樣只要你的代碼進行任保修改,都能自動監測到代碼的變化,并且給你直接編譯出來:

    sass –watch
    <要編譯的Sass文件路徑>/style.scss:<要輸出CSS文件路徑>/style.css

    可以理解為 將sass下的style.scss文件,編譯成css文件夾下的style.css文件
    

    注:查看scss編譯參數

    scss -h

    實例:

    假設我本地有一個項目,我要把項目中“bootstrap.scss”編譯出“bootstrap.css”文件,并且將編譯出來的文件放在“css”文件夾中,我就可以在我的命令終端中執行:

    sass --watch 
    sass/bootstrap.scss:css/bootstrap.css
    

    一旦我的 bootstrap.scss 文件有任何修改,只要我重新保存了修改的文件,命令終端就能監測,并重新編譯出文件

    GUI 界面編譯工具

    目前較為流行的主要有:

    Koala (http://koala-app.com/)

    Compass.app(http://compass.kkbox.com/

    Scout(http://mhs.github.io/scout-app/

    CodeKit(https://incident57.com/codekit/index.html

    Prepros(https://prepros.io/

    相比之下,我比較推薦使用以下兩個:

    Koala (http://www.w3cplus.com/preprocessor/sass-gui-tool-koala.html)

    CodeKit (http://www.w3cplus.com/preprocessor/sass-gui-tool-codekit.html)

    sass自動化編譯

    1、Grunt 配置 Sass 編譯的示例代碼

    module.exports = function(grunt) {
        grunt.initConfig({
            pkg: grunt.file.readJSON('package.json'),
            sass: {
                dist: {
                    files: {
                        'style/style.css' : 'sass/style.scss'
                    }
                }
            },
            watch: {
                css: {
                    files: '**/*.scss',
                    tasks: ['sass']
                }
            }
        });
        grunt.loadNpmTasks('grunt-contrib-sass');
        grunt.loadNpmTasks('grunt-contrib-watch');
        grunt.registerTask('default',['watch']);
    }
    

    2、Gulp 配置 Sass 編譯的示例代碼

    var gulp = require('gulp');
    var sass = require('gulp-sass');
    
    gulp.task('sass', function () {
        gulp.src('./scss/*.scss')
            .pipe(sass())
            .pipe(gulp.dest('./css'));
    });
    
    gulp.task('watch', function() {
        gulp.watch('scss/*.scss', ['sass']);
    });
    
    gulp.task('default', ['sass','watch']);
    

    sass的編譯錯誤

    • 在創建 Sass 文件時,就需要將文件編碼設置為“utf-8”。

    • 建議在項目中文件命名或者文件目錄命名不要使用中文字符。

    sass不同樣式風格的輸出方法(scss->css)

    嵌套輸出方式 nested

    展開輸出方式 expanded

    緊湊輸出方式 compact

    壓縮輸出方式 compressed


    嵌套輸出方式

    在編譯的時候帶上參數“ –style nested”:

    sass --watch test.scss:test.css --style nested
    

    編譯出來的 CSS 樣式風格:

    nav ul {
    margin: 0;
    padding: 0;
    list-style: none; }

    展開輸出方式

    在編譯的時候帶上參數“ –style expanded”:

    sass --watch test.scss:test.css --style expanded
    

    這個輸出的 CSS 樣式風格和 nested 類似,只是大括號在另起一行

    編譯出來:

    nav ul {
    margin: 0;
    padding: 0;
    list-style: none;
    }

    緊湊輸出方式

    在編譯的時候帶上參數“ –style compact”:

    sass --watch test.scss:test.css --style compact
    

    該方式適合那些喜歡單行 CSS 樣式格式的朋友,編譯后的代碼如下:

    nav ul { margin: 0; padding: 0; list-style: none; }

    壓縮輸出方式 compressed

    在編譯的時候帶上參數“ –style compressed”:

    sass --watch test.scss:test.css --style compressed
    

    壓縮輸出方式會去掉注釋及空格。也就是壓縮好的 CSS 代碼樣式風格:

    nav ul{margin:0;padding:0;list-style:none}nav li{display:inline-block}

    scss基礎特性

    聲明變量

    3

    普通變量與默認變量

    • 普通變量

    定義之后可以在全局范圍內使用。

    $fontSize: 12px;
    body{
        font-size:$fontSize;
    }
    
    • 默認變量
      sass 的默認變量僅需要在值后面加上 !default 即可。
    $baseLineHeight:1.5 !default;
    body{
        line-height: $baseLineHeight; 
    }

    sass 的默認變量一般是用來設置默認值,然后根據需求來覆蓋的,覆蓋的方式也很簡單,只需要在默認變量之前重新聲明下變量即可。(默認變量的價值在進行組件化開發的時候會非常有用。)

    $baseLineHeight: 2;
    $baseLineHeight: 1.5 !default;
    body{
        line-height: $baseLineHeight; 
    }
    

    全局變量與局部變量

    //SCSS
    $color: orange !default;
        //定義全局變量(在選擇器、函數、混合宏...的外面定義的變量為全局變量)
    .block {
      color: $color;//調用全局變量
    }
    em {
      $color: red;//定義局部變量
      a {
        color: $color;//調用局部變量
      }
    }
    span {
      color: $color;//調用全局變量
    }

    概念:

    1、全局變量就是定義在元素外面的變量

    2、基本上,局部變量只會在局部范圍內覆蓋全局變量。

    什么時候聲明變量?

    該值至少重復出現了兩次;
    該值至少可能會被更新一次;
    該值所有的表現都與變量有關(非巧合)。

    選擇器嵌套

    Sass 的嵌套分為三種:

    • 選擇器嵌套

    • 屬性嵌套

    • 偽類嵌套


    選擇器嵌套

    想選中 header 中的 a 標簽,在寫 CSS 會這樣寫:

    nav a {
      color:red;
    }
    
    header nav a {
      color:green;
    }
    

    那么在 Sass 中,就可以使用選擇器的嵌套來實現:

    nav {
      a {
        color: red;
    
        header & {
          color:green;
        }
      }  
    }
    

    屬性嵌套

    CSS 有一些屬性前綴相同,只是后綴不一樣,比如:border-top/border-right,與這個類似的還有 margin、padding、font 等屬性。假設你的樣式中用到了:

    .box {
        border-top: 1px solid red;
        border-bottom: 1px solid green;
    }
    

    在 Sass 中我們可以這樣寫:

    .box {
      border: {
       top: 1px solid red;
       bottom: 1px solid green;
      }
    }
    

    偽類嵌套

    .clearfix{
    &:before,
    &:after {
        content:"";
        display: table;
      }
    &:after {
        clear:both;
        overflow: hidden;
      }
    }
    

    編譯出來的 CSS:

    clearfix:before, .clearfix:after {
      content: "";
      display: table;
    }
    .clearfix:after {
      clear: both;
      overflow: hidden;
    }
    

    缺點:代碼難以閱讀; 語句冗長,引用頻繁

    混合宏

    聲明混合宏

    1、不帶參數混合宏:

    在 Sass 中,使用“@mixin”來聲明一個混合宏。如:

    @mixin border-radius{
        -webkit-border-radius: 5px;
        border-radius: 5px;
    }
    

    其中 @mixin 是用來聲明混合宏的關鍵詞,有點類似 CSS 中的 @media、@font-face 一樣。border-radius 是混合宏的名稱。大括號里面是復用的樣式代碼。

    2、帶參數混合宏:

    除了聲明一個不帶參數的混合宏之外,還可以在定義混合宏時帶有參數,如:

    @mixin border-radius($radius:5px){
        -webkit-border-radius: $radius;
        border-radius: $radius;
    }
    

    調用混合宏

    例如在你的樣式中定義了一個圓角的混合宏“border-radius”:

    @mixin border-radius{
        -webkit-border-radius: 3px;
        border-radius: 3px;
    }
    

    在一個按鈕中要調用定義好的混合宏“border-radius”,可以這樣使用:

    button {
        @include border-radius;
    }
    

    這個時候編譯出來的 CSS:

    button {
      -webkit-border-radius: 3px;
      border-radius: 3px;
    }
    

    混合宏的參數–傳一個不帶值的參數

    聲明:
    @mixin border-radius(radius){  
          -webkit-border-radius:
    radius;
    border-radius: $radius;
    }
    調用:
    .box {
    @include border-radius(3px);
    }

    混合宏的參數–傳一個不帶值的參數

    @mixin center($width,$height){
        width: $width;
        height: $height;.box-center {
      @include center(500px,300px);
    }
    

    sass擴展/繼承

    .btn {
      border: 1px solid #ccc;
      padding: 6px 10px;
      font-size: 14px;
    }
    
    .btn-primary {
      background-color: #f36;
      color: #fff;
      @extend .btn;
    }

    占位符

    這段代碼未被調用,不占位

    //SCSS
    %mt5 {
      margin-top: 5px;
    }
    %pt5{
      padding-top: 5px;
    }
    .btn {
      @extend %mt5;
      @extend %pt5;
    }
    
    .block {
      @extend %mt5;
    
      span {
        @extend %pt5;
      }
    }
    編譯出來的CSS
    
    //CSS
    .btn, .block {
      margin-top: 5px;
    }
    
    .btn, .block span {
      padding-top: 5px;
    }

    比較

    4


    [Sass]插值#{}

    $properties: (margin, padding);
    @mixin set-value($side, $value) {
        @each $prop in $properties {
            #{$prop}-#{$side}: $value;
        }
    }
    .login-box {
        @include set-value(top, 14px);
    }
    .login-box {
        margin-top: 14px;
        padding-top: 14px;
    }

    注釋

    1、類似 CSS 的注釋方式,使用 ”/* ”開頭,結屬使用 ”*/ ”(顯示)

    2、類似 JavaScript 的注釋方式,使用“//”(不會顯示)


    sass數據類型

    在 Sass 中包含以下幾種數據類型:

    數字: 如,1、 2、 13、 10px;

    字符串:有引號字符串或無引號字符串,如,”foo”、 ‘bar’、 baz;

    顏色:如,blue、 #04a3f9、 rgba(255,0,0,0.5);

    布爾型:如,true、 false; 空值:如,null;

    值列表:用空格或者逗號分開,如,1.5em 1em 0 2em 、 Helvetica, Arial, sans-serif。

    注意:
    SassScript 也支持其他 CSS 屬性值(property value),比如 Unicode 范圍,或 !important聲明。然而,Sass不會特殊對待這些屬性值,一律視為無引號字符串 (unquoted strings)。


    sass字符串

    SassScript 支持 CSS 的兩種字符串類型:

    • 有引號字符串 (quoted strings),如 “Lucida Grande” 、’http://sass-lang.com‘;
    • 無引號字符串 (unquoted strings),如 sans-serifbold。

    在編譯 CSS 文件時不會改變其類型。

    只有一種情況例外,使用#{}插值語句(interpolation)時,有引號字符串將被編譯為無引號字符串


    sass直列表

    1.獨立的值也被視為值列表——只包含一個值的值列表。

    2.Sass列表函數(Sass list functions)賦予了值列表更多功能(Sass進級會有講解):

    nth函數(nth function) 可以直接訪問值列表中的某一項;

    join函數(join function) 可以將多個值列表連結在一起;

    append函數(append function) 可以在值列表中添加值;

    @each規則(@each rule) 則能夠給值列表中的每個項目添加樣式。

    3.

    • 值列表中可以再包含值列表,比如 1px 2px, 5px 6px 是包含 1px 2px 與 5px 6px
      兩個值列表的值列表。如果內外兩層值列表使用相同的分隔方式,要用圓括號包裹內層,所以也可以寫成 (1px 2px) (5px 6px)。

    • 當值列表被編譯為 CSS 時,Sass 不會添加任何圓括號,因為 CSS 不允許這樣做。(1px 2px) (5px 6px)與 1px 2px 5px 6px 在編譯后的 CSS 文件中是一樣的

    • 它們在 Sass 文件中卻有不同的意義,前者是包含兩個值列表的值列表,而后者是包含四個值的值列表。

    • 可以用 () 表示空的列表,這樣不可以直接編譯成 CSS,比如編譯 font-family: ()時,Sass 將會報錯。

    如果值列表中包含空的值列表或空值,編譯時將清除空值,比如 1px 2px () 3px 或 1px 2px null 3px。


    scss的運算

    1.加法 30px+20px(同類型)

    2.減法 10px-2px(同類型)

    3.乘法 兩個值單位相同時,只需要為一個數值提供單位即可。(同類型)

    4.除法

     ”/  ”符號被當作除法運算符時有以下幾種情況:
    

    ? 如果數值或它的任意部分是存儲在一個變量中或是函數的返回值。

    ? 如果數值被圓括號包圍。

    ? 如果數值是另一個數學表達式的一部分。

    5.

    //SCSS
    p {
      font: 10px/8px;             // 純 CSS,不是除法運算
      $width: 1000px;
      width: $width/2;            // 使用了變量,是除法運算
      width: round(1.5)/2;        // 使用了函數,是除法運算
      height: (500px/2);          // 使用了圓括號,是除法運算
      margin-left: 5px + 8px/2px; // 使用了加(+)號,是除法運算
    }

    6.

     width: (1000px / 100px);//得到無意義數值
    

    7.所有算數運算都支持顏色值,并且是分段運算的。也就是說,紅、綠和藍各顏色分段單獨進行運算。如:

    p {
      color: #010203 + #040506;
    }

    算數運算也能將數字和顏色值 一起運算,同樣也是分段運算的。如:

    p {
      color: #010203 * 2;
    }

    7.17記

    7.18記

    排序算法集錦


    冒泡排序

    1. 算法原理:利用相鄰元素進行比較,使元素如同氣泡一樣不斷移動最終使得最大的元素不斷的向右移動直到適當的位置位置。針對所有的元素重復以上的步驟,除了最后一個。持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較

      兩兩開始比較,從而將最大元素冒泡到最后;進而最后元素在每輪中不必參與比較

    2. 代碼

    function sort(arr){
        for(var i=0;i<arr.length;i++){
            for(var j=0;j<arr.length-i-1;i++){
                if(arr[i]>arr[i+1]){
                    var t=arr[i];
                    arr[i]=arr[i+1];
                    arr[i+1]=t; 
                }
            }   
        }
    }

    3.時間復雜度:冒泡排序的最壞時間復雜度為O(n2)。

    空間復雜度:o(1)

    4.是否借助外部空間的判斷: 沒有借助第二個數組。

    5.穩定排序

    6.代碼優化:如果在哪一輪比較中,沒有元素參與交換,則表示排序已經完成。跳出循環

    function sort(arr){
        for(var i=0;i<arr.length;i++){
            var tag=1;
            for(var j=0;j<arr.length-i-1;j++){
                if(arr[j]>arr[j+1]){
                    var t=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=t;
                    tag=0;  
                }
            }
            if(tag){
                break;
            }   
        }
        return arr;
    }
    
    時間復雜度o(n)~o(n2)

    快速排序

    1.快速排序是對冒泡排序的一種改進。它的基本思想是:通過一躺排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一不部分的所有數據都要小,然后再按次方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

     **思路一** 
    
    假設要排序的數組是A[1]……A[N],首先任意選取一個數據(通常選用第一個數據)作為關鍵數據,然后將所有比它小的數都放到它前面,所有比它大的數都放到它后面,這個過程稱為一躺快速排序。一躺快速排序的算法是:
    

    1)、設置兩個變量I、J,排序開始的時候I:=1,J:=N;

    2)以第一個數組元素作為關鍵數據,賦值給X,即X:=A[1];

    3)、從J開始向前搜索,即由后開始向前搜索(J:=J-1),找到第一個小于X的值,兩者交換;

    4)、從I開始向后搜索,即由前開始向后搜索(I:=I+1),找到第一個大于X的值,兩者交換;

    5)、重復第3、4步,直到I=J;

    當一趟快速排序截止后,返回剛才的基準元素。然后把基準元素的左邊數組和右邊數組分別作為新數組,分別進行第一趟的排序算法。
    

    代碼:

    function quickSort(arr,i,j){
            if(i<j){
                //傳入基準角標,進行第一輪排序
                var s=partition(arr,i,j);
                //0~基準-1
                quickSort(arr,i,s-1);
                //基準+1~末元素
                quickSort(arr,s+1,j);
            }
            return arr;
    
            function partition(arr,i,j){
                var std=arr[i];
                while(i<j){
                    //1.j--,找到后交換
                    while(i<j&&arr[j]>std){
                        j--;
                    }
                    swap(arr,i,j);
                    //2.i++找到后交換
                    while(i<j&&arr[i]<std){
                        i++;
                    }
                    swap(arr,i,j);
                }//返回基準的角標
                return i;
            }
    
            function swap(arr,i,j){
                var temp=arr[i];
                arr[i]=arr[j];
                arr[j]=temp;
            }
        }
        var arr=[2,5,3,1,7,8,78,89];
        quickSort(arr,0,arr.length-1);
        console.log(arr);

    思路二

    i>同時從頭向后遍歷i,叢尾向前遍歷j;遇到順序不對的i、j,停下來。

    ii>如果i

    function quickSort(arr,i,j){//arr 0 n-1
            if(i<j){
                var s=partition(arr,i,j);
                quickSort(arr,i,s-1);
                quickSort(arr,s+1,j);
            }
            function partition(arr,i,j){
                var std=arr[i];
                while(i<j){
                    while(i<j&&arr[j]>std){//先j--
                        j--;
                    }
                    while(i<j&&arr[i]<std){//再i--
                        i++;
                    }
                    swap(arr,i,j);//交換a[i]、a[j]
                }
                return i;
            }
    
            function swap(arr,i,j){
                var temp=arr[i];
                arr[i]=arr[j];
                arr[j]=temp;
            }
        }
        var arr=[2,1,8,5,4,3,45,12,51];
        quickSort(arr,0,arr.length-1);
        console.log(arr);

    2.優化

    將基準選在數組二分位置(分治遞歸)

    代碼:

    、、、、、、、、、尚未寫出來

    思路:
    1.子區間遞歸二分

    2.子區間合并,并排序(將兩個升序合并成一個升序)

    三.最差情況

    當數組為降序有序,則需要兩兩比較,類似于冒泡排序。時間復雜度o(n的平方)

    四.針對思路二

    時間復雜度: 最差o(n2) 平均o(nlog2n)

    空間復雜度: O(log2n)~O(n)

    五.快排是極其不穩定

    分析:例如1,2,3,2,5    
    
    將第一個2選作基準,從后往前遍歷j,則j停在第二個2.    
    
    此時將基準交換,即將兩個2的位置互換。
    

    直接選擇


    1.思想:

    i>先把整個數組作為一個無序數組, 然后每次從無序數組中選出一個最小的元素,插入到有序區間的結尾

    ii>等到無序區間只剩一個元素的時候,不需要比較,因為它就是最大元素。

    2.代碼

        var arr=[3,2,5,8,4,7,6,9];
        function selectionSort(arr) {
            var minIndex, temp;
            for (var i = 0; i < arr.length- 1; i++) {   //外層循環控制趟數,最后一個數字不用排
                minIndex = i;
                for (var j = i + 1; j < arr.length; j++) {  //內層循環尋找最小數
                    if (arr[j] < arr[minIndex]) {     // 尋找最小的數
                        minIndex = j;                 // 將最小數的索引保存
                    }
                }
                //內層循環結束之后minIndex為最小值,和i交換(i:無序區間第一個值)
                temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
            return arr;
        }
    
        console.log(selectionSort(arr));
    

    3.時間復雜度:

    fn=1+2+….+(n-2)+(n-1)=n2/2-n+1/2 根據推導方法為O(n平方)

    最佳情況:T(n) = O(n2) 最差情況:T(n) = O(n2) 平均情況:T(n) = O(n2)

    4.穩定性:不穩定 舉例:arr=[5,8,5,2,9]

    直接插入

    1.思想:

    i>將數組第一個元素作為有序區間元素,將剩余元素作為無序區間元素

    ii>然后每次取出無序區間的第一個值,從后往前遍歷有序區間,把它插入到有序區間里合適的位置

    2.代碼

    var arr=[3,2,5,8,4,7,6,9];

        function insertSort(a){
                var i;
                var j;
                var x;
                for(i=0;i<arr.length;i++){
                    x=a[i];
    //              for(j=i-1;j>-1&&a[j]>x;a[j+1]=a[j],j--);
                    for(j=i-1;j>-1;j--){
                        if(a[j]>x){
                            a[j+1]=a[j];
                        }
                        else  break;
                    }
                    a[j+1]=x;
                }
                return a;
            }
    
            arr=insertSort(arr);
            console.log(arr);
    

    3.時間復雜度: 計算循環嵌套的執行次數 (這種情況相當于等差數列求和)

    fn=n+(n-1)+…..+1=n2/2+n/2 按照法則復雜度為O(n平方)

    最佳情況:輸入數組按升序排列。T(n) = O(n)
    最壞情況:輸入數組按降序排列。T(n) = O(n2)
    平均情況:T(n) = O(n2)
    

    4.穩定性:穩定

    歸并排序

    1.思想:

    i>二分遞歸

    ii>自底向上,合并排序(利用unshift)

    2.代碼

    var arr=[3,2,5,8,4,7,6,9];
        function mergeSort(arr) {  // 采用自上而下的遞歸方法
            var len = arr.length;
            if(len < 2) {
                return arr;
            }
            var middle = Math.floor(len / 2),
                left = arr.slice(0, middle),//數組截取,左閉右開
                right = arr.slice(middle);
                //先不斷調左二分遞歸,再不斷調右二分遞歸。   最后將完全二分后的數組,傳給merge
            return merge(mergeSort(left), mergeSort(right));    //合并兩個有序數組
        }
    
        function merge(left, right)
        {
            var result = [];
            //合并兩個有序數組
            while (left.length && right.length) {
                if (left[0] <= right[0]) {//若果 左數組第一個值<=右數組第一個值
                    //借助一個新的數組,刪除方法的返回值剛好是被刪除數組的第一個值
                    result.push(left.shift());//將左數組第一個值壓入第三數組
                } else {
                    result.push(right.shift());//將右數組第一個值壓入第三數組
                }
            }
            //循環結束的結果有兩種,一種是left先結束,一種是right先結束
            while (left.length)  //right先結束,將剩余元素全部加入到第三方
                result.push(left.shift());
    
            while (right.length)  //left先結束,將剩余元素加入到第三方
                result.push(right.shift());
    
            return result;
        }
        alert(mergeSort(arr));
    

    3.時間復雜度:o(nlog2n)

    4.穩定性:穩定

    5.缺點:需要輔助空間


    點評:

    數值較少:冒泡(穩定、簡單)

    時間性能好:快排

    堆排序:節省空間,排序快;見堆浪費時間

    排序方法 時間復雜度(平均) 時間復雜度(最壞) 時間復雜度(最好) 空間復雜度 穩定性 復雜性
    直接插入 o(n2) o(n2) o(n) o(1) 穩定性 簡單
    希爾排序 o(nlog2n) o(n2) o(n) o(1) 不穩定 較復雜
    直接選擇 o(n2) o(n2) o(n2) o(1) 不穩定 簡單
    堆排序 o(nlog2n) o(nlog2n) o(nlog2n) o(1) 不穩定 較復雜
    冒泡 o(n2) o(n2) o(n) o(1) 穩定 簡單
    快排 o(nlog2n) o(n2) o(nlog2n) o(nlog2n) 不穩定 較復雜
    歸并 o(nlog2n) o(nlog2n) o(nlog2n) o(n) 穩定 較復雜
    基數 o( d(n+r) ) o( d(n+r) ) o( d(n+r) ) o(n+r) 穩定 較復雜

    JQ動畫


    animate() :

    //第一個參數 : {} 運動的值和屬性–》終點(必須參數)

    //第二個參數 : 時間(運動快慢的) 默認 : 400–》開始到結束

    //第三個參數 : 運動形式 只有兩種運動形式 ( 默認 : swing(慢快慢) linear(勻速) )

    //第四個參數 : 回調函數

    $(function(){
        $('#div1').click(function(){
            $(this).animate({width : 300 , height : 300} , 4000 , 'linear',function(){alert(123);});
        });
    });
    $('#div1').stop();   //默認 : 只會阻止當前運動
    
    $('#div1').stop(true); //終止當前動畫,并阻止后續的運動--暫停效果
    
    .$('#div1').stop(true,true);
    //可以立即停止到指定的目標點(讓當前動畫立即切換到終點,并終止后續動畫)
    
    $('#div1').finish();//立即停止到所有動畫指定的目標點
    
    if(!$(element).is(":animated")){} else{}//判斷當前元素是否在執行jq動畫animate
    
    delay()  延時(隔多久之后,再執行)
    .animate({width : 300 , height : 300} , 3000 , 'linear').delay(2000)
    

    Velocity.js動畫

    1.$element.velocity(“scroll”, 1000);

    //參數一:對象(json)或者命令(字符串) 1000:時間

    這個方法將使瀏覽器滾動到選定的元素(jQuery對象,$element),過渡時間為1000ms。

    僅使用jQuery實現這個功能也會很復雜,而且將用到多個函數操作。

    Velocity允許你設置窗口滾動后元素上邊緣距窗口上邊緣的距離,你只需設置offset選項即可完成(向上為負,向下為正):

    $element.velocity(“scroll”, { duration: 1000, offset: “-100px” });

    實例:樓層監聽

    2.讓動畫回到執行前狀態

    $element.velocity({ width: "100px" }, 400); 
    $element.velocity("reverse");

    3.Velocity 接收一個或更多的參數,只有第一個參數是是強制要求的,它可以是一個命令(就像上面例子中的”scroll”),或者是由CSS屬性值組成的對象(這些值是動畫的目標值):

    $element.velocity({ opacity: “0.5”, left: “500px”});

    4.第二個參數是由動畫附加選項組成的對象,比如過渡時長,張弛度,延時還有回調函數:

    $element.velocity(
        { left: "500px" }, 
        { delay: 1000, 
        duration :500,
        easing:[ 1000, 40 ],
        complete:function(){alert(123)}
    });

    easing的默認值(可以系統寫好,也可以自己添加)是swing,duration的默認值是400ms,其他的屬性則是可選的。

    復合屬性的寫法:{paddingLeft:”10px”,paddingTop:”15px”,….}

    5.
    easing(緩動)的取值:

    “ease-in-out” 動畫一開始要逐漸加速最后要逐漸減速。

    “ease-in” 動畫在一開始就達到加速的目的,然后一直到動畫結束

    “ease-out”動畫以恒定速度開始一段時間,然后在動畫結束前減速

    6.張尺度和摩擦系數

    $element.velocity({ width: “100px”}, {easing:[ 500, 20 ]})

    較高的張弛度將提高整體速度和動畫的反彈力度,較小的摩擦系數將提高動畫結
    束時的速度。

    7.開始結束

    begin設置的函數會在動畫開始前觸發。與之相反,為complete設置的函數會在動畫完成時調用。

    8.loop(循環)

    $("div").velocity({ 
        property1: value1,
        property2: value2
    }, {
        /* Default options */
        duration: 400,
        easing: "swing",
        complete: null,
        loop: false
    });
    

    9.支持鏈式調用

    10.支持顏色漸變(要求顏色是十六進制字符串)

    $element.velocity({ 
        borderColor: "#ffff00", //黃色
        backgroundColorAlpha: 0.6,//背景顏色透明度變到60% 
        colorBlue: 200 //rgb 方式 (rgb中的blue:200)
    });(過渡效果)
    

    11.支持2d,3d的擴大,平移,旋轉等

    translateX: 從左向右沿x軸移動元素 (正:右)
    translateY: 從上到下沿y軸移動元素 (正:下)
    rotateZ: 關于z軸旋轉元素 (2d旋轉)   (z:眼睛垂直看屏幕視線)
    rotateX: 關于x軸旋轉元素(看起來由里向外) (3d旋轉)
    rotateY: 關于y軸旋轉元素(由左到右) (3d旋轉)
    scaleX: 成倍數改變元素寬度 
    scaleY: 成倍數改變元素高度 
    如:$element.velocity({ 
    rotateZ: “180deg", scaleX: 2.0});
    

    13.停止+動畫序列(PPT)

    14.預定義動畫(例如:fade等,見庫)


    總結

    一.過渡動畫

    如何在項目中正確、熟練地應用transition動畫?
    

    第一步:在目標元素的樣式聲明中定義元素的初始狀態,然后在同一聲明中用 transition 屬性配置動畫的各種參數。

    可定義的參數有:

    • transition-property:規定對哪個屬性進行過渡。
    • transition-duration:定義過渡的時間,默認是0。
    • transition-timing-function:定義過渡動畫的緩動效果,如淡入、淡出等。

      linear規定以相同速度開始至結束的過渡效果(等于 cubic-bezier(0,0,1,1))。

      ease(默認值)規定慢速開始,然后變快,然后慢速結束的過渡效果(cubic-bezier(0.25,0.1,0.25,1))。

      ease-in 規定以慢速開始的過渡效果(等于 cubic-bezier(0.42,0,1,1))。

      ease-out規定以慢速結束的過渡效果(等于 cubic-bezier(0,0,0.58,1))。

      ease-in-out規定以慢速開始和結束的過渡效果(等于cubic-bezier(0.42,0,0.58,1))。

      cubic-bezier(n,n,n,n) 在 cubic-bezier 函數中定義自己的值。可能的值是 0 至 1 之間的數值。

      transition-delay:規定過渡效果的延遲時間,即在過了這個時間后才開始動畫,默認是0。

      注意:要在同一代碼塊中定義元素初始狀態(樣式),添加transition屬性。
      如果想要同時過渡多個屬性,可以用逗號隔開。

    div{transition:width 3s,height 3s; }
    

    第二步:改變元素的狀態。

    為目標元素添加偽類或添加聲明了最終狀態的類

    注意:單純的代碼不會觸發任何過渡操作,需要通過用戶的行為(如點擊,懸浮等)觸發。

    可觸發的方式有::hover :focus :checked 媒體查詢觸發 JavaScript觸發。

    img {
        height:15px;  /*初始值*/
        width:15px;
        transition:1s 1s height; /*過渡*/
    }
    img:hover {
        height:450px; /*最終值*/
        width:450px;
    }
    

    過渡動畫的局限性
    (1)transition需要事件觸發,所以沒法在網頁加載時自動發生。

    (2)transition是一次性的,不能重復發生,除非一再觸發。

    (3)transition只能定義開始狀態和結束狀態,不能定義中間狀態,也就是說只有兩個狀態。

    二、關鍵幀動畫

    第一步:通過類似Flash動畫中的幀來聲明一個動畫。使用了一個關鍵字 @keyframes 來定義動畫。具體格式為:

    @keyframes 動畫名稱 {    
        時間點 {元素狀態}    
        時間點 {元素狀態}   
    …}
    

    一般來說,0%和100%這兩個關鍵幀是必須要定義的。0%可以由from代替,100%可以由to代替。

    第二步:在目標元素的樣式聲明中使用animation屬性調用關鍵幀聲明的動畫。

    可設置的參數:

    animation-name:none為默認值,將沒有任何動畫效果,其可以用來覆蓋任何動畫。

    animation-duration:默認值為0s,意味著動畫周期為0s,也就是沒有任何動畫效果。

    animation-timing-function:與transition-timing-function一樣。

    animation-delay:在開始執行動畫時需要等待的時間。

    animation-iteration-count:定義動畫的播放次數,默認為1,如果為infinite,則無限次循環播放。

    animation-direction:默認為nomal,每次循環都是向前播放,(0-100)。另一個值為alternate,動畫播放為偶數次則向前播放,如果為基數詞就反方向播放。

    animation-state:默認為running,播放,paused,暫停。

    animation-fill-mode:定義動畫開始之前和結束之后發生的操作。
    參數如下:

    none(默認值),動畫結束時回到動畫沒開始時的狀態;
    
    forwards,動畫結束后繼續應用最后關鍵幀的位置,即保存在結束狀態;
    
    backwards,讓動畫回到第一幀的狀態;
    
    both:輪流應用forwards和backwards規則;
    

    注意:
    animation屬性到目前為止得到了大多數瀏覽器的支持,但是,需要添加瀏覽器前綴!另外,@keyframes必須要加webkit前綴。

    div:hover {
        -webkit-animation:1s changeColor; /*調用動畫*/
        animation:1s changeColor;
    }
    @-webkit-keyframes changeColor { /*聲明動畫*/
        0% {background:#c00;}
        50%{background:orange;}
        100%{background:yellowgreen;}
    }
    @keyframes changeColor {
        0%{background:#c00;}
        50%{background:orange;}
        100%{background:yellowgreen;}
    }

    區別
    animation屬性類似于transition,他們都是隨著時間改變元素的屬性值,其主要區別在于:

    transition需要觸發一個事件才會隨著時間改變其CSS屬性;

    animation在不需要觸發任何事件的情況下,也可以顯式的隨時間變化來改變元素CSS屬性,達到一種動畫的效果

    三、animate.css動畫庫

    大致效果有:

    bounce(跳動)、flash(閃光)、pulse(脈沖)、rubber
    band(橡皮筋)、shake(抖動)、swing(搖擺)、wobble(搖擺不定)
    fade(淡入或淡出)
    flip(翻轉)
    rotate(旋轉)
    slide(滑動)
    zoom(放大或縮小)
    

    如何使用gulp構建符合我們需求的animate.min.css?

    第一步:將整個animate.css項目下載下來,作為生產環境的依賴:

    npm install animate.css --save
    

    第二步:進入animate.css項目根目錄下:

    $cd path/to/animate.css/
    

    第三步:加載dev依賴:

    npm install
    

    第四步:根據實際需要修改animate-config.json文件:

    例如:我們只需要這兩個動畫效果:bounceIn和bounceOut。

    {
        "bouncing_entrances": [
            "bounceIn"
        ],
        "bouncing_exits": [
            "bounceOut"
        ]
    }

    最后一步:進入animate.css項目下,運行gulp任務:gulp default,生成新的animate.min.css覆蓋默認的animate.min.css

    (2)你想要哪個元素進行動畫,就給那個元素添加上animated類 以及特定的動畫類名,animated是每個要進行動畫的元素都必須要添加的類。

    <script>
    $("box").addClass('animated shake');
    </script>
    

    運行一段時間后移除:

    setTimeout(function(){
        $('.box').removeClass('shake');
    },3000)
    

    rem

    再看看


    1.概述:rem 的官方定義『The font size of the root element.』,即以根節點的字體大小作為基準值進行長度計算。一般認為網頁中的根節點是 html 元素,所以采用的方式也是通過設置 html 元素的 font-size 來做屏幕適配。

    即通過實際稿、設計稿的等比縮放來實現

    采用等比縮放的方式 —— 獲得目標屏幕寬度和設計稿寬度的比,作為 rem 的基值(縮放系數),設置為html標簽的字體大小。

    2.以設計稿的寬度為640px,即:designWidth = 640,同時設定在640px屏寬下 1rem=100px ,即:rem2px = 100。


    • 方案一(不同屏寬下實現)
        @media screen and (min-width: 320px) {html{font-size:50px;}}
        @media screen and (min-width: 360px) {html{font-size:56.25px;}}
        @media screen and (min-width: 375px) {html{font-size:58.59375px;}}
        @media screen and (min-width: 400px) {html{font-size:62.5px;}}
        @media screen and (min-width: 414px) {html{font-size:64.6875px;}}
        @media screen and (min-width: 440px) {html{font-size:68.75px;}}
        @media screen and (min-width: 480px) {html{font-size:75px;}}
        @media screen and (min-width: 520px) {html{font-size:81.25px;}}
        @media screen and (min-width: 560px) {html{font-size:87.5px;}}
        @media screen and (min-width: 600px) {html{font-size:93.75px;}}
        @media screen and (min-width: 640px) {html{font-size:100px;}}
        @media screen and (min-width: 680px) {html{font-size:106.25px;}}
        @media screen and (min-width: 720px) {html{font-size:112.5px;}}
        @media screen and (min-width: 760px) {html{font-size:118.75px;}}
        @media screen and (min-width: 800px) {html{font-size:125px;}}
        @media screen and (min-width: 960px) {html{font-size:150px;}}

    • 方案二(html實際字體 = 默認字體16px * font-size)
    @media screen and (min-width: 320px) {html{font-size:312.5%;}}
    @media screen and (min-width: 360px) {html{font-size:351.5625%;}}
    @media screen and (min-width: 375px) {html{font-size:366.211%;}}
    @media screen and (min-width: 400px) {html{font-size:390.625%;}}
    @media screen and (min-width: 414px) {html{font-size:404.2969%;}}
    @media screen and (min-width: 440px) {html{font-size:429.6875%;}}
    @media screen and (min-width: 480px) {html{font-size:468.75%;}}
    @media screen and (min-width: 520px) {html{font-size:507.8125%;}}
    @media screen and (min-width: 560px) {html{font-size:546.875%;}}
    @media screen and (min-width: 600px) {html{font-size:585.9375%;}}
    @media screen and (min-width: 640px) {html{font-size:625%;}}
    @media screen and (min-width: 680px) {html{font-size:664.0625%;}}
    @media screen and (min-width: 720px) {html{font-size:703.125%;}}
    @media screen and (min-width: 760px) {html{font-size:742.1875%;}}
    @media screen and (min-width: 800px) {html{font-size:781.25%;}}
    @media screen and (min-width: 960px) {html{font-size:937.5%;}}

    例如:實際屏幕640px,實際字體=16px*625%=100px


    • 方案三

        var designWidth = 640, rem2px = 100;
        document.documentElement.style.fontSize = ((window.innerWidth / designWidth) * rem2px) + 'px';

    思路:實際寬度/設計寬度 = 實際字體 / 100


    • 方案四
    var designWidth = 640, rem2px = 100;
    document.documentElement.style.fontSize = 
      ((((window.innerWidth / designWidth) * rem2px) / 16) * 100) + '%';

    思路:實際寬度 / 設計寬度= (16*實際字體) / 100

    實際案例:

    假設屏寬=360px,設定默認瀏覽器字體=18px,

    方案三

    則根字體=實際寬度 / 設計寬度 * 100=360/640*100=56.25px(目標字體)

    設計后根字體=65px????

    所以,目標 6.4*rem = 6.4 * 56.25=360px

    然而,實際 6.4*rem = 6.4 * 65=414px

    方案四

    利用方案四公式,html font-size = 360/640*100/16 = 351.5625%

    即:1rem = 1 * htmlFontSize * defaultFontSize = 351.5625% * 18px = 63.28125px


    • 改進方案(動態獲取瀏覽器字體大小,進而不需寫死默認字體16px等)
    var designWidth = 640, rem2px = 100;
    var h = document.getElementsByTagName('html')[0];
    
    //通過getComputedStyle(h, null).getPropertyValue('font-size')獲取瀏覽器默認字體
    
    var htmlFontSize = parseFloat(window.getComputedStyle(h, null).getPropertyValue('font-size'));
    
    document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / htmlFontSize * 100 + '%';

    問題:

    html的font-size使用的是: getPropertyValue(‘font-size’) ,而 1rem 使用的是 getPropertyValue(‘width’),偏差出在計算 font-size 的時候瀏覽器進行了四舍五入。????


    • 再次改進
    var designWidth = 640, rem2px = 100;
    
    //創建一個div元素,方便getComputedStyle(d, null)獲取其寬度,假定寬度=1rem
    
    var d = window.document.createElement('div');
    
    d.style.width = '1rem'; //1rem=1 html font-size
    
    d.style.display = "none";//保證在頁面不占位
    
    var head = window.document.getElementsByTagName('head')[0];
    
    head.appendChild(d);//插入頭部,將不在頁面顯示
    
    var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
    
    //語法:parseInt( window.getComputedStyle(element,null)[attr]) )
    
    優點:可以獲取元素的最終樣式,包括瀏覽器的默認值
    ,并且可被用于檢測元素的樣式(包括style內部樣式和外部樣式)
    
    注意:
    
    不能獲取復合樣式如background屬性值,只能獲取單一樣式如background-color等;
    
    getComputedStyle獲取的值是只讀的;
    
    //style:只能獲取行間樣式
    
    d.remove();//獲取默認字體后,將剛生成的div鏟除掉
    
    document.documentElement.style.fontSize = 
      window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';  
    //通過該語句,更改根節點字體大小rem
    

    • 最終設計方案(考慮到了屏幕旋轉問題)
    function adapt(designWidth, rem2px){
      var d = window.document.createElement('div');
      d.style.width = '1rem';
      d.style.display = "none";
      var head = window.document.getElementsByTagName('head')[0];
      head.appendChild(d);
      var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
      d.remove();
      document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';
      var st = document.createElement('style');
      var portrait = "@media screen and (min-width: "+window.innerWidth+"px) {html{font-size:"+ ((window.innerWidth/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}";
      var landscape = "@media screen and (min-width: "+window.innerHeight+"px) {html{font-size:"+ ((window.innerHeight/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}"
      st.innerHTML = portrait + landscape;
      head.appendChild(st);
      return defaultFontSize
    };
    var defaultFontSize = adapt(640, 100);

    7.19記

    this(默認ES6)

    參考: 你不知道的javascript上卷


    1.每一個函數的this是在調用時被綁定的,它取決于函數的調用位置。

    尋找函數的調用位置:分析調用棧(就是為了到達當前執行位置所調用的所有函數)---具體實現:插入斷點debugger;
    

    2.綁定規則


    1. 默認綁定

    function foo(){
        console.log(this.a);//全局變量a
    }
    
    var a=2;
    
    foo();//2
    function foo(){
        console.log(this.a);    
    }
    
    var a=2;
    
    (function(){
        'use strict';//對于foo,非嚴格模式
    
        foo();//2
    })();
    function foo(){
        'use strict';
        console.log(this.a);
    }
    var a=2;
    foo();//語法錯誤:this is undefined
    非嚴格模式下,默認綁定到全局對象
    
    嚴格模式下,this會綁定到undefined
    
    注:這里的是否嚴格,針對的是函數體
    

    2.隱式綁定

    要考慮到調用位置是否有上下文對象,或者是被某個對象擁有或包含—–即對象里所包含的某個值,是引用類型(函數名),指向了一個具體的函數。

    無論是直接在對象中定義,還是先定義再添加為引用屬性,嚴格來講,這個函數讀不屬于對象。

    思考1:

    function foo(){
        console.log(this.a);//隱式指向obj
    }
    
    var obj={
        a:2,
        foo:foo
    };
    
    obj.foo();//2

    隱式綁定規則會把函數中的this,綁定到這個上下文對象。

    思考2:

    function foo(){
        console.log(this.a);
    }
    
    var obj2={
        a:42,
        foo:foo
    };
    
    var obj1={
        a:2,
        obj2:obj2
    };
    
    obj1.obj2.foo();//42

    對象屬性引用鏈,只有最后一層在調用位置中起作用。

    思考3:

    function foo(){
        comsole.log(this.a);
    }
    
    var obj={
        a:2,
        foo:foo
    }
    
    var bar=obj.foo;//函數別名(bar 是obj.foo的一個引用)
    
    var a='global';//a是全局對象的屬性
    
    bar();//"global"

    注意存在函數引用時,引用的是函數本身,因此此時調用其實是一個不帶修飾的函數調用,故應用了默認綁定。

    思考4:

    function foo(){
        console.log(this.a);
    }
    
    function doFoo(fn){
        //fn引用的是foo
    
        fn();//調用位置
    }
    
    var obj={
        a:2,
        foo:foo
    }
    
    var a="golbal";//a是全局對象的屬性
    
    doFoo(obj.foo);//"global"

    上例中,obj.foo隱式指向obj;調用位置確定了它屬于全局對象,使用默認綁定規則

    存在參數傳遞時,若隱式綁定與調用位置存在沖突,以調用位置為主。

    思考5:

    function setTimeout(fn,delay){
        //等待delay毫秒
        fn();//調用位置
    }
    
    等效于:
    
    setTimeout(obj.foo,100)// obj.foo里,this指向全局

    3.顯式綁定

    1.使用call和appl方法。他們的第一個參數是一個對象,接著在調用函數時將其綁定到this.因為可以直接指定this的綁定對象,故稱之為顯式綁定。

    2.從this的綁定角度講,call(..)和apply(..)是一樣的,區別體現在其他參數上,暫不考慮這些。

    3.裝箱:調用call或apply時,如果你傳入一個原始值(字符串、布爾值、數字)來當作this的綁定對象,這個原始值將被轉化成它的對象形式。類似于new string(…)等。這被統稱為“裝箱”。

    顯示綁定的變種:

    1.硬綁定

    思考1:

    function foo(){
        console.log(thi.a);
    }
    
    var obj={
        a:2
    }
    
    var bar=function(){//函數表達式
        foo.call(obj);//強制把this綁定到obj
    }
    
    bar();//2
    setTimeout(bar,1000);//2
    
    //硬綁定的bar不可能再修改它的this指向
    bar.call(window);//2

    上例,將foo硬綁定到obj,無論之后如何調用函數bar,他總會在obj上調用foo。因他是顯示的強制綁定,故稱它:硬綁定。

    思考2:

    function foo(sth){
        console.log(this.a,sth);
        return this.a+sth;
    }
    
    //簡單的輔助函數
    function bind(fn,obj){//bind(執行函數,要綁定的對象):確保函數使用指定this
        return function(){
            return fn.apply(obj,arguments);
        }
    }
    
    var obj={
        a:2
    }
    
    var bar=bind(obj,foo);
    
    var b=bar(3);//2 3   (將3傳給執行函數)
    console.log(b);//5
    

    上例:創建一個可以重復使用的輔助函數。

    思考3:

    function foo(sth){
        console.log(this.a,sth);
        return this.a + sth;
    }
    
    var obj={
        a:2
    }
    
    var bar=foo.bind(obj);
    
    
    var b=bar(3);  //2 3  (將3傳給執行函數)
    console.log(b);//5

    上例:ES5內置的方法:function.prototype.bind

    2.API調用的上下文

    第三方庫許多函數,度提供了一個可選參數,被稱為“上下文”,目的是確保回調函數使用指定this。

    function foo(el){
        console.log(el,this.id);
    }
    
    var obj={
        id:"cat"
    }
    
    //調用foo時,把this綁定到obj
    [1,2,3].forEach(foo,obj);
    //1 cat 2 cat 3 cat

    上例函數,實際通過call或apply方法實現了顯示綁定。通過庫函數,可減少代碼量。

    4.new綁定

    1.使用new來調用,會自動執行下列的操作:

    1,創建(或者說構造)一個全新的對象。
    
    2,這個新對象會被執行[Prototype]連接。
    
    3,這個新對象會綁定到函數調用的this
    
    4,如果函數沒有返回其他對象,那么new表達式中的函數調用,會自動返回這個新對象。
    
    function foo(a){
        this.a=a;
    }
    
    var bar=new foo(2);
    console.log(bar.a);//2

    使用new時,我們可以構造一個新對象,并把它綁定到函數調用的this上。


    根據優先級判斷this

    按照下面的順序來進行判斷

    (1)函數是否在new中調用(new綁定)?如果是的話 ,this綁定的是新創建的對象。

    var bar=new foo();
    

    (2)函數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this綁定的是指定的對象。

    var bar=foo.call(obj2);
    

    (3)函數是否在某個上下文對象中調用(或隱式綁定)?若果是的話,this綁定的是那個上下文對象。

    var bar=obj1.foo();
    

    (4)如果度不是的話,使用默認綁定。若果在嚴格模式下,就綁定到undefined,否則綁定到全局。

    var bar=foo();
    

    被忽略的this

    1.

    function foo(){
        console.log(this.a);
    }
    
    var a=2;
    foo.call(null);//2

    如果你把null或undefined作為this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。

    2.

    function foo(a,b){
        console.log("a"+a,"b"+b);
    }
    
    //把數組展開成參數
    foo.apply(null,[2,3]);//a:2 b:3
    
    //使用bind(..)進行柯里化
    var bar=foo.bind(null,2);
    bar(3);// a:2 b:3

    使用apply(..)來展開一個數組,并當作參數傳入一個函數。類似,bind可以對參數進行柯里化(即傳入一個參數后,返回傳參后的數組,再傳入一個參數)

    上例1,2屬于使用null來忽略this綁定,因其容易導致bug,故不建議。

    3.推薦做法

    function foo(a,b){
        console.log("a"+a,"b"+b);
    }
    
    //創建一個安全隔離區對象
    var Φ=Object.create(null);
    
    //把數組展開成參數
    foo.apply(Φ,[2,3]);//a:2 b:3
    
    //使用bind初始化
    var bar=foo.bind(Φ,2);
    bar(3); a:2 ,b:3

    此處使用Object.create(null),它和{}不同在于:它不會創建Object.prototype這個委托

    使用變量名Φ優點:提高安全性(保護全局變量),提高代碼可讀性。


    間接引用

    function foo(){
        console.log(this.a);
    }
    
    var a=2;
    var o={a:3,foo:foo};
    var p={a:4};
    
    o.foo();//3
    (p.foo=o.foo)//2
    
    賦值表達式p.foo=o.foo的返回值是目標函數的引用,因此調用位置是foo,這里應用默認綁定。

    間接引用最容易在賦值時發生。


    軟綁定softBind(再看書例題??)

    首先檢查調用時的this,若this綁定到了全局對象或undefined,那就吧指定的默認對象綁定到this。


    this詞法

    1.箭頭函數=>的詞法作用域:

    function foo(){
        //返回一個箭頭函數
        return (a)=>{
            //this繼承自foo()
            console.log(this.a);
        }
    }
    
    var obj1={
        a:2
    }
    
    var obj2={
        a:3
    }
    
    var bar=foo.call(obj1);
    bar.call(obj2);//2
    

    2.箭頭函數的綁定無法被修改

    3.箭頭函數最常用于回調函數中,如 時間處理器或定時器:

    小結:ES6中的箭頭函數并不使用四條標準的綁定規則,而是根據當前詞法作用域來決定this。—箭頭函數會繼承外層函數的this綁定(無論this綁定到什么)。
    這和ES6之前的self=this機制相同。


    this使用:完全采用this風格,必要時采用bind(..),盡量避免self=this和箭頭函數。

    面向對象

    構造函數 類的繼承 多態 超類


    一.構造函數
    1.定義:類構造函數屬于類,通常和類同名。此外,構造函數可以通過new來構建一個新的類實例。

    2.實例:

    class coolguy{
        spetrick=nothing
    
        coolguy(trick){
            spetrick=trick
        }
    
        showoff(){
            output("here is my trick",spetrick);
        }
    }
    
    //調用類的構造函數來生成一個coolguy實例
    joe=new coolguy('jump rope');
    joe.showoff();//這是我的絕技:跳繩

    二.類的繼承

    1.繼承與重寫例子:

    輸入圖片說明

    三。多態

    1.相對多態:雖然任何層次都可以引用繼承層次中中高層的方法(無論高層方法名和當前方法名是否相同),但我們并不想要訪問絕對的繼承層次(或類)。

    2.在上面二中的實例,可用super來代替inherited,它的含義:超類,表示當前的父類或祖先類。

    3.super功能:從子類的構造函數中通過super來調用父類的構造函數。

    真正來說,構造函數屬于類。

    js中,“類”屬于構造函數。(類似于Foo.prototype..這樣的類型引用)

    4.多態并不表示子類和父類有關聯,子類得到的只是父類的一份副本。類的繼承其實就是復制。

    5.多重繼承(js中不提供“多重繼承”的功能)

    輸入圖片說明

    四。混入

    (一)顯式混入

    偽多態

    1.實例:

    輸入圖片說明

    2.顯式多態:vehicle.drive.call(this)

    相對多態:inherited:drive()

    3.偽多態:在上例中,若car(子類)中沒有的方法,在vehicle(父類)出現了,此時不需要方法多態,因為調用mixin(..)時會把父類方法復制到子類中

    4.偽多態缺點:代碼復雜;難以閱讀;難以維護。

    混合復制

    輸入圖片說明

    思路:先進行復制,再對子類進行特殊化

    注意:js中的函數無法真正得進行復制,所以你只能復制對共享函數對象的引用(函數就是對象)。如果你修改了共享的函數對象,那么由他所構造出的父類和子類都會受影響。

    寄生繼承

    注:報錯??

    //父類vehicle
    function vehicle(){
        this.engine=1;  
    }
    
    vehicle.prototype.ignition=function(){
        console.log("turn on my engine");   
    }
    
    vehicle.prototype.drive=function(){
        this.ignition();
        console.log("steering and moving forward"); 
    }
    
    //寄生類(子類)
    function car(){
        //首先,ca是一個vehicle
        var car=new vehicle();
    
        //接著,對ca進行定制
        car.wheels=4;
    
        //保存到vehicle::drive()的特殊引用
        var vehDrive=car.drive();
    
        //重寫vehicle::drive
        car.drive=function(){
            vehDrive.call(this);
            console.log("rolling on all "+this.wheels+" wheels!");  
        }
        return car; 
    }
    
    //方法一
    var mycar=new car();
    mycar.drive();
    
    //方法二(因最后使用的是mycar,所以最初的new car()將被丟棄。所以建議采用方法二,來避免因為創建而丟棄多于對象的情況。)
    car.drive

    思路:首先復制一份vehicle父類(對象的定義),然后混入子類(對象)的定義(如果需要的話保留到父類的特殊引用),最后用這個復合對象來構建實例。

    (二)隱式混入

    var sth={
        cool:funcction(){
            this.greet="hello world";
            this.count=  this.count? this.count:1;
        }
    }
    
    sth.cool();
    sth.greeting();//"hello world"
    sth.count;//1
    
    var ano={
        cool:functuin(){
            //隱式把sth混入ano,即讓sth的this指針指向ano    
            sth.cool.call(this);
        }
    }
    
    ano.cool();
    ano.greeting();
    ano.count();//1 (不是共享狀態)

    評價:雖然更改this指向,但盡量不要用,來保證代碼的整潔與可維護性。


    總點評:在js中模擬類可能會埋下大的隱患,故少用。

    原型鏈


    一。基礎知識

    1.值類型的判斷用typeof,引用類型的判斷用instanceof。

    console.log(typeof null);//object
    
    var fn=function(){};
    console.log(fn instanceof object);//true

    2.函數就是一個對象。

    3.只要是對象,他就是屬性的集合。js中的對象可任意擴展屬性。對象里的屬性表示為鍵值對的形式。

    var obj={
        a:10,
        b:function(){
            alert(this.a);
        }
    }

    4.在jQuery中,“jQuery”或者“$”變量,屬于函數類型。

    alert(typeof $)
    

    5.強類型:假設你在c#代碼中,你定義了一個整型變量后,就不能賦一個字符型數據給這個變量(除非你用強制類型轉換)

    弱類型就像javascript;var 可以接受任何類型

    6.一切引用類型,都是對象。

    7.

    構造函數中: 屬性與方法為當前實例單獨擁有,只能被當前實例訪問,并且每聲明一個實例,其中的方法都會被重新創建一次。
    
    原型中: 屬性與方法為所有實例共同擁有,可以被所有實例訪問,新聲明實例不會重復創建方法。
    
    模塊作用域(閉包)中:屬性和方法不能被任何實例訪問,但是能被內部方法訪問,新聲明的實例,不會重復創建相同的方法。
    

    二。對象與函數

    1.函數、對象的關系:

    • 對象都是通過函數來創建

      例如:

    創建對象的方式:
    
    //(語法糖,便捷方式)
    var obj={a:10,b:20}
    var arr={5,"x",true}
    實際等效于:
    var obj=new Object();
    obj.a=10;
    obj.b=20;
    
    var arr=new Array();
    arr[0]=5;
    arr[1]="x";
    arr[2]=true;

    其中,Array和Object屬于構造函數。—-結論:對象都是通過函數來構建。

    • 每個函數讀有一個屬性叫做prototype,它的屬性值是一個對象。在函數原型里默認得只有一個叫做constructor的屬性,指向這個函數本身。

    6

    原型作為對象,是屬性的集合。

    7

    • 在原型prototype中,添加自定義屬性

    8

      function Fn() { }
            Fn.prototype.name = '王福朋';
            Fn.prototype.getYear = function () {
                return 1988;
     };
    
    • $(“div”)創建對象原理
    myjQuery.prototype.attr = function () {
                //……
    };
    $('div') = new myjQuery();

    具體代碼演示

    function Fn() { }
    Fn.prototype.name = '王福朋';
    Fn.prototype.getYear = function () {
        return 1988;
    };
    
    var fn = new Fn();
    console.log(fn.name);
    console.log(fn.getYear());

    即,Fn是一個函數,fn對象是從Fn函數new出來的,這樣fn對象就可以調用Fn.prototype中的屬性。

    三。函數原型、對象原型

    1.每一個對象都擁有一個隱藏的屬性,proto,這個屬性引用了創建這個對象的函數prototype,可成為隱式原型。

    –即,每個對象都有一個proto__屬性,指向創建該對象的函數的prototype。

    2.定律一:構造函數創建的對象.__proto__===原函數(或創建它的函數).prototype

    3.

    通過下面代碼查看原型屬性:

    var obj={};
    console.log(obj.__proto__)

    9

    4.根據定律一,有:

    obj這個對象本質上是被Object函數創建的,因此obj.__proto=== Object.prototype。我們可以用一個圖來表示。

    10

    5.定律二:Object.prototype的__proto__指向的是null,切記切記!

    11

    6.函數是被Function創建的。

    //不推薦
    var fn1=new Function("x","y","return x+y;");
    console.log(fn1(5,6));

    上圖中,很明顯的標出了:自定義函數Foo._proto指向Function.prototype,Object.___proto指向Function.prototype, 函數實例.___proto指向Function.prototype;

    分析:Function也是一個函數,函數是一種對象,也有proto屬性。既然是函數,那么它一定是被Function創建。所以——Function是被自身創建的。所以它的proto指向了自身的Prototype。

    12

    注意:Function.prototype指向的對象也是一個普通的被Object創建的對象

    如下圖:

    14

    四。instanceof

    1.

    15

    上圖中,f1這個對象是被Foo創建,但是“f1 instanceof Object”為什么是true呢?

    原因如下:

    16

    Instanceof運算符的第一個變量是一個對象,暫時稱為A;第二個變量一般是一個函數,暫時稱為B。

    Instanceof的判斷隊則是:沿著A的proto這條線來找,同時沿著B的prototype這條線來找,如果兩條線能找到同一個引用,即同一個對象,那么就返回true。如果找到終點還未重合,則返回false。

    同理可以解釋:

    17

    具體原因請看下圖(結合圖+之前圖片):

    18

    五。繼承

    1.javascript中的繼承是通過原型鏈來體現的。先看幾句代碼

    19

    2.上述現象的解釋:
    訪問一個對象的屬性時,先在基本屬性中查找,如果沒有,再沿著__proto__這條鏈向上找,這就是原型鏈。

    上例圖:

    20

    上圖中,訪問f1.b時,f1的基本屬性中沒有b,于是沿著proto找到了Foo.prototype.b。

    3.如何區分一個屬性到底是基本的還是從原型中找到的呢?大家可能都知道答案了——hasOwnProperty,特別是在for…in…循環中,一定要注意。

    注意: f1的這個hasOwnProperty方法來源

    它是從Object.prototype中來的,請看圖:

    21

    對象的原型鏈是沿著proto這條線走的,因此在查找f1.hasOwnProperty屬性時,就會順著原型鏈一直查找到Object.prototype。

    由于所有的對象的原型鏈都會找到Object.prototype,因此所有的對象都會有Object.prototype的方法。這就是所謂的“繼承”。

    當然這只是一個例子,你可以自定義函數和對象來實現自己的繼承。

    7.21記

    閉包與作用域


    1.瀏覽器預解析機制:

    變量、函數表達式——變量聲明,默認賦值為undefined;
    this——賦值;
    函數聲明——賦值;
    

    這三種數據的準備情況我們稱之為“執行上下文”或者“執行上下文環境”。

    2.我們在聲明變量時,全局代碼要在代碼前端聲明,函數中要在函數體一開始就聲明好。除了這兩個地方,其他地方都不要出現變量聲明。而且建議用“單var”形式。

    3.作用域

    輸入圖片說明

    作用域有上下級的關系,上下級關系的確定就看函數是在哪個作用域下創建的。例如,fn作用域下創建了bar函數,那么“fn作用域”就是“bar作用域”的上級。

    4.塊級作用域實例:

    輸入圖片說明

    作用域中變量的值是在執行過程中產生的確定的,而作用域卻是在函數創建時就確定了。

    所以,如果要查找一個作用域下某個變量的值,就需要找到這個作用域對應的執行上下文環境,再在其中尋找變量的值。

    5.作用域鏈

    第一步,現在當前作用域查找a,如果有則獲取并結束。如果沒有則繼續;

    第二步,如果當前作用域是全局作用域,則證明a未定義,結束;否則繼續;

    第三步,(不是全局作用域,那就是函數作用域)將創建該函數的作用域作為當前作用域;

    第四步,跳轉到第一步。

    輸入圖片說明

    以上代碼中:第13行,fn()返回的是bar函數,賦值給x。執行x(),即執行bar函數代碼。取b的值時,直接在fn作用域取出。取a的值時,試圖在fn作用域取,但是取不到,只能轉向創建fn的那個作用域中去查找,結果找到了。

    6.閉包應用的兩種情況:

    (一)第一,函數作為返回值

    輸入圖片說明
    如上代碼,bar函數作為返回值,賦值給f1變量。執行f1(15)時,用到了fn作用域下的max變量的值。

    (二)函數作為參數被傳遞

    輸入圖片說明

    如上代碼中,fn函數作為一個參數被傳遞進入另一個函數,賦值給f參數。執行f(15)時,max變量的取值是10,而不是100。

    7.當一個函數被調用完成之后,其執行上下文環境將被銷毀,其中的變量也會被同時銷毀。

    但是有些情況下,函數調用完成之后,其執行上下文環境不會接著被銷毀。這就是需要理解閉包的核心內容。

    8.閉包實例:

    輸入圖片說明

    最后,執行完20行就是上下文環境的銷毀過程,這里就不再贅述了。

    9.if和for的花括號,不是域。

    10.在瀏覽器中,全局執行環境被默認為Window對象,因此所有全局變量和函數都是作為Window對象的屬性和方法創建的。某個執行環境中的所有代碼執行完畢后,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀。(全局執行環境直到應用程序退出,例如關閉網頁或瀏覽器時,才銷毀。)

    11.每個函數獨有自己的執行環境。

    12.如果這個環境是函數,則將其活動對象作為變量對象。

    13.內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境的任何變量和函數。—-每個環境都可以向上搜索作用域鏈,以查詢變量和函數名。但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。

    14.函數參數也被當作變量來對待,因此其訪問規則與執行環境中的其他變量相同。


    閉包的使用情況

    1.外部函數調用之后,其變量對象本應該被銷毀,但閉包的存在使我們仍然可以訪問外部函數的變量對象。

    function outer(){
        var a=1;
        return function(){
            return a;
        }
    }
    
    var b=outer();
    console.log(b());//1
    

    2.模塊級作用域

    (function(){
        var now=new Data();
        if(now.getMonth()==0&&now.getData()==1){
            alert("happy");
        }
    })()
    

    它用來限制向全局作用域中,添加過多的函數和變量。

    3.創建私有變量

    function outer(){
        var a=1;
        return {
            setA:function(val){
                a=val;
            }
            getA:function(){
                return a;
            }
        }
    }
    
    var closure=outer();
    console.log(a);//報錯
    console.log(closure.getA());//1
    closure.setA(2);
    console.log(closure.getA());//2
    

    通過閉包,創建具有私有屬性的實例對象。

    4.閉包只能取得包含函數中,任何變量的最后一個值。

    function arrFunc(){
        var arr=[];
        for(var i=0;i<6;i++){
            arr[i]=function(){
                return i;
            }
        }
        return arr;
    }
    

    arr數組包含了6個匿名函數,每個匿名函數都能訪問外部的變量i;

    當arrFun執行完畢后,其作用與被銷毀,但他的變量對象仍保存在內存,得以被匿名訪問,這時i的值為6.

    要想保存在循環過程中每一個i的值,需要在匿名函數外部再套用一個匿名函數,在這個匿名函數中定義另一個變量,并且立即執行來保存i的值。(如下)

    function arrFunc(){
        var arr=[];
        for(var i=0;i<10;i++){
            arr[i]=function(num){
                return function(){
                    return num;
                }
            }(i)
        }
        return arr;
    }
    
    console.log(arrFunc()[1]());
    

    這是最內部的匿名函數訪問的是num值,所以數組中10個匿名函數的返回值就是1~1-10.

    5.閉包中的this

    (1)

       var name='window';
        var obj={
            name:'object',
            getName:function(){
                return function(){
                    rerurn this.name;
                }
            }
        }
    
        console.log(obj.getName()());//window
    

    obj.getName()()實際是在全局作用域中調用了匿名函數,this指向了Window。即window才是匿名函數功能執行的環境。

    (2)

    若想要this指向外部函數的執行環境,可以這樣改寫:

    var name="window";
    var obj={
        name:"object",
        getName:function(){
            var that=this;
            return function(){
                return that.name;
            }
        }
    }
    console.log(obj.getName()());//object
    

    (3)arguments與this也有相同的問題,注意下面情況:

    var name="window";
    var obj={
        name:"object",
        getName:function(){
            return this.name;
        }
    }
    
    obj.getName();//object  (此時getName()是在對象obj的環境中執行的,所以this指向obj)
    
    (obj.getName=obj.getName)();//window非嚴格模式
    賦值語句返回的是等號右邊的值,在全局作用域中返回,所以(obj.getName=obj.getName)();的this指向全局。
    
    要把函數名和函數功能分割開

    6.內存泄漏

    閉包會引用包含函數的整個變量對象,如果閉包的作用域鏈中保存著一個HTML元素,那么就意味著該元素無法被銷毀。所以我們要在元素操作完后,主動銷毀:

    function assignHandler(){
        var element=document.getElementById("someElement");
        var id=element.id;
        element.onclick=function(){
            alert(id);
        }
        element=null;//見分析
    }
    

    分析:getElementById返回對擁有指定 ID 的第一個對象的引用 ,當不再使用該對象時,只需給指定該對象的指針復制為null,利用js垃圾回收機制(會自動回收無引用的對象),來釋放其所占內存

    7.函數內部的定時器

    當函數內部的定時器,引用了外部函數的變量對象時,該變量不會被銷毀。

    (function(){
        var a=0;
        setInterval(function(){
            console.log(a++);
        },1000)
    })()
    

    運用閉包的關鍵:

    1.閉包引用外部函數變量對象中的值。

    2.在外部函數的外部調用閉包。

    7.23

    構造函數

    設計模式
    

    1.

    工廠模式
    構造函數模式
    原型模式
    組合使用構造函數模式和原型模式
    動態原型模式
    寄生構造函數模式
    穩妥構造函數模式
    

    2.引入:建對象可以使用 Object構造函數 或者 字面量的形式 創建單個對象,但是這樣會產生大量重復的代碼。

    // Object構造函數
    var obj = new Object();
    // 字面量
    var obj = {};
    
    // 例如我們要記錄小明跟小紅的姓名跟年齡
    var xiaoming = {
        name: "xiaoming",
        age: 11
    };
    var xiaohong = {
        name: "xiaohong",
        age: 10
    }; 
    

    很明顯上面例子代碼重復,要是記錄的信息多一點,代碼重復率則越高,為了解決這個問題,就有了工廠模式


    工廠模式

    function createPerson(name, age) {
        var person = {
            name: name,
            age: age,
            getName: function() {
                console.log(this.name);
            }
        };
    
        return person;
    }
    var xiaoming = createPerson("xiaoming", 11);
    xiaoming.getName()    // xiaoming
    

    點評:工廠模式無法解決對象識別的問題,其實是無法辨認實例是由哪個構造函數創建

     console.log(xiaoming instanceof createPerson);    // false
      console.log(xiaoming instanceof Object);    // true
    

    改進: 構造函數模式


    構造函數

    1.

    // 按照慣例,構造函數以大寫字母開頭,以區別普通函數。
    function Person(name) {
        this.name = name;
        this.getName = function() {
            console.log(this.name);
        };
    }
    var xiaoming = new Person("xiaoming");
    xiaoming.getName();    // xiaoming
    
    // 成功解決工廠模式問題
    console.log(xiaoming instanceof Person);    // true
    

    2.之所以成為構造函數是因為 new 操作符,要創建構造函數實例就必須使用 new 操作符,并且經歷以下4個步驟

    1.創建一個新對象
    2.將構造函數的作用于賦給新對象(因此this指向了這個新對象)
    3.執行構造函數中的代碼(為這個新對象添加屬性)
    4.返回這個新對象

    既然說到了構造函數,那自然要說說它的返回值。這其中分三種情況。

    ①: 沒有指明具體的返回值,則返回該構造函數的實例化對象

    function Person(name) {
        this.name = name;
        this.getName = function() {
            console.log(this.name);
        };
    }
    
    var xiaoming = new Person("xiaoming");
    console.log(xiaoming);
    

    ②:若指明返回值,但不是引用類型的話,與情況①結果相同,返回該構造函數的實例化對象。

    function Person(name) {
        this.name = name;
        this.getName = function() {
            console.log(this.name);
        };
        return 12;
    }
    
    var xiaoming = new Person("xiaoming");
    console.log(xiaoming);//object { name:xiaoming,getName:function() }
    

    ③:若指明返回值且是引用類型,則返回該引用類型

    function Person(name) {
        this.name = name;
        this.getName = function() {
            console.log(this.name);
        };
        return {
            age: 12
        };
    }
    
    var xiaoming = new Person("xiaoming");
    console.log(xiaoming);//object{agge:12}
    

    原型模式

    1.所有實例共享它所包含的屬性和方法。

    2.

     function Person(){
    }
    Person.prototype.name = "wei";
    Person.prototype.age = 27;
    Person.prototype.job = "Software";
    Person.prototype.sayName = function(){
        alert(this.name);
    }
    
    var person1 = new Person();
    person1.sayName();      
    
    var person2 = new Person();
    person2.sayName();      
    
    alert(person1.sayName == person2.sayName);
    

    構造函數+原型

    1.構造函數模式用于定義實例屬性,原型模式用于定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度的節省了內存。另外,這種混成模式還支持向構造函數傳遞參數;

    2.

    function Person(name, age){
        this.name = name;
        this.age = age;
        this.friends = ["乾隆","康熙"];
    }
    Person.prototype = {
        constructor:Person,
        sayName:function(){
            alert(this.name);
        }
    }
    var person1 = new Person("wei",29);
    var person2 = new Person("bu",25);
    person1.friends.push("嬴政");
    console.log(person1.friends);   //["乾隆", "康熙", "嬴政"]
    console.log(person2.friends);   //["乾隆", "康熙"]
    console.log(person1.friends === person2.friends);   //false
    console.log(person1.sayName === person2.sayName);   //true
    

    動態原型模式

    function Person(name, age){
        this.name = name;
        this.age = age;
        this.friends = ["乾隆","康熙"];
    
        if(typeof this.sayName!="function"){
            Person.prototype.sayName = function(){
                alert(this.name);
            }
        }
    }
    var person1 = new Person("wei",29);
    person1.friends.push("嬴政");
    person1.sayName();
    

    注意構造函數代碼中的if語句,這里只在sayName()方法不存在的情況下才會將它添加到原型中。這斷代碼只有在第一次調用構造函數的時候才會被執行。此后,原型已經被初始化,不需要再做什么修改。不過要記住,這里所做的修改能立即在所有實例中得到反映。因此,這種方法可以說確實非常完美。

    注意:使用動態原型模式時,不能使用對象字面量(屬性名)重寫原型。如果在已經創建了實例的情況下重寫原型,那么就會切斷現有的實例與新原型之間的聯系。


    寄生構造函數模式

    1.基本思想是創建一個函數,該函數的作用僅僅是封裝創建對象的代碼,然后再返回新創建的對象,但從表面看,這個函數又很像典型的構造函數。

    2.

    function Person(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(this.name);
        }
        return o;
    }
    var person = new Person("wei",29,"banzhuan");
    person.sayName();   
    

    穩妥構造函數模式

    1.所謂穩妥對象,是指沒有公共屬性,而且其方法也不引用this對象。穩妥對象最適合在一些安全環境中(這些環境會禁止使用this和new),或者在防止數據被其他應用程序改動時使用。穩妥構造函數遵循的與寄生構造函數類似的模式,但又兩點不同:一是新創建對象的實例方法不引用this;二是不使用new操作符調用構造函數。

    2.

    function Person(name, age, job){
        //創建要返回的新對象
        var o = new Object();
    
        //可以在這里定義私有變量和函數
    
        //添加方法
        o.sayName = function(){
            alert(this.name);
        };
    
        //返回對象
        return o;
    }
    
    var person =Person("weiqi",22,"banzhuan");
    person.sayName();   
    

    3.注意:與寄生構造函數模式類似,使用穩妥構造函數模式創建的對象與構造函數之間沒有什么關系,因此instanceof操作符對這種對象也沒有意義。

    繼承


    (一)原型鏈繼承

    1.為了讓子類繼承父類的屬性(也包括方法),首先需要定義一個構造函數。然后,將父類的新實例賦值給子類的原型。

    核心:將父類實例作為子類的原型。

    2.例子:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>原型鏈繼承</title>
    </head>
    <body>
    <script src="myJS.js"></script>
    <script>
        <!--定義一個動物類-->
        function Animal(name){
    //        屬性
            this.name=name || Animal;
    //        實力方法
            this.sleep=function(){
                console.log(this.name+"正在睡覺");
            }
        }
    //    原型方法
        Animal.prototype.eat=function(food){
            console.log(this.name+"正在吃"+food);
        };
    //    原型鏈繼承
        function Cat(){
    
        }
        Cat.prototype=new Animal;
        Cat.prototype.name="Cat";
    //    Test code
        var cat=new Cat();
        console.log(cat.name);//cat
        console.log(cat.eat('fish'));//貓吃魚
        console.log(cat.sleep());//貓正在睡覺
        console.log(cat instanceof Animal);//true
        // instanceof它的作用是判斷其左邊對象是否為其右邊類的實例,返回boolean類型的數據。
        console.log(cat instanceof Cat);//true
    
    
    </script>
    </body>
    </html>
    

    3.特點:

    (1)非常純粹的繼承關系,既有父類的實例,也有子類的實例
    (2)父類新增的原型方法/原型屬性,子類都能訪問到
    (3)簡單,也易于實現
    (4)若父類具有引用屬性,子類可以訪問到

    缺點:

    (1)要想為子類新增屬性和方法,必須要在newAnimal()這樣的語句執行之后,不能放在構造器中。
    (2)無法實現多繼承。—多繼承:子類繼承自多個父親
    (3)創建子類實例,無法向構造傳參。 —可以傳參,看下一個


    (二)構造繼承

    核心:使用父類構造函數來增強子類實例,等于是復制父類的實力屬性給子類(沒用到原型)—-類似于C++中繼承

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>構造函數</title>
    </head>
    <body>
    <script src="myJS.js"></script>
    <script>
        <!--定義一個動物類-->
        function Animal(name){
    //        屬性
            this.name=name || Animal;
    //        實力方法
            this.sleep=function(){
                console.log(this.name+"正在睡覺");
            }
        }
        //構造函數
        function Cat(name){
            Animal.call(this);
            this.name=name||"tom";
        }
        //Test Code
        var cat=new Cat();
        console.log(cat.name);
        console.log(cat.sleep);//打印出來是sleep的構造函數,如果加()表示調用
        console.log(cat instanceof Animal);//false
        console.log(cat instanceof Cat);//true
    </script>
    
    </body>
    </html>
    

    特點:

    (1)解決了1中,子類實例共享父類引用屬性的問題
    (2)創建子類實例時,可以向父類傳遞參數。
    (3)可以實現多繼承(call多個父類的對象) 
    

    缺點

    (1)只有子類的實例
    (2)只能繼承父類構造的屬性和方法,不能繼承父類原型屬性和方法
    (3)無法實現函數的復用,每個子類都有父類實力函數的副本,影響性能。(致命)
    

    (三)實例繼承

    1.核心:為父類實例添加新特性,作為子類實例返回

    2.代碼

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>實力繼承</title>
    </head>
    <body>
    <script src="myJS.js"></script>
    <script>
        <!--定義一個動物類-->
        function Animal(name){
    //        屬性
            this.name=name || Animal;
    //        實力方法
            this.sleep=function(){
                console.log(this.name+"正在睡覺");
            }
        }
        //構造函數
        function Cat(name){
            //實例是父類的實例
            var instance=new Animal();
            instance.name=name||"tom";
            return instance;
        }
        //Test Code
        var cat=new Cat();
        console.log(cat.name);
        console.log(cat.sleep());
        console.log(cat instanceof Animal);//true
        console.log(cat instanceof Cat);//false
    </script>
    </body>
    </html>
    

    3.特點:

    不限制調用方式,不管是new子類()還是子類(),都返回父類的實例。
    

    缺點:

    (1)實例是父類的實例,不是子類的實例。
    (2)不支持多繼承。
    

    (四)拷貝繼承

    核心:將父類的原型,枚舉賦值給子類

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>拷貝繼承</title>
    </head>
    <body>
    <script src="myJS.js"></script>
    <script>
        <!--定義一個動物類-->
        function Animal(name){
    //        屬性
            this.name=name || Animal;
    //        實力方法
            this.sleep=function(){
                console.log(this.name+"正在睡覺");
            }
        }
        //構造函數
        function Cat(name){
            var animal=new Animal();
            for( var p in animal){
                Cat.prototype[p]=animal[p];
            }
            Cat.prototype.name=name||"Tom";
        }
        //Test Code
        var cat=new Cat();
        console.log(cat.name);//Tom
        console.log(cat.sleep());//Tom正在睡覺
        console.log(cat instanceof Animal);//false
        console.log(cat instanceof Cat);//true
    </script>
    </body>
    </html>
    

    特點:

    (1)支持多繼承 缺點:
    (1)效率較低,內容占用高(因為要拷貝父類的屬性)
    (2)只能獲取父類可以枚舉的方法(不可枚舉的方法,不能使用for in訪問到)
    

    (五)組合繼承(原型鏈+借用構造函數)

    1.核心思想:使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又保證每個實例都有它自己的屬性

    2.核心:通用調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類型,實現函數復用。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>組合繼承 </title>
    </head>
    <body>
    <script src="myJS.js"></script>
    <script>
        <!--定義一個動物類-->
        function Animal(name){
    //        屬性
            this.name=name || Animal;
    //        實力方法
            this.sleep=function(){
                console.log(this.name+"正在睡覺");
            }
        }
        //構造函數
        function Cat(name){
            //1.繼承父類構造的屬性,并保留傳參的優點(第一次調父類構造)
            Animal.call(this);
            this.name=name||"Tom";
        }
        //2.將父類實例作為子類原型,實現函數復用(第二次調父類構造)
        Cat.prototype=new Animal();
        //Test Code
        var cat=new Cat();
        console.log(cat.name);
        console.log(cat.sleep());
        console.log(cat instanceof Animal);//true
        console.log(cat instanceof Cat);//false
    </script>
    </body>
    </html>
    

    特點:

    (1)彌補了方式2的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法。
    (2)即是子類的實例,也是父類的實例
    (3)不存在引用屬性的共享問題
    (4)可傳參
    (5)函數可復用 
    

    缺點:

    調用了兩次父類構造函數,生成了兩份實例。
    

    (六)寄生組合繼承

    (1)核心:通過寄生方式,砍掉父類的實例屬性(通過聲明超類,且他僅僅等效于父類原型來實現),這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例屬性/方法,避免了組合繼承的缺點

    //父類
    function Animal(name){
        this.name=name;
        this.sleep=function(){
            console.log(this.name+"正在睡覺");
        }
    }
    Animal.prototype.eat=function(food){
        console.log(this.name+"正在吃"+food);
    }
    //子類
    function Cat(name){
        Animal.call(this);//子類第一次繼承
        this.name=name||'Tom';
    }
    (function(){
        //創建一個沒有實例方法的超類
        var Super=function(){};
        //讓超類的原型==父類的原型
        Super.prototype=Animal.prototype;
        //將超類實例作為子類的原型
        Cat.prototype=new Super();
    })();
    //Test Code
    var cat=new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat.eat("fish"));
    console.log(cat instanceof Cat);//true
    console.log(cat instanceof Animal);//true
    

    (2)特點:堪稱完美

    (3)缺點:實現較為復雜

    遞歸


    1.遞歸函數:不改變問題性質,逐漸縮小問題規模。

    不改變問題性質:解決問題的方法相同

    縮小問題規模:逐層返回原規模問題

    2.遞歸函數:一個問題的自我調用


    實例1 遞歸求和

    function sum(n){
        if(n==1) return 1;
        return sum(n-1) + n;
    }
    
    fac(5);
    

    分析:上面是一個累加函數,最多需要保存n個記錄,復雜度為o(n)。當n較大時,需要保存成百上千個調用幀(函數調用時會在內存形成一個“調用記錄”,又稱“調用幀”),很容易發生“棧溢出”,耗費內存。

    優化如下(尾遞歸):

    function sum(n,total) {
          if(n===0){
               return total;
           }
           return sum(n-1,n+total)
       }
    sum(5,0);

    利用尾遞歸,僅保存一個調用記錄,復雜度為o(1)。

    實例2 Fibonacci數列(斐波那契數列)

    1,1,2,3,5,8,13,21,34,55,89…求第 n 項

    遞歸

    分析

    1. 假設已知 fib(n) 為第 n 項
    2. 遞歸關系
      • fib(n) = fib(n-1) + fib(n-2)
    3. 遞歸體
      function fib(n){
      return fib(n-1)+fib(n-2);
      }
    4. 臨界條件
      • fib(1) == 1
      • fib(2) == 1
    5. 遞歸函數
    function fib(n){
        if(n == 0 || n ==1) return 1;
        return fib(n-1) + fib(n-2);
    }
    

    非遞歸

    function fib(n){
            //起始兩個值
            var f1;
            var f2;
            f1=f2=1;
            console.log(f1,f2);
            //因為已經輸出前兩個,所以i從2開始;因為最后一個的fn由它前面的加和,所以循環繼續條件i<n,即i==n-1
            for(i=2;i<n;i++){
                fn=f1+f2;
                console.log(fn);
                f1=f2;
                f2=fn;
            }
        }
        console.log(fib(8));
    

    求冪

    1.概念:

    求冪就是求 某一個數 幾次方
    
    2*2 =2 的 平方, 2 的 2 次方
    
    求 n 的 m 次方,最終要得到一個函數 power( n, m )
    
    n 的 m 次方就是 m 個 n 相乘, 即 n 乘以 (m-1) 個 n 相乘
    

    分析:

    1. 假設已知函數 power(n,m) 為 n 的 m 次冪
    2. 遞歸關系
    * power(n,m-1) * n
    3. 遞歸體
    function power(n,m){
        return power(n,m-1) * n;
    }
    4. 臨界條件
        * m == 1 ,return n
        * m == 0 ,reutnr 1
    5. 遞歸函數
    function power(n,m){
        if(m == 1) return n;
        return power(n,m-1) * n;
    }


    克隆

    (1)淺克隆:淺拷貝只能拷貝值類型的數據,對于引用類型,只會拷貝引用地址,如果有引用類型,多個拷貝對象會共用同一個引用類型的數據,造成混亂。

    function clone(parent, child){
      var i, child = child || {};
      for(i in parent){
        parent.hasOwnProperty(i) && (child[i] = parent[i]);
      }
      return child;
    }
    var o1 = { arr: [1,2,4], name:'jack'};
    var o2 = clone(o1);
    o2.arr.push(3);
    log(o1.arr); //[1, 2, 4, 3]
    log(o2.arr); //[1, 2, 4, 3]
    

    兩個拷貝對象共用同一個引用類型,會相互影響

    (2)深拷貝

    function cloneDeep(parent, child){
      var i, child = child || {};
      for(i in parent){
        //若果父元素的第i個屬性為真
        if(parent.hasOwnProperty(i)){
          if(typeof parent[i]==='object'){
            child[i] = Array.isArray(parent[i]) ? []:{};
            cloneDeep(parent[i], child[i]);
          }else{
            child[i] = parent[i];
          }
        }
      }
      return child;
    }
    var o3 = { arr: [1,2,4], name:'jack'};
    var o4 = cloneDeep(o3);
    o4.arr.push(3);
    log(o3.arr);//[1, 2, 4]
    log(o4.arr);//[1, 2, 4, 3]
    

    兩個對象引用不同的引用地址,互不影響

    7.25

    移動端輪播圖

    <!DOCTYPE html>
    <html id="html">
    <head>
    <meta charset="utf-8">
    <title>無標題文檔</title>
    <meta name="keywords" content="" />
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
    <meta name="format-detection" content="telephone=no" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <style type="text/css">
    body{margin:0;}
    ul{padding:0; margin:0; height:200px; position:absolute; left:0; top:0;}
    li{list-style:none; height:100%; float:left;}
    li img{width:100%; height:100%;}
    #box{width:100%; height:200px; overflow:hidden; position:relative;}
    </style>
    </head>
    <body>
    <div id="box">
        <ul id="ul1">
            <li><img src="image/pic1.jpg" /></li>
            <li><img src="image/pic2.jpg" /></li>
        </ul>
    </div>
    <script src="js/jquery-1.10.1.min.js"></script>
    <script src="js/velocity.min.js"></script>
    <script>
    var iNow = 0;
    //li加倍(ul1里4個li)
    $("#ul1").html($("#ul1").html()+$("#ul1").html())
    //box寬度,賦值給li的寬度(即窗口一張圖片)
    $("li").css("width",$("#box")[0].offsetWidth+"px");
    //li加倍(ul1==4個li寬度)
    $("#ul1").css("width",$("li")[0].offsetWidth*4+"px");
    
    touchFunc($("#box")[0],"left",function(){toLeft()})
    touchFunc($("#box")[0],"right",function(){toRight()})
    
    function toLeft(){
        iNow++;
        //動畫
        $("#ul1").velocity({"left":-$("li")[0].offsetWidth*iNow+"px"},{duration:400,easing:"linear",complete:function(){        
            //如果是第二張,切到第0張
            if(iNow==$("li").length/2){
                iNow=0;
                $("#ul1").css("left",0);
            }
        }})
    }
    function toRight(){
        //如果第0張,切換到第二張
        if(iNow==0){
            iNow=$("li").length/2;
            $("#ul1").css("left",-$("li")[0].offsetWidth*iNow+"px");
        }
        iNow--;
        $("#ul1").velocity({"left":-$("li")[0].offsetWidth*iNow+"px"},{duration:400,easing:"linear"})
    }
    
    function touchFunc(obj,type,func) {
        var init = {x:5,y:5,sx:0,sy:0,ex:0,ey:0};
        var sTime = 0, eTime = 0;
        type = type.toLowerCase();
    
        //1.添加手指落下事件
        obj.addEventListener("touchstart",function(){
            sTime = new Date().getTime();
            //獲取并記錄:起始手指位置
            init.sx = event.targetTouches[0].pageX;
            init.sy = event.targetTouches[0].pageY;
            init.ex = init.sx;
            init.ey = init.sy;
            if(type.indexOf("start") != -1) func();
        }, false);
    
        //2.添加手指滑動事件
        obj.addEventListener("touchmove",function() {
            event.preventDefault();
            //獲取并記錄:手指抬起位置
            init.ex = event.targetTouches[0].pageX;
            init.ey = event.targetTouches[0].pageY;
            if(type.indexOf("move")!=-1) func();
        }, false);
    
        //3.觸摸結束,根據時間、方向,判斷執行事件的類型
        obj.addEventListener("touchend",function() {
            var changeX = init.sx - init.ex;//起始-結束
            var changeY = init.sy - init.ey;
            if(Math.abs(changeX)>Math.abs(changeY)&&(changeY)>init.y) {//滑動范圍大于5  且偏于水平滑動 Math.abs:絕對值
                if(changeX > 0) {
                    if(type.indexOf("left")!=-1) func();//此代碼中沒有寫鼠標上下滑事件,可根據左右滑動類推
                }else{
                    if(type.indexOf("right")!=-1) func();
                }
            }
            else if(Math.abs(changeY)>Math.abs(changeX)&&Math.abs(changeX)>init.x){//同上  判斷垂直
                if(changeY > 0) {
                    if(type.indexOf("top")!=-1) func();
                }else{
                    if(type.indexOf("down")!=-1) func();
                }
            }
            else if(Math.abs(changeX)<init.x && Math.abs(changeY)<init.y){//若改變范圍<5,則默認為點擊事件
                eTime = new Date().getTime();
                if((eTime - sTime) > 300) {//長按事件
                    if(type.indexOf("long")!=-1) func(); 
                }
                else {//點擊事件
                    if(type.indexOf("click")!=-1) func(); 
                }
            }
            if(type.indexOf("end")!=-1) func();
        }, false);
    };
    </script>
    </body>
    </html>
    

    call,apply,bind


    一、總結:

    apply 、 call 、bind 三者都是用來改變函數的this對象的指向的;

    apply 、 call 、bind 三者第一個參數都是this要指向的對象,也就是想指定的上下文;

    apply 、 call 、bind 三者都可以利用后續參數傳參;

    bind是返回對應函數,便于稍后調用;apply、call則是立即調用 。

    二、call、apply區別:

    func.call(this, arg1, arg2);
    func.apply(this, [arg1, arg2])
    

    call(this)、 apply(this) 指的是func()這個函數本身

    三、使用

    1、數組之間追加

    var array1 = [12 , "foo" , {name "Joe"} , -2458];
    var array2 = ["Doe" , 555 , 100];
    Array.prototype.push.apply(array1, array2);
    /* array1 值為 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
    

    讓array1 具備Array的push方法

    2、獲取數組中的最大值和最小值

    var numbers = [5, 458 , 120 , -215 ];
    var maxInNumbers = Math.max.apply(Math, numbers), //458
    maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
    

    讓numbers 具備Math的max方法,number 本身沒有 max 方法,但是 Math 有,我們就可以借助 call 或者 apply 使用其方法。

    3、驗證是否是數組(前提是toString()方法沒有被重寫過)

     function isArray(obj){
            var bool;
            if (Object.prototype.toString.call(obj) === '[object Array]') {
                bool = "true";
            } else {
                bool = "false";
            }
            return bool;
        }
    alert(isArray([1,2,3,4]));
    

    4、類(偽)數組 => 真正數組 具備數組方法

    var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
    

    Javascript中存在一種名為偽數組的對象結構。比較特別的是 arguments 對象,還有像調用 getElementsByTagName, document.childNodes 之類的,它們返回NodeList對象都屬于偽數組。不能應用 Array下的 push , pop 等方法。

    5、例子

    定義一個 log 方法,讓它可以代理 console.log 方法,常見的解決方法是:

    function log(msg) {
        console.log(msg);
    }
    log(1); //1
    log(1,2); //1
    

    上面方法可以解決最基本的需求,但是當傳入參數的個數是不確定的時候,上面的方法就失效了,這個時候就可以考慮使用 apply 或者 call,注意這里傳入多少個參數是不確定的,所以使用apply是最好的,方法如下:

    function log(){
        console.log.apply(console, arguments);
    };
    log(1); //1
    log(1,2); //1 2
    

    接下來的要求是給每一個 log 消息添加一個”(app)”的前輟,比如:

    log("hello world"); //(app)hello world
    

    該怎么做比較優雅呢?這個時候需要想到arguments參數是個偽數組,通過 Array.prototype.slice.call 轉化為標準數組,再使用數組方法unshift,像這樣:

    function log(){
        //偽類數組 => 正真的數組
        var args = Array.prototype.slice.call(arguments);
        args.unshift('(app)');
    
        console.log.apply(console, args);
    };
    

    6、bind

    var bar = function(){
        console.log(this.x);
    }
    var foo = {
        x:3
    }
    var sed = {
        x:4
    }
    var func = bar.bind(foo).bind(sed);
    func(); //3
    
    var fiv = {
        x:5
    }
    var func = bar.bind(foo).bind(sed).bind(fiv);
    func(); //3
    

    答案是,兩次都仍將輸出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是無效的。更深層次的原因, bind() 的實現,相當于使用函數在內部包了一個 call / apply ,第二次 bind() 相當于再包住第一次 bind() ,故第二次以后的 bind 是無法生效的。


    caller、callee

    1.caller

    作用:返回當前這個函數的調用者,在函數中調用有效,否則就返回null

    var a = function(){
        console.log(a.caller);
    }
    a();//null
    
    var b = function(){
        a();
    }
    b();//? (){a()};
    

    第一個打印是null,第二個打印的是b所對應的函數.第一次打印是window去調用的a函數,window是頂層調用,所以打印的是null,第二個打印是在b函數里進行,這樣就能通過a函數找到對應的函數調用者.

    2.callee

    (1)作用:callee是函數arguments的屬性,它會返回當前這個正在被調用的函數

    function test(){
        console.log(arguments.callee);
    }
    test(10, 20)
    

    打印的就是test這個函數

    (2)callee只有一個屬性,length,這個屬性的作用是函數的形參個數
    arguments.length返回的是實參個數

    function test(a){
        console.log(arguments.callee.length); // 1
        console.log(arguments.length);           // 2
    }
    test(10, 20)
    

    (3)通過這個屬性,能實現遞歸函數的作用,下面就通過他來實現以下5的階乘

    function test(a){
        if(a == 1){
            return 1;
        }
        return arguments.callee(a - 1) * a;
    }
    console.log(test(5)); // 120
    

    No way is impossible to courage!

    輸入圖片說明


    前端基礎進階@簡書波同學


    (一):內存空間詳細圖解

    1.在操作對象時,實際上是在操作對象的引用而不是實際的對象。因此,引用類型的值都是按引用訪問的。

    2.

    var a1 = 0;   // 變量對象
    var a2 = 'this is string'; // 變量對象
    var a3 = null; // 變量對象
    
    var b = { m: 20 }; // 變量b存在于變量對象中,{m: 20} 作為對象存在于堆內存中
    var c = [1, 2, 3]; // 變量c存在于變量對象中,[1, 2, 3] 作為對象存在于堆內存中
    

    輸入圖片說明

    3.內存空間管理

    (1)JavaScript的內存生命周期

    1. 分配你所需要的內存
    
    2. 使用分配到的內存(讀、寫)
    
    3.不需要時將其釋放、歸還
    

    (2)為了便于理解,我們使用一個簡單的例子來解釋這個周期。

        var a = 20;  // 在內存中給數值變量分配空間
        alert(a + 100);  // 使用內存
        a = null; // 使用完畢之后,釋放內存空間

    (3)JavaScript有自動垃圾收集機制,那么這個自動垃圾收集機制的原理是什么呢?其實很簡單,就是找出那些不再繼續使用的值,然后釋放其占用的內存。垃圾收集器會每隔固定的時間段就執行一次釋放操作。

    (4)在JavaScript中,最常用的是通過標記清除的算法來找到哪些對象是不再繼續使用的,因此a = null其實僅僅只是做了一個釋放引用的操作,讓 a 原本對應的值失去引用,脫離執行環境,這個值會在下一次垃圾收集器執行操作時被找到并釋放。而在適當的時候解除引用,是為頁面獲得更好性能的一個重要方式。

    (5)在局部作用域中,局部變量當執行完后就自動釋放。而全局變量釋放時候不確定。建議:盡量避免使用全局變量,以確保性能問題。


    (二)執行上下文

    1.因此在一個JavaScript程序中,必定會產生多個執行上下文,JavaScript引擎會以堆棧的方式來處理它們,這個堆棧,我們稱其為函數調用棧(call stack)。棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文。

    當代碼在執行過程中,遇到以下三種情況,都會生成一個執行上下文,放入棧中,而處于棧頂的上下文執行完畢之后,就會自動出棧。

    JavaScript中的運行環境大概包括三種情況。
    
    全局環境:JavaScript代碼運行起來會首先進入該環境
    函數環境:當函數被調用執行時,會進入當前函數中執行代碼
    eval
    

    2.

    var color = 'blue';
    
    function changeColor() {
        var anotherColor = 'red';
    
        function swapColors() {
            var tempColor = anotherColor;
            anotherColor = color;
            color = tempColor;
        }
    
        swapColors();
    }
    
    changeColor();
    

    輸入圖片說明

    3.結論:

    • 單線程

    • 同步執行,只有棧頂的上下文處于執行中,其他上下文需要等待

    • 全局上下文只有唯一的一個,它在瀏覽器關閉時出棧

    • 函數的執行上下文的個數沒有限制

    • 每次某個函數被調用,就會有個新的執行上下文為其創建,即使是調用的自身函數,也是如此。

    4.

    function f1(){
        var n=999;
        function f2(){
            alert(n); 
        }
        return f2;
    }
    var result=f1();
    result(); // 999
    

    輸入圖片說明

    (三)閉包

    1.簡要概念:假設函數A在函數B的內部進行定義了,并且當函數A在執行時,訪問了函數B內部的變量對象,那么B就是一個閉包。

    2.例子:

    var fn = null;
    function foo() {
        var a = 2;
        function innnerFoo() {
            console.log(c); //not define
            console.log(a);//2
        }
        fn = innnerFoo; // 將 innnerFoo的引用,賦值給全局變量中的fn
    }
    
    function bar() {
        var c = 100;
        fn(); // 此處的保留的innerFoo的引用
    }
    
    foo();
    bar();
    

    3.所以,通過閉包,我們可以在其他的執行上下文中,訪問到函數的內部變量。需要注意的是,雖然例子中的閉包被保存在了全局變量中,但是閉包的作用域鏈并不會發生任何改變。在閉包中,能訪問到的變量,仍然是作用域鏈上能夠查詢到的變量。

    4.應用:

    function fn() {
        console.log('this is test.')
    }
    var timer =  setTimeout(fn, 1000);
    /*注:setTimeout的返回值是一個整形id(從1開始),也就是這個setTimeout的唯一標示符。即作為一個引用,指向setTimeout。*/
    console.log(timer);
    

    結果:變量timer的值,會立即輸出出來,表示setTimeout這個函數本身已經執行完畢了。但是一秒鐘之后,fn才會被執行。

    分析:這是在函數的內部實現中,setTimeout通過特殊的方式,保留了fn的引用,讓setTimeout的變量對象,并沒有在其執行完畢后被垃圾收集器回收。因此setTimeout執行結束后一秒,我們任然能夠執行fn函數。


    (四)函數聲明與表達式

    1.JavaScript中,有兩種聲明方式,一個是使用var的變量聲明,另一個是使用function的函數聲明。

    變量對象的創建過程中,函數聲明比變量聲明具有更為優先的執行順序,即我們常常提到的函數聲明提前。因此我們在執行上下文中,無論在什么位置聲明了函數,我們都可以在同一個執行上下文中直接使用該函數。

    fn();  // function
    
    function fn() {
        console.log('function');
    }
    

    2.函數表達式

    與函數聲明不同,函數表達式使用了var進行聲明,那么我們在確認他是否可以正確使用的時候就必須依照var的規則進行判斷,即變量聲明。我們知道使用var進行變量聲明,其實是進行了兩步操作。

    // 變量聲明
    var a = 20;
    
    // 實際執行順序
    var a = undefined; 
    // 變量聲明,初始值undefined,變量提升,提升順序次于function聲明
    a = 20;  // 變量賦值,該操作不會提升
    

    同樣的道理,當我們使用變量聲明的方式來聲明函數時,就是我們常常說的函數表達式。函數表達的提升方式與變量聲明一致。

    fn(); // 報錯
    var fn = function() {
        console.log('function');
    }
    

    上例子的執行順序為:

    var fn = undefined;   // 變量聲明提升
    fn();    // 執行報錯
    fn = function() {   // 賦值操作,此時將后邊函數的引用賦值給fn
        console.log('function');
    }
    

    3.匿名函數

    使用場景:多作為一個參數傳入另一個函數中

    var a = 10;
    var fn = function(bar, num) {
        return bar() + num;
    }
    
    fn(function() {
        return a;
    }, 20)
    

    4.函數自執行與塊級作用域

    利用閉包,我們可以訪問到執行上下文內部的變量和方法,因此,我們只需要根據閉包的定義,創建一個閉包,將你認為需要公開的變量和方法開放出來。

    (function() {
       // 私有變量
       var age = 20;
       var name = 'Tom';
    
    
       // 私有方法
       function getName() {
           return `your name is ` + name;
       }
    
    
       // 共有方法
       function getAge() {
           return age;
       }
    
       // 將引用保存在外部執行環境的變量中,形成閉包,防止該執行環境被垃圾回收
       window.getAge = getAge;
    })();

    (五).柯里化

    (1)map(): 對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組。

    通俗來說,就是遍歷數組的每一項元素,并且在map的第一個參數(回調函數)中進行運算處理后返回計算結果。返回一個由所有計算結果組成的新數組。

    // 回調函數中有三個參數
    // 第一個參數表示newArr的每一項,第二個參數表示該項在數組中的索引值
    // 第三個表示數組本身
    
    var newArr = [1, 2, 3, 4].map(function(item, i, arr) {
        console.log(item, i, arr, this);  // 可運行試試看
        return item + 1;  // 每一項加1
    })
    
    console.log(newArr); // [2, 3, 4, 5]
    

    (2)柯里化(英語:Currying),又稱為部分求值,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回一個新的函數的技術,新函數接受余下參數并返回運算結果。

    接收單一參數,因為要攜帶不少信息,因此常常以回調函數的理由來解決。
    將部分參數通過回調函數等方式傳入函數中
    返回一個新函數,用于處理所有的想要傳入的參數
    

    (3)例子:

         function add() {
                // 第一次執行時,定義一個數組專門用來存儲所有的參數
                var _args = [].slice.call(arguments);
    
                // 在內部聲明一個函數,利用閉包的特性保存_args并收集所有的參數值
                var adder = function () {
                    var _adder = function() {
                        [].push.apply(_args, [].slice.call(arguments));
                        return _adder;
                    };
    
                    // 利用隱式轉換的特性,當最后執行時隱式轉換,并計算最終的值返回
                    _adder.toString = function () {
                        return _args.reduce(function (a, b) {
                            return a + b;
                        });
                    }
    
                    return _adder;
                }
                return adder.apply(null, [].slice.call(arguments));
            }
    
            // 輸出結果,可自由組合的參數
            console.log(add(1, 2, 3, 4, 5));  // 15
            console.log(add(1, 2, 3, 4)(5));  // 15
            console.log(add(1)(2)(3)(4)(5));  // 15
    

    (六)拖拽封裝

    1.讓元素動起來:我們常常會通過修改元素的top,left,translate來其的位置發生改變。由于修改一個元素top/left值會引起頁面重繪,而translate不會,因此從性能優化上來判斷,我們會優先使用translate屬性。如下:

       var ele1=document.querySelector(".ele1");
       var ele2=document.querySelector(".ele2");
    
       var btn1=document.querySelector(".btn1");
       var btn2=document.querySelector(".btn2");
    
       var left=0;
       var translateX=0;
    
       btn1.onclick=function () {
           left=left+5;
           ele1.style.left=left+"px";
       }
    
       btn2.onclick=function () {
           ele2.style.transform = 'translateX('+ translateX +'px)';
           //alert(translateX)
           translateX+=5;
       }
    

    2.拖拽

     // 獲取當前瀏覽器支持的transform兼容寫法
    /* function getTransform() {
        var transform = '',
            divStyle = document.createElement('div').style,
            // 判斷瀏覽器是否支持transforma,若是,用transform;否則,通過position來設定元素位置
            transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'],
    
            i = 0,
            len = transformArr.length;
    
        for(; i < len; i++)  {
            if(transformArr[i] in divStyle) {
                // 找到之后立即返回,結束函數
                return transform = transformArr[i];
            }
        }
    
        // 如果沒有找到,就直接返回空字符串
        return transform;
    }
    //alert(getTransform())  transform
    
    
    //封裝獲取元素的屬性(兼容)
    function getStyle(elem, property) {
        // ie通過currentStyle來獲取元素的樣式,其他瀏覽器通過getComputedStyle來獲取
        return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(elem, false)[property] : elem.currentStyle[property];
    }
    
    //獲取起始位置
    function getTargetPos(elem) {
        var pos = {x: 0, y: 0};
        var transform = getTransform();
        if(transform) {
            var transformValue = getStyle(elem, transform);
            if(transformValue == 'none') {
                elem.style[transform] = 'translate(0, 0)';
                return pos;
            } else {
                var temp = transformValue.match(/-?\d+/g);
                return pos = {//?
                    x: parseInt(temp[4].trim()),
                    y: parseInt(temp[5].trim())
                }
            }
        } else {
            if(getStyle(elem, 'position') == 'static') {
                elem.style.position = 'relative';
                return pos;
            } else {
                var x = parseInt(getStyle(elem, 'left') ? getStyle(elem, 'left') : 0);
                var y = parseInt(getStyle(elem, 'top') ? getStyle(elem, 'top') : 0);
                return pos = {
                    x: x,
                    y: y
                }
            }
        }
    }
    
    //設置位置
    // pos = { x: 200, y: 100 }
    function setTargetPos(elem, pos) {
        var transform = getTransform();
        if(transform) {
            elem.style[transform] = 'translate('+ pos.x +'px, '+ pos.y +'px)';
        } else {
            elem.style.left = pos.x + 'px';
            elem.style.top = pos.y + 'px';
        }
        return elem;
    }
    
    
    var oElem = document.getElementById('target');
    
    //保存鼠標起始位置
    var startX = 0;
    var startY = 0;
    
    //保存元素起始位置
    var sourceX = 0;
    var sourceY = 0;
    
    oElem.addEventListener('mousedown', start, false);
    
    function start(event) {
        // 獲取鼠標初始位置
        startX = event.pageX;
        startY = event.pageY;
    
        // 獲取元素初始位置
        var pos = getTargetPos(oElem);
    
        sourceX = pos.x;
        sourceY = pos.y;
    
        // 綁定
        document.addEventListener('mousemove', move, false);
        document.addEventListener('mouseup', end, false);
    }
    
    function move(event) {
        // 獲取鼠標當前位置
        var currentX = event.pageX;
        var currentY = event.pageY;
        // 獲取差值
        var distanceX = currentX - startX;
        var distanceY = currentY - startY;
        //  計算并設置元素當前位置
        setTargetPos(oElem, {
            x: (sourceX + distanceX).toFixed(),//toFixed():四舍五入,不保留小數部分
            y: (sourceY + distanceY).toFixed()
        })
    }
    
    function end(event) {
        document.removeEventListener('mousemove', move);
        document.removeEventListener('mouseup', end);
        // do other things
    }*/
    

    3.拖拽封裝

    完整封裝—不推薦,因為若拖拽中使兩元素重疊,則在拖拽元素上抬起時,可能丟失對該元素的拖拽事件,或者鼠標抬起不能釋放元素

    ;
    (function() {
        // 這是一個私有屬性,不需要被實例訪問
        var transform = getTransform();
    
        function Drag(selector) {
            // 放在構造函數中的屬性,都是屬于每一個實例單獨擁有
            this.elem = typeof selector == 'Object' ? selector : document.getElementById(selector);
            this.startX = 0;
            this.startY = 0;
            this.sourceX = 0;
            this.sourceY = 0;
    
            this.init();
        }
    
    
        // 原型
        Drag.prototype = {
            constructor: Drag,
    
            init: function() {
                // 初始時需要做些什么事情
                this.setDrag();
            },
    
            // 稍作改造,僅用于獲取當前元素的屬性,類似于getName
            getStyle: function(property) {
                return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this.elem, false)[property] : this.elem.currentStyle[property];
            },
    
            // 用來獲取當前元素的位置信息,注意與之前的不同之處
            getPosition: function() {
                var pos = {x: 0, y: 0};
                if(transform) {
                    var transformValue = this.getStyle(transform);
                    if(transformValue == 'none') {
                        this.elem.style[transform] = 'translate(0, 0)';
                    } else {
                        //獲取transform已有屬性值
                        var temp = transformValue.match(/-?\d+/g);
                        pos = {
                            x: parseInt(temp[4].trim()),
                            y: parseInt(temp[5].trim())
                        }
                    }
                } else {
                    if(this.getStyle('position') == 'static') {
                        this.elem.style.position = 'relative';
                    } else {
                        pos = {
                            x: parseInt(this.getStyle('left') ? this.getStyle('left') : 0),
                            y: parseInt(this.getStyle('top') ? this.getStyle('top') : 0)
                        }
                    }
                }
    
                return pos;
            },
    
            // 用來設置當前元素的位置
            setPostion: function(pos) {
                if(transform) {
                    this.elem.style[transform] = 'translate('+ pos.x +'px, '+ pos.y +'px)';
                } else {
                    this.elem.style.left = pos.x + 'px';
                    this.elem.style.top = pos.y + 'px';
                }
            },
    
            // 該方法用來綁定事件
            setDrag: function() {
                var self = this;
                this.elem.addEventListener('mousedown', start, false);
                function start(event) {
                    self.startX = event.pageX;
                    self.startY = event.pageY;
    
                    var pos = self.getPosition();
    
                    self.sourceX = pos.x;
                    self.sourceY = pos.y;
    
                    document.addEventListener('mousemove', move, false);
                    document.addEventListener('mouseup', end, false);
                }
    
                function move(event) {
                    var currentX = event.pageX;
                    var currentY = event.pageY;
    
                    var distanceX = currentX - self.startX;
                    var distanceY = currentY - self.startY;
    
                    self.setPostion({
                        x: (self.sourceX + distanceX).toFixed(),
                        y: (self.sourceY + distanceY).toFixed()
                    })
                }
    
                function end(event) {
                    document.removeEventListener('mousemove', move);
                    document.removeEventListener('mouseup', end);
                    // do other things
                }
            }
        }
    
        // 私有方法,僅僅用來獲取transform的兼容寫法
        function getTransform() {
            var transform = '',
                divStyle = document.createElement('div').style,
                transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'],
    
                i = 0,
                len = transformArr.length;
    
            for(; i < len; i++)  {
                if(transformArr[i] in divStyle) {
                    return transform = transformArr[i];
                }
            }
    
            return transform;
        }
    
        // 一種對外暴露的方式
        window.Drag = Drag;
    })();
    
    // 使用:聲明2個拖拽實例
    new Drag('target');
    new Drag('target2');
    
    不推薦,因為若拖拽中使兩元素重疊,則在拖拽元素上抬起時,可能丟失對該元素的拖拽事件,或者鼠標抬起不能釋放元素
    

    4.改進(清除瀏覽器默認事件+元素抬起問題)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>無標題文檔</title>
    <style type="text/css">
    body{margin:0;}
    #div1{width:150px; height:150px; background-color:red; position:absolute; left:0; top:0;}
    #div2{width:150px; height:150px; background-color:blue; position:absolute; left:250px; top:250px;}
    </style>
    </head>
    <body style="height:2000px;">
    <div id="div1">div div div</div>
    <div id="div2"></div>
    <script src="myJS.js"></script>
    <script>
    $("div1").onmousedown = function(ev){
        var ev = ev || event;
        var scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
        var scrollLeft =  document.body.scrollLeft || document.documentElement.scrollLeft;
        var disX = ev.clientX + scrollLeft - $("div1").offsetLeft;
        var disY = ev.clientY + scrollTop - $("div1").offsetTop;
        document.onmousemove = function(ev){
            var ev = ev || event;
            var scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
            var scrollLeft =  document.body.scrollLeft || document.documentElement.scrollLeft;
            $("div1").style.left = ev.clientX + scrollLeft - disX + "px";
            $("div1").style.top = ev.clientY +scrollTop - disY + "px";
        }
        document.onmouseup = function(){
            if($("div1").releaseCapture){$("div1").releaseCapture()};
            document.onmousemove = document.onmouseup = null;
        }
        if($("div1").setCapture){$("div1").setCapture()};
        return false;
    }
    </script>
    </body>
    </html>

    5.帶有范圍的拖拽

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>無標題文檔</title>
    <style type="text/css">
    body{margin:0;}
    #div1{width:150px; height:150px; background-color:red; position:absolute; left:0; top:0;}
    #div2{width:150px; height:150px; background-color:blue; position:absolute; left:250px; top:250px;}
    </style>
    </head>
    <body style="height:2000px;">
    <div id="div1">div div div</div>
    <div id="div2"></div>
    <script src="myJS.js"></script>
    <script>
    $("div1").onmousedown = function(ev){
        var ev = ev || event;
        var scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
        var scrollLeft =  document.body.scrollLeft || document.documentElement.scrollLeft;
        var disX = ev.clientX + scrollLeft - $("div1").offsetLeft;
        var disY = ev.clientY + scrollTop - $("div1").offsetTop;
        document.onmousemove = function(ev){
            var ev = ev || event;
            var scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
            var scrollLeft =  document.body.scrollLeft || document.documentElement.scrollLeft;
            var winW = document.documentElement.clientWidth;
            var winH = document.documentElement.clientHeight;
            var nowLeft = ev.clientX + scrollLeft - disX;
            var nowTop = ev.clientY +scrollTop - disY;
            if(nowLeft<=0){
                nowLeft = 0;
            }else if(nowLeft>=winW-$("div1").offsetWidth){
                nowLeft = winW-$("div1").offsetWidth;
            }
            $("div1").style.left = nowLeft + "px";
            if(nowTop<=0){
                nowTop = 0;
            }else if(nowTop>=winH-$("div1").offsetHeight){
                nowTop = winH-$("div1").offsetHeight;
            }
            $("div1").style.top = nowTop + "px";
        }
        document.onmouseup = function(){
            if($("div1").releaseCapture){$("div1").releaseCapture()};
            document.onmousemove = document.onmouseup = null;
        }
        if($("div1").setCapture){$("div1").setCapture()};
        return false;
    }
    </script>
    </body>
    </html>

    (七)promise

    1. 當我們想要確保某代碼在誰誰之后執行時,我們可以利用函數調用棧,將我們想要執行的代碼放入回調函數中。
        // 一個簡單的封裝
        function want() {
            console.log('這是你想要執行的代碼');
        }
    
        function fn(want) {
            console.log('這里表示執行了一大堆各種代碼');
    
            // 其他代碼執行完畢,最后執行回調函數
            want && want();
        }
    
        fn(want);
        //這里表示執行了一大堆各種代碼
        //這是你想要執行的代碼

    還可以利用隊列機制。

    function want() {
        console.log('這是你想要執行的代碼');
    }
    
    function fn(want) {
        // 將想要執行的代碼放入隊列中,根據事件循環的機制,我們就不用非得將它放到最后面了,由你自由選擇
        want && setTimeout(want, 0);
        console.log('這里表示執行了一大堆各種代碼');
    }
    
    fn(want);
    //這里表示執行了一大堆各種代碼
    //這是你想要執行的代碼

    2.promise機制

            function want() {
                console.log('這是你想要執行的代碼');
            }
    
            function fn(want) {
                console.log('這里表示執行了一大堆各種代碼');
    
                // 返回Promise對象
                return new Promise(function(resolve, reject) {
                    if (typeof want == 'function') {
                        resolve(want);
                    } else {
                        reject('TypeError: '+ want +'不是一個函數')
                    }
                })
            }
    
            fn(want).then(function(want) {
                want();
            })
            //這里表示執行了一大堆各種代碼
            //這是你想要執行的代碼
    
            fn('1234').catch(function(err) {
                console.log(err);
            })
            //這是你想要執行的代碼
            //TypeError: 1234不是一個函數

    (1)Promise對象有三種狀態,他們分別是:

    pending: 等待中,或者進行中,表示還沒有得到結果
    
    resolved(Fulfilled): 已經完成,表示得到了我們想要的結果,可以繼續往下執行
    
    rejected: 也表示得到結果,但是由于結果并非我們所愿,因此拒絕執行
    
    這三種狀態不受外界影響,而且狀態只能從pending改變為resolved或者rejected,并且不可逆。
    

    (2)Promise對象的構造函數中,將一個函數作為第一個參數。而這個函數,就是用來處理Promise的狀態變化。

    new Promise(function(resolve, reject) {
        if(true) { resolve() };
        if(false) { reject() };
    })
    

    上面的resolve和reject都為一個函數,他們的作用分別是將狀態修改為resolved和rejected。

    (3)Promise對象中的then方法,可以接收構造函數中處理的狀態變化,并分別對應執行。then方法有2個參數,第一個函數接收resolved狀態的執行,第二個參數接收reject狀態的執行。

    function fn(num) {
        return new Promise(function(resolve, reject) {
            if (typeof num == 'number') {
                resolve();
            } else {
                reject();
            }
        }).then(function() {
            console.log('參數是一個number值');
        }, function() {
            console.log('參數不是一個number值');
        })
    }
    
    fn('hahha');//參數不是一個number值
    fn(1234);//參數是一個number值

    then方法的執行結果也會返回一個Promise對象。因此我們可以進行then的鏈式執行,這也是解決回調地獄的主要方式。

    (4)

    function fn(num) {
        return new Promise(function(resolve, reject) {
            if (typeof num == 'number') {
                resolve();
            } else {
                reject();
            }
        })
        .then(function() {
            console.log('參數是一個number值');
        })
        .then(null, function() {
            console.log('參數不是一個number值');
        })
    }
    
    fn('hahha');
    fn(1234);
    //then(null, function() {}) 就等同于catch(function() {})
    

    (4)promise數據傳遞

    var fn = function(num) {
        return new Promise(function(resolve, reject) {
            if (typeof num == 'number') {
                resolve(num);
            } else {
                reject('TypeError');
            }
        })
    }
    
    fn(2).then(function(num) {
        console.log('first: ' + num);
        return num + 1;
    })
    .then(function(num) {
        console.log('second: ' + num);
        return num + 1;
    })
    .then(function(num) {
        console.log('third: ' + num);
        return num + 1;
    });
    
    // 輸出結果
    first: 2
    second: 3
    third: 4
    

    (5)基于promise對ajax封裝

    var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
    
    // 封裝一個get請求的方法
    function getJSON(url) {
        return new Promise(function(resolve, reject) {
            var XHR = new XMLHttpRequest();
            XHR.open('GET', url, true);
            XHR.send();
    
            XHR.onreadystatechange = function() {
                if (XHR.readyState == 4) {
                    if (XHR.status == 200) {
                        try {
                            var response = JSON.parse(XHR.responseText);
                            resolve(response);
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        reject(new Error(XHR.statusText));
                    }
                }
            }
        })
    }
    
    getJSON(url).then(resp => console.log(resp));

    3.Promise.all

    當有一個ajax請求,它的參數需要另外2個甚至更多請求都有返回結果之后才能確定,那么這個時候,就需要用到Promise.all來幫助我們應對這個場景。

    Promise.all接收一個Promise對象組成的數組作為參數,當這個數組所有的Promise對象狀態都變成resolved或者rejected的時候,它才會去調用then方法。

    var p1 = Promise.resolve(3);
    var p2 = 1337;
    var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "foo");
    }); 
    
    Promise.all([p1, p2, p3]).then(values => { 
    console.log(values); // [3, 1337, "foo"] 
    });
    

    4.Promise.race

    與Promise.all相似的是,Promise.race都是以一個Promise對象組成的數組作為參數,不同的是,只要當數組中的其中一個Promsie狀態變成resolved或者rejected時,就可以調用.then方法了。而傳遞給then方法的值也會有所不同,大家可以再瀏覽器中運行下面的例子與上面的例子進行對比。

      var p1 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, "one"); 
    });
    var p2 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, "two"); 
    });
    
    Promise.race([p1, p2]).then(function(value) {
      console.log(value); // "two"
      // 兩個都完成,但 p2 更快
    });
    
    var p3 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, "three");
    });
    var p4 = new Promise(function(resolve, reject) { 
        setTimeout(reject, 500, "four"); 
    });
    
    Promise.race([p3, p4]).then(function(value) {
      console.log(value); // "three"
      // p3 更快,所以它完成了              
    }, function(reason) {
      // 未被調用
    });
    
    var p5 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, "five"); 
    });
    var p6 = new Promise(function(resolve, reject) { 
        setTimeout(reject, 100, "six");
    });
    
    Promise.race([p5, p6]).then(function(value) {
      // 未被調用             
    }, function(reason) {
      console.log(reason); // "six"
      // p6 更快,所以它失敗了
    });
    

    (八)循環機制

    1.(1)Javascript有一個main thread 主進程和call-stack(一個調用堆棧),在對一個調用堆棧中的task處理的時候,其他的都要等著。

    (2)當在執行過程中遇到一些類似于setTimeout等異步操作的時候,會交給瀏覽器的其他模塊(以webkit為例,是webcore模塊)進行處理

    (3)當到達setTimeout指定的延時執行的時間之后,task(回調函數)會放入到任務隊列之中。一般不同的異步任務的回調函數會放入不同的任務隊列之中。

    (4)等到調用棧中所有task執行完畢之后,接著去執行任務隊列之中的task(回調函數)。

    2.輸入圖片說明

    (1)調用棧中遇到DOM操作、ajax請求以及setTimeout等WebAPIs的時候就會交給瀏覽器內核的其他模塊進行處理(webkit內核在Javasctipt執行引擎之外,有一個重要的模塊是webcore模塊。對于圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding、network、timer模塊來處理底層實現。)

    (2)等到這些模塊處理完這些操作的時候,將回調函數放入任務隊列中

    (3)等棧中的task執行完之后,再去執行任務隊列之中的回調函數。

    3.例子:事件循環機制究竟是怎么執行setTimeout的。

    1.首先main()函數的執行上下文入棧

    輸入圖片說明

    2.代碼接著執行,遇到console.log(‘Hi’),此時log(‘Hi’)入棧,console.log方法只是一個webkit內核支持的普通的方法,所以log(‘Hi’)方法立即被執行。此時輸出’Hi’。

    輸入圖片說明

    3.當遇到setTimeout的時候,執行引擎將其添加到棧中。

    輸入圖片說明

    4.調用棧發現setTimeout是之前提到的WebAPIs中的API,因此將其出棧之后將延時執行的函數交給瀏覽器的timer模塊進行處理。

    輸入圖片說明

    5.timer模塊去處理延時執行的函數,此時執行引擎接著執行將log(‘SJS’)添加到棧中,此時輸出’SJS’。

    輸入圖片說明

    6.當timer模塊中延時方法規定的時間到了之后就將其放入到任務隊列之中,此時調用棧中的task已經全部執行完畢。

    輸入圖片說明

    輸入圖片說明

    7.調用棧中的task執行完畢之后,執行引擎會接著看執行任務隊列中是否有需要執行的回調函數。這里的cb函數被執行引擎添加到調用棧中,接著執行里面的代碼,輸出’there’。等到執行結束之后再出棧。

    4.總結:

    (1)所有代碼都要在函數調用棧中執行
    (2)當遇到那三種APIs的時候,會交給瀏覽器內核的其他模塊進行處理
    (3)任務隊列用來存放回調函數
    (4)等到調用棧中任務執行完,再回去執行任務隊列中任務
    

    5.例子

    for (var i = 0; i < 5; i++) {
        setTimeout(function() {
          console.log(new Date, i);
        }, 1000);
    }
    console.log(new Date, i);
    

    (1)當i=0,1,2,3,4,執行棧執行循環體里面的代碼,發現是setTimeout,將其出棧之后把延時執行的函數交給Timer模塊進行處理。

    (2)當i=5的時候,不滿足條件,因此for循環結束,console.log(new Date, i)入棧,此時的i已經變成了5。因此輸出5。

    (3)此時原設定的等待時間1s已經過去,timer模塊將5個回調函數按照注冊的順序返回給任務隊列。

    (4)因為調用棧中任務已執行完,所以執行引擎去執行任務隊列中的函數,5個function依次入棧執行之后再出棧,此時的i已經變成了5。因此幾乎同時輸出5個5。

    (5)其實只有輸出第一個5之后需要等待1s,這1s的時間是timer模塊需要等到的規定的1s時間之后才將回調函數交給任務隊列。等執行棧執行完畢之后再去執行任務對列中的5個回調函數。這期間是不需要等待1s的。結果:5 -> 5,5,5,5,5,即第1個 5 直接輸出,1s之后,輸出 5個5;

    6.宏任務與微任務例子

    (function test() {
        setTimeout(function() {console.log(4)}, 0);
        new Promise(function executor(resolve) {
            console.log(1);
            for( var i=0 ; i<10000 ; i++ ) {
                i == 9999 && resolve();
            }
            console.log(2);
        }).then(function() {
            console.log(5);
        });
        console.log(3);
    })()
    

    (1)setTimeout和Promise都被稱之為任務源,來自不同任務源的回調函數會被放進不同的任務隊列里面。

    (2)Promise的第一個參數并不會被放進Promise的任務隊列之中,而會在當前隊列就執行。

    (3)setTimeout和Promise(不含then)的任務隊列叫做macro-task(宏任務)

    小結:

    ★macro-task包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
    
    ★micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver
    

    其中上面的setImmediate和process.nextTick是Node.JS里面的API,瀏覽器里面并沒有

    事件循環順序

    (1)script->全局->宏任務(先將它交給處理他的模塊,在將它放入宏任務隊列)->微任務(將回調放入微任務隊列),知道函數調用棧只剩全局

    (2)先執行宏任務的一個隊列,再執行所有的微任務

    (3)循環(2)

    案例分析

    (1)首先,script任務源先執行,全局上下文入棧。

    (2)script任務源的代碼在執行時遇到setTimeout,作為一個macro-task,將其回調函數放入自己的隊列之中。

    (3)script任務源的代碼在執行時遇到Promise實例。Promise構造函數中的第一個參數是在當前任務直接執行不會被放入隊列之中,因此此時輸出 1 。

    (4)在for循環里面遇到resolve函數,函數入棧執行之后出棧,此時Promise的狀態變成Fulfilled(執行)。代碼接著執行遇到console.log(2),輸出2。

    (5)接著執行,代碼遇到then方法,其回調函數作為micro-task入棧,進入Promise的任務隊列之中。

    (6).代碼接著執行,此時遇到console.log(3),輸出3。

    (7)輸出3之后第一個宏任務script的代碼執行完畢。接著執行所有微任務(then),輸出5

    (8).這時候所有的micro-task執行完畢,第一輪循環結束。第二輪循環從setTimeout的任務隊列開始,setTimeout的回調函數入棧執行完畢之后出棧,此時輸出4。

    ==》如下圖:

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    輸入圖片說明

    結論:

    (1)不同的任務會放進不同的任務隊列之中。

    (2)當有多個macro-task(micro-task)隊列時,事件循環的順序是按上文macro-task(micro-task)的分類中書寫的順序執行的。


    面試小結


    不適用箭頭函數的時候

    下面我就總結一下什么情況下不該使用箭頭函數。

    1.在對象上定義函數

    先來看下面這段代碼

    var obj = {  
        array: [1, 2, 3],
        sum: () => {
            console.log(this === window); // => true
            return this.array.reduce((result, item) => result + item);
        }
    };
    
    // Throws "TypeError: Cannot read property 'reduce' of undefined"
    obj.sum();
    

    sum 方法定義在 obj 對象上,當調用的時候我們發現拋出了一個 TypeError ,因為函數中的 this 是 window 對象,所以 this.array 也就是 undefined 。原因也很簡單,相信只要了解過es6 箭頭函數的都知道

    箭頭函數沒有它自己的this值,箭頭函數內的this值繼承自外圍作用域

    解決方法也很簡單,就是不用唄。這里可以用es6里函數表達式的簡潔語法,在這種情況下,this值就取決于函數的調用方式了。

    var obj = {  
        array: [1, 2, 3],
        sum() {
            console.log(this === obj); // => true
            return this.array.reduce((result, item) => result + item);
        }
    };
    
    obj.sum(); // => 6
    

    通過object.method()語法調用的方法使用非箭頭函數定義,這些函數需要從調用者的作用域中獲取一個有意義的this值。

    2.在原型上定義函數

    在對象原型上定義函數也是遵循著一樣的規則

    function Person (pName) {
        this.pName = pName;
    }
    
    Person.prototype.sayName = () => {
        console.log(this === window); // => true
        return this.pName;
    }
    
    var person = new Person('wdg');
    
    person.sayName(); // => undefined
    

    使用function函數表達式

    function Person (pName) {
        this.pName = pName;
    }
    
    Person.prototype.sayName = function () {
        console.log(this === person); // => true
        return this.pName;
    }
    
    var person = new Person('wdg');
    
    person.sayName(); // => wdg
    

    所以給對象原型掛載方法時,使用function函數表達式

    3.動態上下文中的回調函數

    this 是js中非常強大的特點,他讓函數可以根據其調用方式動態的改變上下文,然后箭頭函數直接在聲明時就綁定了this對象,所以不再是動態的。

    在客戶端,在dom元素上綁定事件監聽函數是非常普遍的行為,在dom事件被觸發時,回調函數中的this指向該dom,可當我們使用箭頭函數時:

    var button = document.getElementById('myButton');  
    button.addEventListener('click', () => {  
        console.log(this === window); // => true
        this.innerHTML = 'Clicked button';
    });
    

    因為這個回調的箭頭函數是在全局上下文中被定義的,所以他的this是window。所以當this是由目標對象決定時,我們應該使用函數表達式:

    var button = document.getElementById('myButton');  
    button.addEventListener('click', function() {  
        console.log(this === button); // => true
        this.innerHTML = 'Clicked button';
    });
    

    4.構造函數中

    在構造函數中,this指向新創建的對象實例

    this instanceOf MyFunction === true
    

    需要注意的是,構造函數不能使用箭頭函數,如果這樣做會拋出異常

    var Person = (name) => {
        this.name = name;
    }
    
    // Uncaught TypeError: Person is not a constructor
    var person = new Person('wdg');
    

    理論上來說也是不能這么做的,因為箭頭函數在創建時this對象就綁定了,更不會指向對象實例。

    5.太簡短的(難以理解)函數

    箭頭函數可以讓語句寫的非常的簡潔,但是一個真實的項目,一般由多個開發者共同協作完成,就算由單人完成,后期也并不一定是同一個人維護,箭頭函數有時候并不會讓人很好的理解,比如

    let multiply = (a, b) => b === undefined ? b => a * b : a * b;
    
    let double = multiply(2);
    
    double(3); // => 6
    
    multiply(2, 3); // =>6
    

    這個函數的作用就是當只有一個參數 a 時,返回接受一個參數 b 返回 a*b 的函數,接收兩個參數時直接返回乘積,這個函數可以很好的工作并且看起很簡潔,但是從第一眼看去并不是很好理解。

    為了讓這個函數更好的讓人理解,我們可以為這個箭頭函數加一對花括號,并加上 return 語句,或者直接使用函數表達式:

    function multiply(a, b) {
        if (b === undefined) {
            return function (b) {
                return a * b;
            }
        }
        return a * b;
    }
    
    let double = multiply(2);
    
    double(3); // => 6
    multiply(2, 3); // => 6
    

    總結

    毫無疑問,箭頭函數帶來了很多便利。恰當的使用箭頭函數可以讓我們避免使用早期的 .bind() 函數或者需要固定上下文的地方并且讓代碼更加簡潔。

    箭頭函數也有一些不便利的地方。我們在需要動態上下文的地方不能使用箭頭函數:定義需要動態上下文的函數,構造函數,需要 this 對象作為目標的回調函數以及用箭頭函數難以理解的語句。在其他情況下,請盡情的使用箭頭函數。

    關于Call,apply能否給箭頭函數賦值


    1.使用call,apply,bind改變函數的this指向

    let obj = {
      name: "qianzhixiang",
      func: function(a,b){
          console.log(this.name,a,b);
      }
    };
    obj.func(1,2); // qianzhixiang 1 2
    let func = obj.func;
    func(1,2); //   1 2
    let func_ = func.bind(obj);
    func_(1,2);// qianzhixiang 1 2
    func(1,2);//   1 2
    func.call(obj,1,2);// qianzhixiang 1 2
    func.apply(obj,[1,2]);// qianzhixiang 1 2
    

    2.使用call,apply,bind改變箭頭函數的this指向?

    let obj = {
      name: "qianzhixiang",
      func: (a,b) => {
          console.log(this.name,a,b);
      }
    };
    obj.func(1,2); // 1 2
    let func = obj.func;
    func(1,2); //   1 2
    let func_ = func.bind(obj);
    func_(1,2);//  1 2
    func(1,2);//   1 2
    func.call(obj,1,2);// 1 2
    func.apply(obj,[1,2]);//  1 2
    

    因為箭頭函數中的,apply、call方法指向的對象會被默認忽略。故對箭頭函數,call、apply不能更改其this指向。


    版權聲明:本文為cjg214原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/cjg214/article/details/77963276

    智能推薦

    公司內部Wiki及搭建wiki系統-confluence

    1. 前言 Wiki 是一個協同著作平臺或稱開放編輯系統。我們可以用Wiki來建設幫助系統,知識庫系統。國內公共wiki最著名就是百度百科.那公司內部為什么要使用wiki呢? 2.內部wiki的作用 1.鼓勵分享 分享是互聯網的精神,wiki能將互幫互助融入到企業文化之中。除了工作上的成就,讓員工在工作之余,能夠體會到幫助他人的成就和快樂。 2.提升員工個人能力 很多難懂深奧的問題,專研半天終于搞...

    lnmp服務一搭建wiki

    1.創建wiki數據庫與用戶并授權 2.將前面配置文件放到一個目錄中 3.下載wiki安裝包 4.解壓wiki安裝包 5.授權 6.網頁訪問,安裝 1.創建wiki數據庫與用戶并授權 2.將前面配置文件放到一個目錄中 3.下載wiki安裝包 4.解壓wiki安裝包 5.授權 6.網頁訪問,安裝...

    LAMP環境搭建media wiki

    準備工作 安裝apache(httpd) 編譯安裝httpd 優化路徑并啟動: 驗證: 安裝MySQL 編譯安裝mysql 配置 配置數據庫: 安裝php 編譯安裝 修改配置 驗證LAMP 修改配置 驗證LNMP 出現此頁面證明成功 安裝mediaWiki 瀏覽器輸入:IP/wiki/mw-config/index.php 開始配置mediaWiki 配置完成:...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    猜你喜歡

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    精品国产乱码久久久久久蜜桃不卡