前端學習筆記--JavaScript、Vue、Webpack

JavaScript

不一樣數據類型間的比較

答案:true true falsejavascript

  • 解析:

第一個輸出:將[]轉爲布爾類型,爲true。只有如下類型轉爲布爾值爲false:Boolean(null)、Boolean(undefined)、Boolean(0)、Boolean(‘’)、Boolean(NaN)css

第二和第三:其餘類型與布爾類型比較,將兩個類型轉爲數字再進行比較。Number([])=0,Number(false)=0,因此第二個輸出true。Number({})=NaN,NaN==0 --> false,因此第三個輸出falsehtml

Number([2]) //2
Number(['x']) //NaN
Number(undefined);  //NaN
Number(null);   //0
null+1=1
undefined+1=NaN

undefined==false;   //false
false==''   //true

null==undefined;    //true,都轉爲boolean類型
null=undefined;     //false,不強制轉換
複製代碼

FormData

  • 建立FormData對象
    • 像建立對象同樣建立,無參數
    • 將已有的表單對象做爲參數來建立FormData對象
HTML部分
<form action="" id='form'>
        <label for="">
            姓名: <input type="text" name="name">
        </label>
        <label for="">
            文件:<input id="file" type="file" name="file">
        </label>
        <label for="">
            <input type="button" value="保存">
        </label>
</form>
複製代碼
JS部分
var form=document.getElementById('form');
var formdata=new FormData(form);
var name=formdata.get(name);
var file=formdata.get(file);
formdata.append('token','sdcsdc');
//提交數據
var xhr=new XMLHttpRequest();
    xhr.open("post","http://127.0.0.1/adv");
    xhr.send(formdata);
    xhr.onload=function(){
        if(xhr.readyState==4&&xhr.status==200){
            //...
        }
    }
複製代碼
  • 往FormData對象添加數據 -- 數據類型是鍵值對
    • FormData.append( key,value [,filename] )
    • filename:當添加的數據是file對象就能夠添加這個參數,用來設置傳遞給服務器的文件名稱(可選參數)
var formdata=new FormData();
formdata.append('name','jack');
formdata.append('name','rose');

formdata.get('name');   //jack
formdata.getAll('name');    //[jack,rose]
複製代碼
  • 獲取數據
    • FormData.get(key)
    • FormData.getAll(key)
  • 判斷是否存在某個值
    • FormData.has(key)
  • 遍歷數據
    • FormData.keys():返回全部數據的鍵值
    • FormData.values():返回全部數據的值
    • FormData.entries():返回FormData對象的迭代器
formData.append("k1", "v1");
formData.append("k1", "v2");
formData.append("k2", "v1");

var i = formData.entries();

i.next(); // {done:false, value:["k1", "v1"]}
i.next(); // {done:fase, value:["k1", "v2"]}
i.next(); // {done:fase, value:["k2", "v1"]}
i.next(); // {done:true, value:undefined}

//返回的對象的value屬性以數組的形式,第一個是key,第二個爲value

//以循環的形式
for(var pair of formData.entries()){    //pair是每次迭代獲取到的對象裏的value
    console.log(pair[0]+':'+pair[1])
    //k1:v1
    //k1:v2
    //k2:v1
}
複製代碼
  • 修改數據
    • FormData.set(key,value) :若key不存在會自動建立
  • 刪除數據
    • FormData.delete(key):即便key不存在也不會出錯

js垃圾回收機制

  • 標記清除:給存儲在內存中的變量都添加標記,而後去掉那些處在環境中的變量和被環境中的變量引用的變量的標記,剩下被標記的就是將要刪除的變量。而後垃圾回收機制到下一個週期運行時,將釋放這些變量的內存,回收他們佔用的空間。
  • 引用計數:語言引擎有一張引用表,保存內存裏全部資源的引用次數。若是一個值的引用次數是0,則表示這個值再也不用到了,所以能夠將這塊內存釋放。

js垃圾回收機制前端

線程與進程的區別

  • 進程是CPU資源分配的最小單位
  • 線程是程序執行時的最小單位,是CPU調度和分派的最小單位

  • 一個程序至少有一個進程,一個進程至少有一個線程。
  • 一個進程能夠由多個線程組成,線程間共享進程的全部資源,每一個線程有本身的局部變量和堆棧,而進程有本身的獨立地址空間。
  • 線程由CPU獨立調度執行,多CPU環境下就容許多個線程同時執行。
  • 單線程(單個CPU運行):好比,打開任意一個瀏覽器,裏面有多個標籤頁;當某一個標籤頁系統崩潰時,其餘標籤頁也不能使用,必須關掉瀏覽器。
  • 多線程(多個cpu等運行):好比,瀏覽器中有多個標籤頁,當某一個標籤頁系統崩潰時,只是該標籤頁不能使用,不影響其餘標籤頁的正常使用。

A:-1 B:-2 C:0 D:1

答案:vue

  • 每一個線程對a均作了兩次操做:+1,-2
  • 當線程1與線程2不併發時:1執行完後a=-1,2使用-1做爲a的初值,執行完後a=-2
  • 當線程1與線程2併發時:此時讀寫衝突,至關於只有一個線程對a的讀寫最終生效,結果a=-1
  • 當線程1與線程2部分併發:1執行到第一個操做時,a=1。此時線程2開始。1的讀寫被2覆蓋,2把a=1做爲初值,結果爲0

逗號運算符,=號運行符,做用域

下面輸出結果是:java

var out = 25,
   inner = {
        out: 20,
        func: function () {
            var out = 30;
            return this.out;
        }
    };
console.log((inner.func, inner.func)());
console.log(inner.func());
console.log((inner.func)());
console.log((inner.func = inner.func)());
複製代碼

結果:25 20 20 25node

  • 第一個輸出:逗號運算符是運算前面的,返回最後一個的結果,此時返回的是最後一個inner.func的結果,便是一個匿名函數。

例子webpack

let a=1,b=2,k=0;
console.log( (a++,b++,k++) );   //輸出0,注意是要把整個括起來,而後將計算後的結果返回
console.log(a); //2
console.log(b); //3
console.log(k); //1
複製代碼
  • 第二個輸出和第三個輸出:都是對象調用函數,函數的上下文this是對象
  • 第四個輸出:等號運算符返回的是賦值後的結果,即返回一個匿名函數

例子:ios

let a=3,b;
console.log(b=a);   //輸出3
複製代碼

引用類型的賦值,等號運算符與.運算符的優先級問題

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x);
console.log(b.x);
複製代碼

答案:nginx

undefined
{n:2}
複製代碼

分析:

  • b和a指向同一地址,對象{n:1}的地址
  • 在a.x = a = {n: 2};中,因爲.號運算符優先級更高,因此先進行a.x
  • 那到底是a.x=a,a={n:2}仍是a = {n: 2} && a.x = {n:2} 仍是 a.x = {n:2} && a= {n:2}。藉助Proxy來講明:
const obj = new Proxy({}, {
  set(target, key, value, r) {
    console.log(key, value)
    if (key === 'a') Reflect.set(target, key, 'isA', r);
    else Reflect.set(target, key, value, r);
  }
});

obj.b = obj.a= {n: 1};
// 輸出:
// "a" {n: 1}
// "b" {n: 1}

obj.a; // isA
obj.b; // {n: 1}
複製代碼
能夠得出 賦值的順序是從右邊開始到左邊的。
並且是直接 a = {n: 1}, a.x = {n:1 },而不是 a.x = a 這樣去賦值
複製代碼

再借助 Proxy 來分析一開始part1這道題,用obj.a, obj.b 來代替原題目的 a和b。

var obj = new Proxy({}, {
        get: function (target, key, receiver) {
            console.log(`getting ${key}!`);
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            console.log(`setting ${key}!`);
            return Reflect.set(target, key, value, receiver);
        }
    });

    obj.a = {n: 1 };// setting a;
    obj.b = obj.a; // getting a; setting b;
    obj.a.x = obj.a = {n:2 }; // getting a; setting a;
    
*****************分割線**********************************    
    console.log(obj.a);     //{n;2}
    obj.a.n=3;          //getting a
    console.log(obj.b); //getting b {n:1,x:{n:3}}
複製代碼
第一部分:
能夠看到obj.a.x = obj.a = {n: 2}這段語句執行時,會先輸出一個 getting a 再輸出 setting a。
這就意味着在對 obj.a.x 賦值時,程序是先獲取 obj.a指向的對象的內存地址,此時觸發了 getting a,
而後再對右邊 obj.a 進行賦值,觸發了 setting a, 賦值完最後一步纔是對 obj.a.x賦值 {n:2 }。
注意:最後一步對obj.a.x賦值時,不會再去讀取obj的a,則說明此時的a還是指向原來的地址-- 對象{n:1}的地址,也是obj.b指向的地址。
賦值以後,此時obj.a(和obj.b再也不指向同一地址)和obj.b.x指向的是同一地址 -- {n:2}的地址
複製代碼
第二部分:
在分割線之下,obj.a.n=3; 讀取的a再賦值,改變了指向的對象{n:2}的值
而obj.b.x也指向該對象
複製代碼

參考

變量提高,(字符串/變量 in obj)的含義

if (!("a" in window)) {
    var a = 1;
}
alert(a);   //undefined
複製代碼
  • "a" in window:意思是window對象是否有a這個屬性,寫成a in window也能夠

此時代碼的意思是,若是沒有這個屬性,則建立這個屬性且賦值。

但if不是代碼塊,存在變量提高,因此上面代碼實際爲

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a); 
複製代碼

做用域

函數內屬於一個做用域,try和catch不屬於一個做用域

(function f() {
        try{
           throw new Error()
        }catch (x) {
            var x=1,y=2;
            var t=3;
            console.log(x);
        }
        console.log(t); //3
        console.log(x); //undefined
        console.log(y); //2
    })();
複製代碼
在f函數中,x、y、t會變量提高
在catch中,var x=1,就近原則,此時對x的賦值實際上是對形參的賦值,不是f函數裏的x,因此函數f的x在catch外訪問依舊是undefined
複製代碼

函數聲明、函數表達式

聲明變量b是一個函數,使用函數表達式

一、let b=function(){}

二、let b=function f(){} //能夠爲函數起一個名字,可是'f'不屬於window的屬性,則沒法調用f(),至關於匿名

下面程序的輸出結果?

var a = 1;
var b = function a(x) {
  x && a(--x);
};
alert(a);   //1
console.log(b(a))  //undefined 由於函數沒有return,有return的話就是0

/* 在函數外部,a是number類型,在外面調用函數a會出錯,但在函數內部調用不會出錯 上面其實等同於 var b=function(x){...} */
複製代碼
  • 函數聲明和變量聲明都存在提高,但函數表達式不會,只會提高引用的變量。
  • 若是函數名和變量重名,函數聲明會覆蓋變量聲明,但賦值不會,誰後就是那個值。即函數聲明級別比變量聲明高。

例子:

function a(){console.log('function')}
var a;
console.log(typeof a)   //function

//若是變量賦值了
function a(){console.log('function')}
var a=1;
console.log(typeof a)   //number
複製代碼

函數內的變量提高,與全局變量重名

var a = 4;
    function b() {
        a = 3;
        console.log(a);
        function a(){};
    }
    b();    //3
    console.log(a); //4
複製代碼

函數b內的函數a會提高,而後被a=3覆蓋了值,因此輸出3

挖坑:

var a = 4;
    function b() {
        a = 3;
        
    }
console.log(a); //4
複製代碼

爲何不是輸出3?—— 由於沒有調用b()....

數據類型

  • 基本數據類型:string , number , boolean , undefined , null , symbol(ES6)
  • 引用類型:對象、數組、函數

typeof返回的數據類型

string、number、boolean、undefined、object、function、symbol

null與undefined

  • null指的是一個對象爲空,即變量是有值的,值爲null
  • undefined:表示的是一個變量/屬性沒有設置值。
  • null==undefinde —— true, null===undefinde —— false

JSON.stringify轉化null與undefined

JSON.stringify({1:undefined}) //「{}」
JSON.stringify({0:null})    //「{「0」:null}」
複製代碼

返回undefined的狀況

  • 當一個變量未賦值
  • 訪問對象的某個屬性不存在
  • 函數沒有返回值
  • 函數應該傳遞的參數沒有提供,該參數等於undefined

symbol

  • symbol值是經過Symbol函數生成,沒有new,生成的Symbol是一個相似於字符串的原始類型的值。
  • Symbol(para):para能夠是字符串/對象,對象會調用toString方法轉爲字符串
  • 傳入的參數只是對當前Symbol值的描述,不是返回的值,因此即便傳入相同的參數,Symbol的返回值也是不一樣的
var sm=Symbol('xx');
console.log(sm);   //Symbol('xx')
typeof sm;  //'symbol'

var ss1=Symbol('xx');
console.log(ss==ss1);  //false
ss=ss1;     //由於是基本數據類型,因此能夠用=令兩個值相等
console.log(ss===ss1);   //true 
複製代碼

js常見的內置對象

object、String、Number、Boolean、Math、Array、Function、JSON

如何理解JSON

  • JSON是JS一個內置對象,含有兩個函數,stringify和parse
  • 同時JSON也是一種輕量級的數據交換格式,以{}括起來
  • JSON 與 JS 對象的關係:JSON是JS對象的字符串表示法,它使用文本表示一個 JS 對象的信息,本質是一個字符串。
  • 任何支持的類型均可以經過 JSON 來表示,例如字符串、數字、對象、數組等。

JS的內置函數

String、Number、Boolean、Object、Function、RegExp、Error、Date、Array

日期Date

Date.now():返回當前時間毫秒數

var dt=new Date();
dt.getFullYear();   //年
dt.getMonth();      //月(0-11)
dt.getDate();       //日(1-31),返回一個月中的某一天

dt.getDay();        //天(0-6),返回一星期中的第幾天(0表示星期日)

dt.getHours()       //小時(0-23)
dt.getMinutes()     //分鐘(0-59)
dt.getSeconds()     //秒數(0-59)

dt.getTime()        //返回從1970/1/1 至今的毫秒數

##有對應的set方法
複製代碼

對象的鍵

var a={},
b={key:'b'},
c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);  //456
複製代碼

解析:

由於鍵的名稱只能是字符串,b/c做爲鍵會調用toString獲得的都是[object Object],因此a[b]和a[c]都是a["[object Object]"]

複製代碼

this

  • 函數直接加()調用,this=window
  • 對象打點調用實例函數,this是這個對象
  • 從數組/類數組中枚舉出函數(即函數是數組的元素)調用,this是這個數組
function fun1(fn){  //第二步
    arguments[0](3,4);  //類數組枚舉調用傳入的fun2(3,4)
}
function fun2(){
    alert(this.length); //此時this是fun1的arguments,表示fun1傳入的實參個數--5
    alert(arguments.length); //此時表示fun2傳入的實參個數-- 2
}
fun1(fun2,5,6,7,8); //第一步
複製代碼
  • 定時器的回調函數,this=window
  • 事件處理函數,this是綁定事件的元素
  • 用new調用函數,this是建立的實例對象
  • call、apply綁定this,若是沒有傳參或傳的是null,則this指向window

setTimeOut

for(var i=0;i<5;i++){
    setTimeout(console.log(i),0)    //第一個參數不是函數
    //0
    //1
    //2
    //3
    //4
}
複製代碼

數組的方法

  • 對於那些遍歷數組的每一項,而後每一項都去執行回調的方法,都要有return,除了forEach

數組的方法

  • lastIndexOf(val [,startIndex]):若是未傳入第二個參數,則從尾向頭查找第一次出現val的位置,若是指定了第二個參數,則從這個參數向前找。用這個方法能夠找某個元素 在該數組最後一次出現的位置,由於是從後往前找。

parseInt

parseInt(string [,radix]):解析一個字符串參數,並返回一個指定基數的整數

參數:

  • string:要被解析的值,若是參數不是一個字符串,則將其轉換爲字符串,使用toString,字符串開頭的空白符會被忽略。
  • radix:一個介於2-36之間的整數,表示把第一個參數看做是一個數的幾進製表示,默認爲10

返回值:

  • 返回的是第一個參數的十進制表示
若是radix爲undefined或0或未指定,
一、若是string以‘0x’或‘0X’開頭,則基數是16(16進制)
二、若是string以‘0’開頭,則基數是8或10進制,具體是哪一個進制由實現環境決定
三、若是字符串string以其餘任何值開頭,則基數是10
複製代碼

例子:

['10','10','10','10','10'].map(parseInt);
// [10, NaN, 2, 3, 4]
複製代碼

實際執行的代碼是:

['10','10','10','10','10'].map((item, index) => {
	return parseInt(item, index)
})
//第一個radix爲0,屬於上面所說的狀況,此時看item,不是以0或0x開頭,則基數是10,10的十進制就是10

//第二個 10,radix是1,表示10是1進製表示的數字,錯誤
....
複製代碼

異步過程

1)主線程發起一個異步請求

2)相應的工做線程接收到請求後告知主線程已收到(異步函數返回)

3)主線程能夠繼續執行下面的代碼,同時工做線程執行異步任務。

4)等到工做線程完成任務後,通知主線程

5)主線程收到通知後,執行必定動做(調用回調函數)。

JS引擎中負責解釋和運行js代碼的只有一個,叫作主線程,還有其餘線程,例如處理Ajax請求的線程,處理DOM事件的線程,定時器線程等工做線程(可能存在與js引擎內或外)。

綜上,能夠總結爲兩個要素:

一、發起函數(註冊函數)-- 用來發起異步過程

二、執行回調函數 -- 用來處理結果

兩個函數都是在主線程上調用的


異步任務執行完畢後須要通知主線程,這個通知機制是如何實現的?—— 消息隊列和事件循環

異步任務執行完畢後,工做線程將回調函數封裝成消息message放到消息隊列中,主線程經過事件循環過程去取出消息

  • 消息隊列:是一個先進先出的隊列,存放各類消息
  • 事件循環:指的是主線程重複的從消息隊列裏取出消息並執行的過程,取出一個消息並執行的過程就叫作一次循環。
    • 同步和異步任務分別進入不一樣的執行場所,同步進入主線程,異步進入Event table並註冊回調函數
    • 異步任務執行完畢後,會將註冊的函數加入事件隊列裏
    • 當主線程內的任務執行完畢後,會去事件隊列裏讀取消息,到主線程執行回調函數
    • 重複以上過程就是事件循環

例子:

setTimeout(fn,2000)
複製代碼

setTimeout就是發起異步過程的函數,fn就是回調函數,可是回調函數並不必定要做爲發起函數的參數,如:

var xhr = new XMLHttpRequest(); 
xhr.onreadystatechange = xxx; // 添加回調函數
xhr.open('GET', url);  //發起異步請求的函數
xhr.send(); // 發起函數
複製代碼

宏任務和微任務

除了廣義的同步任務和異步任務以外,還有宏任務和微任務

  • 宏任務:總體的js代碼、setTimeout、setInterval、setImmediate
  • 微任務:promise.then,process.nextTick(優先級比promise.then高,比它先執行)

不一樣的任務會進入不一樣的事件/任務隊列,好比setTimeoutsetInterval會進入相同的Event Queue。

先執行宏任務隊列的一個,而後再執行宏任務下的全部微任務,再去渲染,完成一次事件循環。而後再執行下一個宏任務。

事件循環的一次循環,就是執行事件隊列裏的一個任務。先執行宏任務,再執行宏任務裏的微任務,這樣就是一次循環,第一輪循環執行後,在執行下一次循環

例子

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
複製代碼

結果:1,7,6,8,2,4,3,5,9,11,10,12。

第一輪事件循環:

  • 整個js代碼做爲第一個宏任務進入事件隊列裏,遇到console.log,輸出1。
  • 遇到setTimeout,屬於異步任務,等到異步任務完成後,回調函數被分發到宏任務的事件隊列裏,咱們暫時標記爲setTimeout1
  • process.nextTick(),其回調函數被分發到微任務Event Queue中。咱們記爲process1。
  • 遇到Promise,new Promise直接執行,輸出7。then被分發到微任務Event Queue中。咱們記爲then1。
  • 又遇到了setTimeout,其回調函數被分發到宏任務Event Queue中,咱們記爲setTimeout2。此時宏任務隊列裏就有兩個回調函數了。
  • 第一輪循環的宏任務執行完後,有兩個微任務,process1和then1
  • 執行process1,輸出6。
  • 執行then1,輸出8。

第二輪事件循環:

  • 執行第二個宏任務,從setTimeout1宏任務開始,輸出2
  • 接下來遇到了process.nextTick(),一樣將其分發到微任務Event Queue中,記爲process2。
  • new Promise當即執行輸出4,then也分發到微任務Event Queue中,記爲then2。
  • 第二輪事件循環宏任務結束,咱們發現有process2和then2兩個微任務能夠執行
  • 輸出3
  • 輸出5

第三輪事件循環:

  • 只剩setTimeout2了,執行。輸出9。
  • 將process.nextTick()分發到微任務Event Queue中。記爲process3。
  • 直接執行new Promise,輸出11。 將then分發到微任務Event Queue中,記爲then3。
  • 第三輪事件循環宏任務執行結束,執行兩個微任務process3和then3
  • 輸出10
  • 輸出12

例子

process.nextTick(() => {    //微任務
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {     //微任務
    console.log('then')
  })
setImmediate(() => {    //宏任務
  console.log('setImmediate')
})
console.log('end')
複製代碼

運行結果:

end
nextTick
then
setImmediate
複製代碼

微任務裏的微任務

process.nextTick(()=>{
    console.log(1)
    process.nextTick(()=>{
        console.log(2)
    })
})
process.nextTick(()=>{
    console.log(3)
})
console.log('run')
複製代碼
run
1
3
2
複製代碼

詳解

promise

promise是異步編程的解決方案,比傳統的異步編程解決方案【事件】和【回調函數】更加合理和強大。

promise對象用於異步操做,它表示未完成且預計將來完成的異步操做

  • promise對象能夠經過Promise構造函數建立,Promise構造函數須要傳遞一個函數做爲參數,當建立Promise實例時,則會調用該函數。
  • 這個函數參數有兩個參數,resolve和reject函數,用來改變promise對象的狀態。
  • promise有三個狀態,分別是pending、fulfilled和rejected。只能從pending轉爲fulfilled或rejected,狀態肯定以後不可改變
  • promise原型上有then函數,傳遞的參數是fulfilled狀態和rejected狀態執行的回調,回調的參數就是promise的值,則resolve和reject函數的實參
  • then函數返回一個新的promise實例,該實例的狀態與回調函數的執行狀況和返回值有關。默認返回一個resolved狀態。若是拋出錯誤,則返回的promise實例就是rejected狀態。若是返回一個新的promise實例,則取決於這個promise的狀態,等待它狀態改變纔會執行下面的then的回調
  • promise原型上還有catch方法,能夠捕獲then回調拋出的錯誤
  • 原型上還有race方法,能夠傳入多個promise任務,多個任務同步執行,最終返回第一個執行結束的promise任務的結果
  • 原型上還有all方法,只有全部的promise任務都執行成功纔會調用成功的回調,只要有一個失敗則調用失敗的回調
  • 原型上還有resolve,用來建立promise實例。
    • Promise.resolve()能夠傳一個普通值,返回一個resolved類型的promise實例
    • 能夠傳入一個promise實例,返回的就是這個promise實例
    • 能夠傳入thenable對象,返回的promise實例的狀態取決於這個對象的最終狀態
  • 原型上還有reject方法,也是用來建立promise對象,不管傳入什麼值,返回的都是一個rejected類型的promise實例

在沒有 promise 以前,怎麼解決異步回調

  • 回調函數
  • 事件監聽
  • 訂閱發佈模式 資料

async與await

使異步變同步

  • async定義的函數表示該函數是一個異步函數,調用該函數時,不會阻塞其餘代碼的運行
  • 當async會封裝一個由Promise.resolve()建立的Promise實例,而後返回。若是拋出錯誤,則封裝一個Promise.reject()返回。若是顯式寫了return一個值,那這個值就是返回的promise實例的值。
  • 若是要獲取Promise的值,須要寫then函數,傳入回調
  • async函數不必定須要await,但await必須包含在async內
  • await表示等待的意思,等待後面的代碼返回結果以後再繼續指向。
  • await後面能夠是任何表達式,但一般是一個返回Promise實例的表達式
async function timeout() {
    return 'hello world'
}
console.log(timeout());
console.log('雖然在後面,可是我先執行');
複製代碼

結果

Promise {<resolved>: "hello world"}
test.html:85 雖然在後面,可是我先執行
複製代碼

獲取Promise的值

async function timeout() {
    return 'hello world'
}
timeout().then(result => {
    console.log(result);
})
console.log('雖然在後面,可是我先執行');
複製代碼

結果

雖然在後面,可是我先執行
hello world
複製代碼

await

function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result); //60
}

testResult();   
複製代碼

Ajax

var xhr=new XMLHttpRequest();
xhr.open('get','/index.js',true)    //true表示異步
xhr.onreadystatechange=function () {
    if(xhr.readyState===4)
        if(xhr.status===200){
            console.log(xhr.responseText)
        }
}
xhr.send(null); //將請求發送給服務端,僅用於post請求
複製代碼
xhr.open()的第三個參數表示是否異步
XMLHttpRequest 對象若是要用於 AJAX 的話,其 open() 方法的 async 參數必須設置爲 true

xhr.send():當使用初始化)send方法還未調用
1:(載入)已調用send方法,正在發送請求
2:(載入完成)send方法已經執行完畢,已經接收到全部響應內容
3:(交互)正在解析響應內容
4:(完成)響應內容解析完成,能夠在客戶端調用
複製代碼

如何保證cookie的安全性

  • 對保存到cookie的私密數據進行加密
  • 設置http-only=true
  • 給cookie設置有效期
  • 給cookie加個時間戳和ip戳,實際就是讓cookie在同個ip下過多少時間後失效

cookie、localStorage、sessionStorage的區別

cookie

  • 由於HTTP是無狀態的,對事務沒有記憶能力。cookie是網站用來標識用戶身份的(好比存儲用戶信息),存儲在本地的數據。
  • 在第一次請求服務端時,服務端在響應報文頭添加set-cookie屬性,屬性值便是用戶的cookie值。瀏覽器在下次請求時會自動在請求報文頭加上Cookie屬性,屬性值即由服務端產生。
  • cookie數據始終在同源的HTTP請求中攜帶(即便不須要),在瀏覽器和服務端之間來回傳輸

storage

  • sessionStorage和localStorage都是在本地保存,不會發送給服務端

共同點:

  • 都是保存在客戶端,且同源的。

數據有效時間

  • localStorage是永久性保存,除非手動刪除
  • sessionStorage保存的數據在當前窗口關閉後自動刪除
  • cookie數據在cookie設置的過時時間前一直有效,即便關閉窗口或瀏覽器

做用域不一樣:

  • sessionStorage在不一樣的瀏覽器窗口不共享
  • cookie和localStorage在全部同源窗口共享

cookie和session的區別

  • cookie是保存在客戶端,服務器可以知道其中的信息
  • session是保存在服務端,客戶端不知道其中的信息
  • cookie保存的是字符串,session保存的是對象
  • session不能設置路徑,同一個用戶在訪問一個網站的期間,全部的session在任何一個地方都能訪問到
  • cookie能設置路徑參數,那麼同一個網站的不一樣路徑下的cookie互相是訪問不到的
  • Session的實現是基於Cookie:Session技術是將數據存儲在服務器端的技術,會爲每一個客戶端都建立一塊內存空間 存儲客戶的數據,但客戶端須要每次都攜帶一個標識ID去服務器中尋找屬於本身的內存空間。

從敲入 URL 到渲染完成的整個過程

  • 用戶輸入url地址,瀏覽器根據DNS解析域名,獲得IP地址
  • 通過三次握手創建TCP鏈接
  • 瀏覽器向服務器發送HTTP請求
  • 服務端接收請求,將html文檔返回給瀏覽器
  • 瀏覽器接收文檔後,若是有壓縮則解壓處理,而後就是頁面解析
  • 瀏覽器將文檔下載下來後,便開始從上到下解析,解析完成以後,會生成DOM。若是頁面中有css,會根據css的內容造成CSSOM,而後DOM和CSSOM會生成一個渲染樹,最後瀏覽器會根據渲染樹的內容計算出各個節點在頁面中的確切大小和位置,並將其繪製在瀏覽器上。
  • 若渲染過程遇到js文件,html文檔會掛起渲染的線程,等待js文件加載和解析完畢,纔可恢復html文檔的渲染進程。

DOMContentLoaded和onload的區別

DOMContentLoaded:顧名思義,就是dom內容加載完畢。那什麼是dom內容加載完畢呢?咱們從打開一個網頁提及。當輸入一個URL,頁面的展現首先是空白的,而後過一會,頁面會展現出內容,可是頁面的有些資源好比說圖片資源還沒法看到,此時頁面是能夠正常的交互,過一段時間後,圖片才完成顯示在頁面。從頁面空白到展現出頁面內容,會觸發DOMContentLoaded事件。而這段時間就是HTML文檔被加載和解析完成。

onload:頁面上全部的資源(圖片,音頻,視頻等)被加載之後纔會觸發load事件

對url進行編碼的函數:escape,encodeURI,encodeURIComponent

  • escape(url):該方法不會對 ASCII 字母和數字進行編碼,也不會對下面這些 ASCII 標點符號進行編碼: - _ . ! ~ * ' ( ) 。其餘全部的字符都會被16進制的轉義序列替換。 所以若是想對URL編碼,最好不要使用此方法。
  • encodeURI(url):該方法的目的是對 URI 進行完整的編碼,所以對如下在 URI 中具備特殊含義的 ASCII 標點符號,encodeURI() 函數是不會進行轉義的:;/?:@&=+$,#
  • 該方法不會對 ASCII 字母和數字進行編碼,也不會對這些 ASCII 標點符號進行編碼: - _ . ! ~ * ' ( ) 。 其餘字符(好比 :;/?:@&=+$,# 這些用於分隔 URI 組件的標點符號),都是由一個或多個十六進制的轉義序列替換的。 因此能夠用來將鏈接組件(協議、主機號、端口號)間的符號轉換
var str='https://www.bilibili.com/text.html'
    console.log(escape(str));
    console.log(encodeURI(str));
    console.log(encodeURIComponent(str));
    
encodeURI("http://www.w3school.com.cn/My first/")    
複製代碼

輸出

https%3A//www.bilibili.com/text.html
test.html:59 https://www.bilibili.com/text.html
test.html:60 https%3A%2F%2Fwww.bilibili.com%2Ftext.html

http://www.w3school.com.cn/My%20first/
複製代碼

性能優化:將js放到body標籤底部

  • 問題: js會阻塞文檔的解析,頁面的渲染,但生成完整的dom樹是須要讀完整個文檔,那script不管放在哪都同樣
  • 實際: 瀏覽器爲了更好的用戶體驗,瀏覽器可以渲染不完整的dom樹和cssDom,儘快減小白屏的時間,這就是瀏覽器的first paint。
  • 總結: 因此若是把script標籤放在文檔頭部,js會阻塞後續文檔的加載,阻塞解析dom,阻塞first paint,致使白屏時間延長,可是不會減小DOMContentLoaded被觸發的時間。

參考

document.write 和 innerHTML 的區別

  • 若是想獲取document,使用document.documentElement
  • document.write是重寫整個document內容
  • innerHTML是HTMLElement的屬性,能夠精確到一個具體的元素,能讀和寫一個元素內部的html內容

script 標籤的 defer 和 async 標籤的做用與區別

  • :沒有defer和async屬性,加載到該標籤時,瀏覽器會當即加載和執行相應的腳本,會阻塞後面的文檔的加載,
  • :有了async屬性以後,js腳本的加載與後續文檔的加載和渲染是並行執行的,即異步執行。腳本加載以後當即執行,腳本執行時會阻塞 HTML 解析。
  • :有了defer屬性,後續文檔的加載與js腳本的加載(不包括執行)是並行執行的,即異步操做。js的執行會等到文檔全部元素解析完成以後,DOMContentLoaded事件觸發執行以前。
所以,defer與async的區別就是js加載以後什麼時候執行:
它們在加載腳本都是異步執行
async在加載以後就會當即執行

若是存在多個有defer屬性的腳本,那麼它們是按照加載順序執行腳本的

都只適用於外部腳本文件,對與內聯的 script 標籤是不起做用
複製代碼

因此若是必定要將script放在頭部,但又不想阻塞頁面的加載,則加上defer屬性

JS 識別不一樣瀏覽器信息

function myBrowser() {
  var userAgent = navigator.userAgent; //取得瀏覽器的userAgent字符串 
  var isOpera = userAgent.indexOf("Opera") > -1;
  if (isOpera) {
    return "Opera"
  }; //判斷是否Opera瀏覽器 
  if (userAgent.indexOf("Firefox") > -1) {
    return "Firefox";
  }  //判斷是否Firefox瀏覽器 
  if (userAgent.indexOf("Chrome") > -1) {
    return "Chrome";
  }   //判斷是否Google瀏覽器 
  if (userAgent.indexOf("Safari") > -1) {
    return "Safari";
  } //判斷是否Safari瀏覽器 
  if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
    return "IE";
  }; //判斷是否IE瀏覽器 
} 
複製代碼

HTML、XML與JSON

  • HTML是超文本標記語言,除了能夠標記文本以外,標記 圖片,視頻,連接 等其餘內容。
  • XML是可擴展性的標記語言,就是在文檔里加上標籤來講明裏面的數據是什麼意思。方便存儲、傳輸、分享數據。與HTML的區別是,HTML標籤是預約義的,XML是可擴展的。
  • JSON是比較輕量級的數據交換格式,由鍵值對組成,方便讀寫,格式都是通過壓縮的,佔用帶寬小。

編寫一個方法,求一個字符串的字節長度

  • String.prototype.charAt(index):返回的是字符
  • String.prototype.charCodeAt(index):返回的是字符編碼

假設:一個英文字符佔用一個字節,一箇中文字符佔用兩個字節

function getBytes(str){
    var len = str.length;   //獲取字符串長度
    var bytes = len;
    for(var i = 0; i < len; i++){
        if (str.charCodeAt(i) > 255)  bytes++; //中文則+1
    }
    return bytes;
}
alert(getBytes("你好,as"));
複製代碼

new操做符具體幹了什麼?

  • 建立一個新對象
  • 這個新對象的原型(Object.getPrototypeOf(target))指向構造函數的prototype對象
  • 該函數內的this會綁定在新建立的對象上
  • 若是函數沒有返回其餘對象,那麼會自動返回這個新對象

原型

function f(){}
typeof f.prototype;  //"object"
typeof f.__proto__;  //"function"
f.__proto__===Function.prototype    //true
typeof Function.prototype;          //"function"
複製代碼
  • 全部引用類型(對象、數組、函數)都有__proto__隱式屬性
  • 因爲兼容性問題,如今使用 Object.getPrototypeOf(target) 來替代 proto
  • 使用 let obj=Object.create(target),讓obj.proto=target
  • Object.setPrototypeOf(target)(寫操做)
  • prototypeObj.isPrototypeOf(object):測試一個對象是否存在於另外一個對象的原型鏈上
  • obj.hasOwnProperty(prop): 指示對象自身屬性中是否具備指定的屬性,拒絕查找原型鏈

有哪些性能優化的方法?

web 前端是應用服務器處理以前的部分,前端主要包括:HTML、CSS、javascript、image 等各類資源,針對不一樣的資源有不一樣的優化方式。

內容優化

  • 減小 HTTP 請求數。這條策略是最重要最有效的,由於一個完整的請求要通過 DNS 尋址,與服務器創建鏈接,發送數據,等待服務器響應,接收數據這樣一個消耗時間成本和資源成本的複雜的過程。 常見方法:合併多個 CSS 文件和 js 文件,利用 CSS Sprites 整合圖像,Inline Images (使用 data:URL scheme 在實際的頁面嵌入圖像數據 ),合理設置 HTTP 緩存等。
  • 減小 DNS 查找
  • 避免重定向
  • 使用 Ajax 緩存
  • 延遲加載組件,預加載組件
  • 減小頁面上的DOM 元素數量。頁面中存在大量 DOM 元素,會致使 javascript 遍歷 DOM 的效率變慢。
  • 最小化 iframe 的數量。iframes 提供了一個簡單的方式把一個網站的內容嵌入到另外一個網站中。但其建立速度比其餘包括 JavaScript 和 CSS 的 DOM 元素的建立慢了 1-2 個數量級。
  • 避免 404。HTTP 請求時間消耗是很大的,所以使用 HTTP 請求來得到一個沒有用處的響應(例如 404 沒有找到頁面)是徹底沒有必要的,它只會下降用戶體驗而不會有一點好處。

服務器優化

  • 使用內容分發網絡(CDN)。把網站內容分散到多個、處於不一樣地域位置的服務器上能夠加快下載速度。
  • GZIP 壓縮
  • 設置 ETag:ETags(Entity tags,實體標籤)是 web 服務器和瀏覽器用於判斷瀏覽器緩存中的內容和服務器中的原始內容是否匹配的一種機制。
  • 提早刷新緩衝區
  • 對 Ajax 請求使用 GET 方法
  • 避免空的圖像 src
  • Cookie 優化,減少 Cookie 大小針對 Web組件使用域名無關的 Cookie

CSS 優化

  • 將 CSS 代碼放在 HTML 頁面的頂部
  • 避免使用 CSS 表達式
  • 使用 < link> 來代替 @import
  • 避免使用 Filters

javascript 優化

  • 將 JavaScript 腳本放在頁面的底部。
  • 將 JavaScript 和 CSS 做爲外部文件來引用。 在實際應用中使用外部文件能夠提升頁面速度,由於 JavaScript 和 CSS 文件都能在瀏覽器中產生緩存。
  • 縮小 JavaScript 和 CSS
  • 刪除重複的腳本
  • 最小化 DOM 的訪問。使用 JavaScript 訪問 DOM 元素比較慢。
  • javascript 代碼注意:謹慎使用 with,避免使用 eval Function 函數,減小做用域鏈查找。

圖像優化

  • 優化圖片大小
  • 經過 CSS Sprites 優化圖片
  • 不要在 HTML 中使用縮放圖片
  • favicon.ico 要小並且可緩存

類數組轉爲數組

  • Array.prototype.slice.call(類數組 [,0])
  • Array.from(類數組)

應用:將節點列表 (NodeList) 轉換爲數組

若是你運行 document.querySelectorAll("p") 方法,它可能會返回一個 DOM 元素的數組 — 節點列表對象。 但這個對象並不具備數組的所有方法,如 sort(),reduce(), map(),filter()。 爲了使用數組的那些方法,你須要把它轉換爲數組。

var elements = document.querySelectorAll("p"); // NodeList
var arrayElements = [].slice.call(elements); // 如今 NodeList 是一個數組

var arrayElements = Array.from(elements); // 這是另外一種轉換 NodeList 到 Array 的方法
複製代碼

jq 的 ready 和 onload 事件的區別

  • onload 是等 HTML 的全部資源都加載完成後再執行 onload 裏面的內容,全部資源包括 DOM 結構、圖片、視頻 等資源;
  • ready 是當 DOM 結構加載完成後就能夠執行了,至關於 jQuery 中的 $(function(){ js 代碼 });
  • 另外,onload 只能有一個,ready 能夠有多個。

Object.create(proto)函數

  • Object.create()方法建立一個新對象,傳入的參數對象爲建立的對象的__proto__
  • 在建立的對象上改變繼承的屬性不會對傳入的參數對象有影響。
  • 返回建立的對象的constructor是ƒ Object()

例子

var a4 = { a: 1 }
var a3 = Object.create(a4);

console.log("a3.__proto__:", a3.__proto__); // Object {a: 1}

console.log(oo.constructor.prototype==Object.prototype)//true

console.log(a3.__proto__ === a3.constructor.prototype); //false

//通常obj.__proto__=obj.constructor.prototype,除了Object.create()建立的對象
複製代碼

閉包

  • 什麼是閉包:閉包是一個對象,裏面保存着內部函數引用的外部函數的變量及變量的值
  • 閉包的做用:
    • 可以訪問函數裏的變量
    • 能讓這些變量一直保存在內存中
function out(x){
       return function () {
           let ins='inside'
           console.log(x)
       }
}
let inside=out('xx');
   inside();
複製代碼

打斷點以後能看到Scopes裏的closure對象就是閉包,裏面保存着引用的變量

  • 造成閉包的條件:只要是嵌套的函數,內部函數引用了外部函數的變量就會造成閉包

堆和棧

  • 堆是沒有結構的,數據能夠任意存放。用於爲引用類型的數據分配空間
  • 棧是有結構的,主要存放基本數據類型的變量和對象的引用。好比有:函數的參數、局部變量等。
  • 棧內存是線性的有序存儲,容量小,系統分配效率高
  • 存儲在堆內存的變量須要先在堆內存中分配存儲區域,以後再將地址存儲到棧內存中,效率就相對較低。/
  • 關於內存的回收:
    • 存儲在棧內存的變量用完就回收了
    • 存儲在堆內存的變量不能輕易回收,由於存在不肯定的引用,只有當引用的變量爲0時才能回收。

如何實現跨域?

在JavaScript中,有一個很重要的安全性限制,被稱爲同源策略。這一策略對於JavaScript代碼可以訪問的頁面內容作了很重要的限制,即JavaScript只能訪問與包含它的文檔在同一域下(協議+域名+端口號相同)的內容。

script、image、link都是能跨域的,是發送get請求。在頁面上有三種資源是能夠與頁面自己不一樣源的。它們是:js腳本,css樣式文件,圖片

  • jsonp:解決AJAX受同源策略的限制的問題,但jsonp只能發送get請求。原理是動態生成script標籤,在src屬性寫上要不一樣域的路徑,而後將回調函數加在路徑後面,做爲參數傳過去。
  • WebSocket:是一種雙向通訊協議,創建鏈接後,客戶端和服務端都能主動向對方發送或接收數據。
  • CORS:須要瀏覽器支持,整個過程由瀏覽器自動完成,不需用戶參與,當發現請求跨域時,會自動添加一些附加的頭信息。
  • 使用postMessage API(otherWindow.postMessage(message, targetOrigin, [transfer]);)
  • document.domain+iframe,只能解決主域相同的跨域問題。使用iframe去加載同一主域的頁面,而後用js添加document.domain=該頁面的域名或更高一級的父域,在請求的頁面也需寫上這一屬性。
  • window.name+代理iframe。當iframe的頁面跳轉到其餘網站時,其window.name值保持不變,可是主頁面仍然是不可訪問跨域下的iframe的window.name,因此須要一個和主頁相同域的代理頁面,這樣當iframe從請求數據的跨域頁面加載完後(監聽onLoad事件),再切換成這個代理頁面時,就可訪問window.name而不會有跨域的問題
  • 代理服務器(Node中間件):服務器向服務器請求就無需遵循同源策略。客戶端向本地的代理服務器發送請求,代理服務器接收請求,將請求轉發給目標服務器,最後將目標服務器的響應結果轉發給枯客戶端。
  • nginx反向代理:實現原理相似於Node中間件代理,須要你搭建一箇中轉nginx服務器,用於轉發請求。(須要下載nginx)

總結:

  • CORS支持全部類型的HTTP請求,是跨域HTTP請求的根本解決方案
  • JSONP只支持GET請求,JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。
  • 無論是Node中間件代理仍是nginx反向代理,主要是經過同源策略對服務器不加限制。
  • 平常工做中,用得比較多的跨域方案是cors和nginx反向代理

例子1 例子2

CORS

瀏覽器將CORS請求分爲兩類:簡單請求和非簡單請求,對於非簡單請求,在正式通訊以前,增長一次HTTP查詢請求,稱爲「預檢」請求。

簡單請求:
一、get、post、head
二、HTTP頭信息不超過如下幾種字段:Accept、Accept-Language、Content-Language、Content-Type(application/x-www-form-urlencoded 或 multipart/form-data 或 text/plain)
複製代碼
非簡單請求:
對服務器有特殊要求的請求,好比put、delete,或者Content-Type類型是application/json
複製代碼

資料

ES6

資料

  • 使用export導出數據
  • 使用import接收
util.js文件
//一、先定義後導出
let num1=1; let num2=2;
export {num1,num2};
//二、直接導出定義的變量
export var num=1;
export var num2=2;

main.js
//接收
import {num1,num2} from 'util.js'
//能夠起別名
import {num1:n1,num2:n2} from 'util.js'
//能夠一次性全接收
import * as obj from 'util.js';
console.log(obj.num1,obj.num2);
---------------------------

util.js文件
//三、導出默認數據
export default const obj={}

main.js
//接收
import data from 'util.js'; //能夠隨意命名一個變量
-------------------------------

//同時導出默認的和部分的,export default只能使用一次
export default const example3 = {
  birthday : '2018 09 20'
}
export let name  = 'my name'
export let age  = 'my age'
export let getName  = function(){ return 'my name'}

// 導入默認與部分
import example3, {name, age} from './example1.js'

複製代碼

迭代器與生成器

  • 迭代器:迭代器是一個對象,含有next方法,該方法返回一個對象,該對象有done和value屬性,value表示迭代到的值,done表示是否迭代結束,當數據迭代完畢,返回true

用ES5實現迭代器的建立

function generator(arr) {
        let i=0;
        return {
            next(){
                let done=(i>=arr.length);
                let value=!done? arr[i++]:undefined
                return{done,value}
            }
        }
    }
    let iterator=generator([1,2,3]);    //獲得迭代器對象
    let obj=iterator.next();
    while (!obj.done) {
        console.log(obj.value);
        obj=iterator.next();
    }
複製代碼
  • 生成器:ES6語法,在函數名前加一個*表示生成器,用來生成一個迭代器對象,函數像正常代碼同樣編寫,函數內部使用yield關鍵字來指定每次迭代到的數據,當運行到yield函數就會中止向下執行,直到迭代器調用next方法

用ES6的生成器建立迭代器,迭代數組

function *Generator(arr) {
        for(let i=0;i<arr.length;i++){
            yield arr[i]
        }
    }
    let it=Generator([1,2,3]);
    let obj_=it.next();
    while (!obj_.done){
        console.log(obj_.value);
        obj_=it.next()
    }
複製代碼

可迭代對象有:數組、字符串、map、set對象,這些都是含有Symbol.iterator屬性

commonjs模塊(require和module.exports)和ES6模塊(import和export)的區別

  • commonjs是動態加載,是運行時執行,require能夠寫在任何地方。獲取到的是模塊的淺拷貝,能夠對模塊從新賦值
  • es6是靜態加載,是編譯時加載,import必須寫在文件頭部,獲取到的模塊的引用,因此對模塊只有只讀,不能修改模塊指向的地址,對模塊從新賦值會編譯報錯

es6模塊

export const person = {
    name: 'lc',
    friends: ['xm','xh']
};
setTimeout(() => person.name ='liangcheng', 500);
複製代碼

commonjs模塊

var dog = {
    name: 'xsz', // 不要猜想是誰的名字
    owner: ['lc']
};
module.exports = dog;

setTimeout(() => dog.name = 'xushizhou' ,500);
複製代碼

引用模塊

import { person } from './a1';
const dog = require('./a2');

setTimeout(() => console.log(person.name, dog.name), 1000); //liangcheng xushizhou
複製代碼

面向對象

三要素:封裝、繼承、多態

# 1.封裝
// 假設須要登記學籍,分別記錄小明和小紅的學籍號、姓名
let name1 = "小明"
let num1 = "030578001"
let name2 = "小紅"
let num2 = "030578002"

// 若是須要登記大量的數據,則弊端會很是明顯,並且很差維護,那麼咱們會使用如下方法來登陸,這也是面向對象的特性之一:封裝

let p1 = {
    name:"小明",
    num:"030578001"
}

let p2 = {
    name:"小紅",
    num:"030578002"
}
# 2.繼承
// 從已有的對象上,獲取屬性、方法
function Person(){
    this.name = "邵威儒"
}

Person.prototype.eat = function(){
    console.log("吃飯")
}

let p1 = new Person()
p1.eat() // 吃飯

let p2 = new Person()
p2.eat() // 吃飯

# 3.多態
// 同一操做,針對不一樣對象,會有不一樣的結果
let arr = [1,2,3]
arr.toString() // 1,2,3

let obj = new Object()
obj.toString() // [object Object]

複製代碼

面向過程和麪向對象的本質理解

  • 面向過程是流程化的,分析解決問題須要幾個步驟,用函數把每個步驟實現,在使用的時候調用函數便可。
  • 面向對象是模型化的,抽象出一個類,這是一個封閉的環境,在這個環境中有數據有解決問題的方法,你若是須要什麼功能直接使用就能夠了,至因而怎麼實現的,你不用知道。

總結: 面向對象的底層仍是面向過程,面向過程抽象成類,而後封裝,方便使用就是面向對象。

ES5的繼承和ES6的繼承的區別

  • ES5的繼承實質上是先建立子類的實例,再將父類的方法添加到this上 (Parent.apply(this))
  • ES6是先建立父類的實例對象this(經過調用super方法來調用父類的構造函數),而後子類再將本身的屬性和方法添加到this上
class Parent{
        constructor(){
            this.x='parent'
        }
    }
class Son extends Parent{
    constructor(){
    var s=super()
    console.log(s === this);    //true
    }
}
var son=new Son()
複製代碼

圖片懶加載

  • 將頁面上的<image>的src屬性寫成data-src,將圖片的路徑寫在這個下面
  • 一開始加載頁面就判斷圖片距離瀏覽器的頂部的距離是否小於窗口可視高度($img.getBoundingClientRect().top<=window.innerHeight; ),是則將data-src屬性賦給src屬性
  • 而後將已加載的圖片設一個標記位,表示已加載過,下次無需再判斷
  • 而後監聽頁面的滑動,要使用防抖函數,防止持續的滾動頁面從而不斷的觸發相關函數致使性能損耗。
  • 滑動事件監聽函數裏也是進行前三步的判斷

資料

用CSS3動畫替代JS模擬動畫的好處

好處:

  • 不佔用js主線程
  • 能夠採用硬件加速(將瀏覽器的渲染過程交給GPU處理,可使得animation和transition更加順暢)
    • 使用3d效果來開啓硬件加速:transform3d/rotate3d/scale3d/translateZ
  • 瀏覽器能夠對動畫進行優化(元素不可見時不動畫,減小對FPS的影響)

壞處:

  • 瀏覽器對渲染的批量異步化處理會致使動畫難以控制,須要強制同步
$.fn.repaint = function () {
   this.each(function (item) {
     return item.clientHeight;
 });  
}
複製代碼

CSS3動畫與javascript模擬動畫有如下區別:

  • CSS 3D動畫在js中沒法實現
  • CSS 2D矩陣動畫(指矩陣transform的變化,好比scale、變形、x軸、y軸)效率高於js用margin和left、top模擬的矩陣動畫
  • CSS3其它常規動畫屬性的效率均低於js模擬的動畫,常規動畫屬性在這裏是指:height,width,opacity,border-width,color…..

資料

JavaScript設計模式

資料

Webpack

  • 做用:使前端實現模塊化開發,可以使用require與exports,export與import

  • 優勢:

    • 依賴管理:方便引用第三方模塊、讓模塊更容易複用、避免全局注入致使的衝突、避免重複加載或加載不須要的模塊。
    • 各路插件:babel 把 ES6+ 轉譯成 ES5 ,eslint 能夠檢查編譯期的錯誤……
    • 合併代碼:把各個分散的模塊集中打包成大文件,減小 HTTP 的請求連接數,配合 UglifyJS 能夠減小、優化代碼的體積。
  • 原理:一切皆爲模塊,因爲 webpack 並不支持除 .js 之外的文件,從而須要使用 loader 轉換成 webpack 支持的模塊,plugin插件用於擴展 webpack 的功能,在 webpack 構建生命週期的過程在合適的時機作了合適的事情。

  • webpack 從構建到輸出文件結果的過程

    • 解析配置參數,合併從 shell 傳入和 webpack.config.js文件的配置信息,輸出最終的配置信息
    • 註冊配置中的插件,好讓插件監聽 webpack 構建生命週期中的事件節點,作出對應的反應
    • 解析配置文件中 entry 入口文件,並找出每一個文件依賴的文件,遞歸下去
    • 在遞歸每一個文件的過程當中,根據文件類型和配置文件中 loader 找出相對應的 loader 對文件進行轉換
    • 遞歸結束以後獲得每一個文件最終的結果,根據 entry 配置生成代碼 chunk
    • 輸出全部 chunk 到文件系統

模塊化和組件化

  • 組件化從功能出發,強調的是功能性和可複用性
  • 模塊化從業務邏輯出發,強調的是完整性和業務性
  • 好處是:提升代碼的複用性,解耦

Vue

數據劫持與監聽

vue.js是經過Object.defineProperty以及發佈訂閱模式來進行數據劫持和監聽

v-model的原理

v-model實際是個語法糖,原理是用v-bind綁定表單組件的value/selected/checked,和v-on綁定input/change事件。一般用在表單控件的雙向綁定。

自定義組件的v-model如何生效

對於添加了v-model指令的組件(若是含表單控件input),會默認利用value和input事件。但若是是其餘類型的表單控件checkbox,就會利用checked和change事件,因此爲了通用性,組件將會有model對象屬性,定義了prop和event屬性來定義綁定的值和事件類型

  • 在組件添加v-model指令
<base-checkbox v-model="lovingVue"></base-checkbox>
複製代碼
  • 至關於寫成
<base-checkbox v-bind:checked="lovingVue" v-on:change="lovingVue=arguments[0]"></base-checkbox>
複製代碼
  • 這個組件:
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
      //checkbox表單控件本身有change事件,它的事件監聽函數觸發的是這個vue組件的change事件,是父組件綁定的事件,兩個change事件是屬於不一樣對象的
    >
  `
})
複製代碼

綜上:父組件的lovingVue傳遞給子組件,子組件用checked接收,注意在子組件要在props屬性顯示指出checked這個prop。當子組件的表單控件發生變化,觸發父組件綁定的事件,將表單控件的值做爲事件函數的參數傳遞過去,從而改變父組件的lovingVue值。實現子組件更新父組件的值。

資料

computed和watch的區別

相同:二者都是能監聽/依賴一個數據,並進行相應的處理

  • computed:相似於過濾器,對綁定到視圖的數據進行處理

    • 在computed裏定義函數,函數被看成屬性使用,函數要有返回值
    • 具備緩存,只有依賴的屬性發生變化時纔會從新計算求值
    • 不能夠執行異步操做
  • watch:watch是一個偵聽的動做,用來觀察和響應 Vue 實例上的數據變更

    • 是以綁定到視圖的數據命名的函數(底層實際上是裏面包裹了一個名爲handler的函數)
    • 當數據發送變化時,這個響應函數就會被調用
    • 這個函數有兩個參數,newVal和oldVal
    • 能夠執行異步操做
    • 因爲能夠執行異步操做,因此在獲得最終結果前能夠設置中間狀態
<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
複製代碼

watch執行異步操做,限制咱們執行該操做的頻率,並在咱們獲得最終結果前,設置中間狀態。這些都是計算屬性沒法作到的:

<!-- 由於 AJAX 庫和通用工具的生態已經至關豐富,Vue 核心代碼沒有重複 -->
<!-- 提供這些功能以保持精簡。這也可讓你自由選擇本身更熟悉的工具。 --> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script> <script> var watchExampleVM = new Vue({ el: '#watch-example', data: { question: '', answer: 'I cannot give you an answer until you ask a question!' }, watch: { // 若是 `question` 發生改變,這個函數就會運行 question: function (newQuestion, oldQuestion) { this.answer = 'Waiting for you to stop typing...' this.debouncedGetAnswer() } }, created: function () { // `_.debounce` 是一個經過 Lodash 限制操做頻率的函數。 // 在這個例子中,咱們但願限制訪問 yesno.wtf/api 的頻率 // AJAX 請求直到用戶輸入完畢纔會發出。想要了解更多關於 // `_.debounce` 函數 (及其近親 `_.throttle`) 的知識, // 請參考:https://lodash.com/docs#debounce this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) }, methods: { getAnswer: function () { if (this.question.indexOf('?') === -1) { this.answer = 'Questions usually contain a question mark. ;-)' return } this.answer = 'Thinking...' var vm = this axios.get('https://yesno.wtf/api') .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) } } }) </script> 複製代碼

總結:computed主要用於對同步數據的處理,watch則主要用於觀測某個值的變化去完成一段開銷較大的複雜業務邏輯。

Vue的單向數據流

  • 全部的prop使得父子組件間造成單向下行綁定:父級prop的更新會向下流動到子組件中,所以子組件的prop時時能獲取到父組件更新的值。
  • 但子組件不容許改變prop,若是想改變能夠經過$emit派發一個自定義事件,父組件接收後,由父組件修改。

有兩種常見的試圖改變一個prop的情形:

  • 將prop做爲子組件的初始值,而且這個子組件接下來但願將其做爲一個本地的prop數據使用:最好定義到本地的data屬性
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}
複製代碼
  • 這個 prop 以一種原始的值傳入且須要進行轉換:最好使用這個 prop 的值來定義一個計算屬性
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}
複製代碼

以前給數組項賦值和給一個空對象添加屬性,Vue能檢測到變化嗎?

不能。

  • 當你利用索引直接設置一個數組項時,例如:vm.items[indexOfItem] = newValue
  • 當你修改數組的長度時,例如:vm.items.length = newLength
雖然Object.defineProperty()能夠監聽到數組下標的變化,
可是爲了性能優化,vue源碼並無使用Object.defineProperty()來劫持數組類型的數據:
複製代碼

如何解決?

  • 使用Vue.set(數據,屬性/索引,屬性值)
  • 使用vue.$set(數據,屬性/索引,屬性值)
  • 改變數組長度:vm.items.splice(newLength)

虛擬DOM樹-- Virtual DOM 算法

  • 有js對象結構表示DOM樹的結構,而後利用它構建一個真正的DOM樹,再插入到文檔中。
  • 當狀態變動時,用js對象結構再建立一個虛擬的DOM,而後與以前的虛擬DOM進行比較,記下差別的地方
  • 把差別應用到真正的DOM樹上,視圖就更新了

難點:比較兩棵虛擬DOM樹的差別

比較兩棵 DOM 樹的差別是 Virtual DOM 算法最核心的部分,這也是所謂的 Virtual DOM 的 diff 算法。

  • 兩個樹的徹底的 diff 算法是一個時間複雜度爲 O(n^3) 的問題。可是在前端當中,你不多會跨越層級地移動 DOM 元素。

因此 Virtual DOM 只會對同一個層級的元素進行對比:

上面的 div 只會和同一層級的 div 對比,第二層級的只會跟第二層級對比。這樣算法複雜度就能夠達到 O(n)。

深度優先遍歷,記錄差別

在實際的代碼中,會對新舊兩棵樹進行一個深度優先的遍歷,這樣每一個節點都會有一個惟一的標記:

在深度優先遍歷的時候,每遍歷到一個節點就把該節點和新的的樹進行對比。若是有差別的話就記錄到一個對象裏面。

Vue生命週期

  • new一個Vue實例時,實例只有默認的事件和生命週期函數

  • 最先能訪問到this是:beforeCreate函數

  • 在beforeCreate函數以後created以前:data、methods、watch、computed屬性進行初始化

  • 在created函數能訪問data、methods、watch、computed屬性,除了el屬性

  • 在created函數以後,beforeMount函數以前:進行模板編譯

    • 若是沒有el屬性,則等到調用vm.$mounted()掛載時再繼續往下執行
    • 若是沒有template屬性,則將el的outerHtml內容做爲模板template
    • 將template編譯成render函數
  • beforeMounte函數:此時內存已構建出虛擬的dom樹,視圖還未更新,不能訪問dom上一些動態變量,此時訪問el還是未各類表達式{{}}

  • beforeMounte函數以後:建立vm.$el,與el進行替換

  • Mounted函數:已將虛擬dom掛載到頁面上,能訪問dom上的動態變量


  • 此時已完成實例的初始化階段,進入運行階段
  • beforeUpdate函數:data已經發生變化觸發該函數,視圖還未更新
  • beforeUpdate函數以後:根據data從新渲染新的虛擬dom,對比新老虛擬dom樹,將差別應用到真正的dom樹上,完成頁面更新
  • updated函數:此時頁面已經是最新的數據

  • beforeDestory函數:實例銷燬前調用,this仍能獲取到實例,經常使用於銷燬定時器、解綁全局事件、銷燬插件對象等操做
  • destroyed函數:在Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。

)

Vue.use()

  • 在全局註冊/安裝組件,把組件掛載到Vue.prototype上,全部組件實例均可訪問

組件間傳遞數據

  • props(父向子,當傳遞的不是變量僅是單純的字符串時,不須要冒號綁定)
  • 父組件在子組件標籤上綁定事件 v-on,子組件觸發事件$emit(子向父)
  • slot:父向子傳遞‘標籤數據’
//父組件
<child>
    <div slot="xxx">父組件動態插入數據</div>
    <div slot="yyy">父組件動態插入數據</div>
</child>   

//子組件
<template>
    <div>
        <slot name='xxx'></slot>
        <div>組件肯定的標籤結構</div>
        <slot name='yyy'></slot>
    </div>
</template>
複製代碼
  • $refs.名稱,獲取組件實例
  • $parent / $children:訪問父 / 子實例
// component-a 子組件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
複製代碼
// 父組件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 彈窗
    }
  }
</script>
複製代碼
  • bus
    • 建立一個js文件,使用export default向外暴露一個對象,對象含有install方法
    • 在入口文件main.js引入這個文件,賦給一個變量,好比bus
    • 使Vue.use(bus),這個方法會去調用bus的install方法,且會傳入Vue做爲參數
    • 在bus對應的js文件的install函數寫:將$bus屬性添加到Vue.prototype上,屬性值是一個Vue實例(由於爲了實現數據更新,視圖更新,因此讓$bus=Vue實例,利用Vue的data有數據劫持的特色。)
    • 將共用的數據添加在data對象(是對象不是方法,由於不是組件,是js文件不是.vue文件)
    • 在methods添加on方法、emit方法、off方法
//event-bus.js文件
export default const install=function(Vue){
    Vue.prototype.$bus=new Vue({
        methods:{
            emit(event,...args){
                this.$emit(event,...args);
            },
            on(event,callback){
                this.$on(event,callback);
            },
            off(event,callback){
                this.$off(event,callback)
            }
        }
    })
}
複製代碼

在入口文件main.js引入插件

import bus from 'event-bus.js'
Vue.use(bus)
複製代碼

使用$bus綁定事件this.$bus.on()要注意:

一、綁定在哪一個生命週期函數,才能確保在其餘組件觸發事件以前已經綁定好事件,即組件間生命週期函數的執行順序

二、在組件銷燬以前將事件解除,不然當再有組件綁定該事件,添加事件監聽函數時,此時事件的監聽函數還保留着以前的事件監聽函數,即會觸發多個回調。

  • vuex

    • 安裝Vuex
    • 新建一個js文件,引入Vuex,使用Vue.use()安裝Vuex
    • 向外暴露一個Vuex.Store實例
    • 向Vuex.Store()構造函數傳入四個對象:state、getters、actions(定義的方法能夠重名,會按註冊的順序依次觸發。能夠包含同步/異步操做,用來提交mutation,不直接改變狀態state)、mutations(定義的方法不能夠重名,只能進行同步操做)
    • 在main.js引入這個文件,在Vue實例添加store屬性等於這個文件暴露出來的Vuex.Store實例,這樣全部組件都有$store屬性
    • 使用$store.state訪問數據
    • 使用$store.dispatch(action名,data)調用actions裏的方法
    • actions裏的方法使用commit調用mutations
  • pubsub消息發佈/訂閱

    • 安裝pubsub-js
    • 接收數據的組件訂閱消息
    • 傳遞數據的組件發佈消息
//訂閱消息
mounted: {
PubSub.subscribe("deleteTodo",(messageName, todosIndex)=>{
    this.deleteTodo(todosIndex);
});
//發佈消息
methods: {
PubSub.publish("deleteTodo", this.index);
}
複製代碼
  • $attrs/$listeners:

多級組件嵌套須要傳遞數據時,一般使用的方法是經過vuex。但若是僅僅是傳遞數據,而不作中間處理,使用 vuex 處理,未免有點大材小用。

$attrs:包含了從父組件接收的(來自父做用域的),但props沒有定義的數據 (class 和 style 除外)。 當一個組件沒有聲明任何 prop 時,$attrs這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件 這是惟一一次v-bind直接與=相連,通常要傳給子組件且props能接收的要v-bind:變量名=數據

$listeners:包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件


總結:

  • 父向子傳遞數據:props,$attrs/$listeners、slot(傳遞的是標籤數據)

  • 子向父傳遞數據:事件綁定和觸發$emit事件回調,$ref獲取組件實例

  • 兄弟間:bus,vuex,pubsub訂閱發佈消息

  • 跨級通訊:bus,vuex,pubsub訂閱發佈消息,$attrs/$listeners

  • 任意級別通訊:bus、vuex、pubsub訂閱發佈消息

Vuex的mapState、mapGetters、mapActions

一、state是什麼?

  • vuex的state和vue中的data有不少類似之處,都是用於存儲一些數據或者說是狀態值。
  • 這些值都將被掛載到數據和dom的雙向綁定事件,則當你改變值的時候能夠觸發dom的更新。
  • 雖然state和data有不少類似之處,但state在使用時通常應該掛載到組件的computed計算屬性上,這樣有利於state的值發生改變時及時響應給子組件(若是你用data去接收$store.state,固然能夠接收到值,但因爲這只是一個簡單的賦值操做,所以state中的狀態改變的時候不能被vue中的data監聽到,固然你也能夠經過watch $store去解決這個問題)

二、mapState輔助函數 mapState是語法糖,不用在computed裏屢次定義函數,而後返回不一樣的this.$store.state.數據名,能夠直接將store的state直接添加到computed裏

  • 使用前,引入這個輔助函數
  • 語法:mapState( { } / [ ] ),傳入對象或數組
import { mapState } from 'vuex'
...
computed: mapState({
    count: 'count', // 第一種寫法,count爲state裏定義的,後面的count能夠省略
    sex: (state) => state.sex, // 第二種寫法
    from: function (state) { // 用普通函數this指向vue實例,要注意
      return this.str + ':' + state.from
    },
    // 注意下面的寫法看起來和上面相同,事實上箭頭函數的this指針並無指向vue實例,所以不要濫用箭頭函數
    // from: (state) => this.str + ':' + state.from
    
    //也能夠在mapState裏定義不使用到state的函數
    myCmpted: function () {
      // 這裏不須要state,測試一下computed的原有用法
      return '測試' + this.str
    }
})
複製代碼

三、...mapState

  • ...mapState是ES6語法,展開符
  • 當computed已經定義一些函數時,此時發現要引入state,就可使用展開符將state添加到computed裏
computed:{
    //原來的繼續保留
    fn1(){ return ...},
    fn2(){ return ...},
    fn3(){ return ...}
    ......
    //再維護vuex
    ...mapState( //這裏的...不是省略號了,是對象擴展符
       [ count ]
    )
}
複製代碼

自定義指令

語法:

  • 全局指令:
Vue.directive('指令名',{
    bind(el,binding,vnode){
        //一開始綁定時調用,只調用一次
        //el:綁定指令的dom元素,binding傳遞的是綁定的值,存儲的是對象
    },
    update(el,binding,vnode){
        //綁定的數據發生更新時調用,0-屢次
    }
})
複製代碼
<p v-focus:top="10"></p>

Vue.directive('focus',{
    bind(el,binding,vnode){
        el.style[binding[arg]]=binding[value]+'px'
    },
    update(el,binding,vnode){
        //綁定的數據發生更新時調用,0-屢次
    }
})
複製代碼
  • 私有指令:在Vue實例裏定義directives屬性
directives:{
    focus:{
        binding(el,binding){
            
        }
    }
}
複製代碼

v-for添加key屬性

當Vue用 v-for 正在更新已渲染過的元素列表是,它默認用「就地複用」策略。若是數據項的順序被改變,Vue將不是移動DOM元素來匹配數據項的改變,而是簡單複用此處每一個元素,而且確保它在特定索引下顯示已被渲染過的每一個元素。

  • 由於vue組件高度複用組件,增長Key能夠標識組件的惟一性,更好地區別各個組件
  • key的做用主要是爲了高效的更新虛擬DOM diff算法

$router與$route的區別

  • $router是VueRouter的一個實例,是經過安裝並引入VueRouter,安裝VueRouter —— Vue.use(VueRouter)和new VueRouter({})獲得的,是一個全局對象
    • $router.push({path:'/home'}):使用js方式代替切換路由,能夠回退到上一個
    • $router.replace({path:'/home'}):替換路由,沒有歷史記錄
  • $route是局部對象,指定當前路由,$route有path(絕對路徑)、params、query、name等屬性

vue-router會出現的相關問題

一、當使用路由參數切換路由,實現同一個路由路徑+不一樣的參數來展現不一樣的內容,即:先後都是同一組件,但顯示的內容不同

  • 緣由:原來的組件會被複用,由於兩個路由都渲染同一組件,比起銷燬再建立,複用則顯得更加高效,這意味着組件的生命週期函數不會再調用
  • 解決:在當前組件監視路由的變化
watch:{
    $route(to,from){
        //to:就是切換後的路由
        //根據需求更改數據
    }
}
複製代碼

二、使用路由切換組件時,一切換以前的組件就會被銷燬,再次切換到該組件時,以前的狀態不會保存,由於是再次建立的

  • 解決:使用<keep-alive>包裹,實現組件緩存。可是要根據需求決定是否緩存組件。

vue-router的導航守衛

vue-router 提供的導航守衛主要用來經過跳轉或取消的方式守衛導航。 有多種機會植入路由導航過程當中:全局的, 單個路由獨享的, 或者組件級的。

展現一種全局的,更多的查看連接:

路由的元信息

定義路由的時候能夠配置 meta 字段,屬性值是對象

meta:能夠與上面的導航守衛搭配使用,好比切換到網站的主頁路由時,須要用戶登陸纔可成功跳轉,顯示組件,那就在該路由加配置meta字段,當跳轉路由時,就會觸發router.beforeEach鉤子函數,在裏面進行對路由的meta裏自定義的一些標誌位的判斷,再決定是否放行。

SPA單頁面

SPA:僅在Web頁面初始化時加載相應的HTML、JavaScript、CSS,加載完成後,不會由於用戶的操做而進行頁面的從新加載或跳轉。取而代之的是利用路由機制來實現HTML內容的變換,避免頁面的從新加載。

  • 優勢:

    • 用戶體驗好、快:內容改變時不用從新加載整個頁面,避免了沒必要要的跳轉和重複渲染。
    • 基於上面一點,SPA對服務器壓力小
  • 缺點:

    • 初次加載耗時多,得等到Vue編譯後的JS文件下載完後才能渲染
    • SEO難度大:因爲全部內容都在一個頁面中動態地替換顯示

單頁應用的一些基本配置

Vue SSR

  • Vue.js是構建客戶端應用程序的框架。默認狀況下,能夠在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操做 DOM。
  • SSR是指在服務端將組件渲染成html片斷而後返回給瀏覽器,最後將這些靜態標記"激活"爲客戶端上徹底可交互的應用程序。

服務端渲染SSR的優缺點:

  • 優勢:
    • 更好的SEO:由於 SPA 頁面的內容是經過 Ajax 獲取,而搜索引擎爬取工具並不會等待 Ajax 異步完成後再抓取頁面內容,因此在 SPA 中是抓取不到頁面經過 Ajax獲取到的內容;而 SSR 是直接由服務端返回已經渲染好的頁面(數據已經包含在頁面中),因此搜索引擎爬取工具能夠抓取渲染好的頁面。
    • 首屏加載速度更快: SPA 會等待全部 Vue 編譯後的 js 文件都下載完成後,纔開始進行頁面的渲染,文件下載等須要必定的時間等,因此首屏渲染須要必定的時間;SSR 直接由服務端渲染好頁面直接返回顯示,無需等待下載 js 文件及再去渲染等,因此 SSR 有更快的內容到達時間;
  • 缺點:
    • 更多的開發條件限制:例如服務端渲染只支持 beforCreate 和 created 兩個鉤子函數;服務端渲染應用程序,須要處於 Node.js server 運行環境;
    • 更多的服務器負載

MVVM

  • view層:視圖層,由HTML和css構建
  • model層:數據模型,保存每一個頁面中單獨的數據
  • viewModel層:視圖模型層,是v與m的橋樑,起承上啓下的做用。
    • 向上於視圖層進行雙向數據綁定,向下與模型層經過接口請求的方式進行數據交互。
    • vm將獲取到的數據進行處理轉換,作二次封裝,以生成符合view層的數據模型。這樣view層展示的不是model層的數據,而是vm層的數據。
    • 雙向數據綁定使前端開發者沒必要低效又麻煩地去操做dom去更新視圖。

MVVM和MVC的區別

  • MVC:是後端分層開發的概念。V層接收用戶的輸入操做,C層響應view的事件,當涉及到數據的增刪改查曾調用model層的接口對數據進行操做,model層數據變化後通知v層更新視圖。
  • MVVM:與MVC最大的區別就是實現了m層與v層的自動同步,vm層是m層和v層的橋樑,與v層進行雙向數據綁定,與m層經過接口的形式進行數據交換。開發者不用手動操做dom就能實現v層的更新。(在vm層直接修改數據便可實現v層的更新)

Vue如何實現數據的雙向綁定

  • view -- > data變化:經過事件監聽的方式
  • data --> view變化:數據劫持與監聽、訂閱發佈模式
    • Object.defineProperty()爲data對象的每層屬性添加getter和setter,當數據發生改變,就會觸發setter,從而監聽到數據的變化。實現數據的劫持與監聽(Observer)
    • 當在遍歷每一層屬性時,在每一層都實例化一個Dep實例--訂閱器,用於存放訂閱者,當某個屬性發生變化,該層的Dep實例就會發布消息,讓Dep實例上的全部訂閱者去更新視圖。
    • 訂閱者的來歷:先編譯模板(Compile),將指令替換成數據顯示,每個指令都實例出一個Watcher實例(訂閱者),Watcher實例含更新視圖的函數,在Watcher的構造函數裏,會去訪問data的屬性,從而調用getter,所以在getter裏找到該層屬性的Dep實例,將Watcher實例添加到Dep實例裏,實現了訂閱。
    • Watcher是Dep和Compile的橋樑
    • Dep是Observer和Watcher的橋樑

vue爲何不能監聽數組的變化

在vue中,不能檢測到如下對數組的操做:

  • arr[index]=newVal
  • arr.length=5

緣由?

  • vue是經過Object.defineProperty實現數據劫持的,給屬性添加getter和setter
  • 但Object.defineProperty也能將數組的索引當初屬性來添加getter和setter
  • 但出於對性能的考慮,vue的源碼只對是對象類型的數據使用Object.defineProperty

vue只能監聽如下八種對數組操做的方法:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
複製代碼

Object.defineProperty() 和 proxy 的區別

vue中爲了對對象的每層屬性都實現監聽,須要不斷遍歷對象的屬性,使用Object.defineProperty()來實現數據劫持,則Object.defineProperty()只能劫持數據的屬性,若是能直接劫持一個完整的對象而非對象上的屬性,則無需層層遍歷屬性了。

Proxy:

Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。

var proxy=new Proxy(target,handler)

參數:

  • target:是用Proxy包裝的被代理對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理)
  • handler:一個對象,定義了一些函數,用於攔截對應的操做。好比,定義一個get方法,用來攔截對目標對象屬性的訪問請求。

返回值:

  • 返回代理對象,直接操做該對象來達到操做目標對象的效果
  • 若是直接操做目標對象不能達到Proxy的效果
let obj = {};
 let handler = {
   get(target, property) {
    console.log(`${property} 被讀取`);
    return property in target ? target[property] : 3;
   },
   set(target, property, value) {
    console.log(`${property} 被設置爲 ${value}`);
    target[property] = value;
   }
 }
 
 let p = new Proxy(obj, handler);
 p.name = 'tom' //name 被設置爲 tom
 p.age; //age 被讀取 3
複製代碼

p 讀取屬性的值時,實際上執行的是 handler.get() :在控制檯輸出信息,而且讀取被代理對象 obj 的屬性。

p 設置屬性值時,實際上執行的是 handler.set() :在控制檯輸出信息,而且設置被代理對象 obj 的屬性的值。

handler能夠定義多種攔截的函數:Proxy


綜上:取代Object.defineProperty()的Proxy有如下兩個優勢

能直接劫持整個對象,無需經過遞歸和遍歷data對象來實現對數據的監控
有多種劫持操做
複製代碼

Webpack

模塊化解決了前端的哪些痛點

  • 代碼複用
  • 命名衝突
  • 文件依賴

webpack 的 loader 和 plugin 區別

  • loader用於對模塊中的源代碼進行轉換。loader 能夠將文件從不一樣的語言(如 TypeScript)轉換爲 JavaScript,或將內聯圖像轉換爲 data URL。loader 甚至容許你直接在 JavaScript 模塊中 import CSS文件! 由於 webpack 自己只能處理 JavaScript,若是要處理其餘類型的文件,就須要使用 loader 進行轉換,loader 自己就是一個函數,接受源文件爲參數,返回轉換的結果。
  • Plugin 是用來擴展 Webpack 功能的。使用 plugin 豐富的自定義 API 以及生命週期事件,能夠控制 webpack 打包流程的每一個環節,實現對 webpack 的自定義功能擴展。plugin能監聽webpack構建生命週期的事件節點。
相關文章
相關標籤/搜索