前端知識體系(1)-js篇

1 聲明

1-1 「js函數聲明三種方式:」

(1) Function()構造器javascript

var f =new Function()

(2) 函數聲明css

function f (){
     console.log(2);
}

(3) 函數表達式html

var f = function() {
      console.log(1);  
}

1-2「js變量聲明:」

  • var聲明的變量會掛載在window上,而let和const聲明的變量不會
  • var聲明變量存在變量提高,let和const不存在變量提高(嚴格來講,let也存在)
  • let和const聲明造成塊做用域
  • let存在暫存死區
  • const聲明必須賦值

(1) var聲明的變量會掛載在window上,而let和const聲明的變量不會:前端

var a = 100;
console.log(a,window.a);    // 100 100
let b = 10;
console.log(b,window.b);    // 10 undefined
const c = 1;
console.log(c,window.c);    // 1 undefined

(2) var聲明變量存在變量提高,let和const不存在變量提高html5

console.log(a); // undefined  ===>  a已聲明還沒賦值,默認獲得undefined值
var a = 100;
console.log(b); // 報錯:b is not defined  ===> 找不到b這個變量
let b = 10;
console.log(c); // 報錯:c is not defined  ===> 找不到c這個變量
const c = 10;

(3) let和const聲明造成塊做用域java

if(1){
    var a = 100;
    let b = 10;
}
console.log(a); // 100
console.log(b)  // 報錯:b is not defined  ===> 找不到b這個變量
//
if(1){
    var a = 100;     
    const c = 1;
}
 console.log(a); // 100
 console.log(c)  // 報錯:c is not defined  ===> 找不到c這個變量

(4) 同一做用域下let和const不能重複聲明,而var能夠node

var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
let a = 100;
let a = 10;
//  控制檯報錯:Identifier 'a' has already been declared  ===> 標識符a已經被聲明瞭。

(5) 暫存死區jquery

var a = 100;
if(1){
    a = 10;
    let a = 1;
    //在當前塊做用域中存在a使用let/const聲明的狀況下,給a賦值10時,只會在當前做用域查找變量a,
    // 而這時,還未到聲明時候,因此控制檯Error:a is not defined
    // 即let 和 const 不會聲明提早
}

(6) constwebpack

一旦聲明必須賦值,不能使用null佔位。
聲明後不能再修改
若是聲明的是引用類型數據,能夠修改其屬性
const a = 100; 
const list = [];
list[0] = 10;
console.log(list);  // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}

2 數據類型的分類:

2-1 基本類型:

  • string(字符串)--原始類型
  • boolean(布爾值)--原始類型
  • number(數字)--原始類型
  • symbol(符號)--原始類型
  • null(空值)
  • undefined(未定義)
  • BigInt(BigInt數據類型的目的是比Number數據類型支持的範圍更大的整數值,精度在(2^53-1)範圍內,BigInt(10) 值爲:10n) ES2020新出的

其中 SymbolBigIntES6 中新增的數據類型:es6

  • Symbol 表明建立後獨一無二且不可變的數據類型,它主要是爲了解決可能出現的全局變量衝突的問題。
  • BigInt 是一種數字類型的數據,它能夠表示任意精度格式的整數,使用 BigInt 能夠安全地存儲和操做大整數,即便這個數已經超出了 Number 可以表示的安全整數範圍。

注意:NaN不是數據類型

typeof NaN === 'number' //true
NaN==NaN  //false

2-2 對象類型(引用類型),有如下3種:

A.內置對象/原生對象

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

B.宿主對象

  • (1)BOM對象: Window、Navigator、Screen、History、Location
  • (2)DOM對象:Document、Body、Button、Canvas等

C.自定義對象--(指由用戶建立的對象,兼容性問題須要由編寫者注意)

建立自定義對象幾種方式:

(1)對象直接量:

var obj1 = {};
var obj2 = {x:0,y:0};
var obj3 = {name:‘Mary’,age:18}

(2)工廠模式--用函數來封裝以特定接口建立對象的細節:

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    return o;
}
var person1 = createPerson('zhang',30,'java');

(3)構造函數模式:

function Person(name,age,job){
    this.name= name;
    this.age = age;
    this.job = job;
}
var person1 = new Person('zhang',30,'java');

(4)原型模式:

function Person(){}
Person.prototype.name = 'zhang';
Person.prototype.age = '22';
Person.prototype.job = 'html5';
var person1 = new Person();

2-3 數據類型的判斷:

2-3-1: typeof

通常經過 typeof 操做符來判斷一個值屬於哪一種 基本類型

缺點:沒法分辨對象類型

typeof 'seymoe'    // 'string'
typeof true        // 'boolean'
typeof 10          // 'number'
typeof Symbol()    // 'symbol'
typeof null        // 'object' 沒法斷定是否爲 null
typeof undefined   // 'undefined'
typeof {}           // 'object'
typeof []           // 'object'
typeof(() => {})    // 'function'

爲何typeof null爲object:

js 在底層存儲變量的時候,會在變量的機器碼的低位1-3位存儲其類型信息

  • 000:對象
  • 010:浮點數
  • 100:字符串
  • 110: 布爾值
  • 1:整數

可是, 對於 undefined 和 null 來講,這兩個值的信息存儲是有點特殊的。

  • null:全部機器碼均爲0
  • undefined:用 −2^30 整數來表示

因此,typeof 在判斷 null 的時候就出現問題了,因爲null 的全部機器碼均爲0,所以直接被當作了對象來看待。

2-3-2:instanceof

判斷對象類型:測試構造函數的 prototype 是否出如今被檢測對象的原型鏈上。

缺點:沒法判斷一個值到底屬於數組仍是普通對象

[] instanceof Array            // true
({}) instanceof Object         // true
(()=>{}) instanceof Function   // true
let arr = []
let obj = {}
arr instanceof Array    // true
arr instanceof Object   // true
obj instanceof Object   // true

在這個例子中,arr 數組至關於 new Array() 出的一個實例,
因此 arr.__proto__ === Array.prototype,
又由於 Array 屬於 Object 子類型,
即 Array.prototype.__proto__ === Object.prototype,
因此 Object 構造函數在 arr 的原型鏈上

判斷不了原始類型

console.log(true instanceof Boolean);// false
console.log(undefined instanceof Object); // false
console.log(arr instanceof Array);  // true
console.log(null instanceof Object); // false
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function);// true

2-3-3: Object.prototype.toString.call()

全能型,幾乎都能判斷
Object.prototype.toString.call({})// '[object Object]'
Object.prototype.toString.call([])// '[object Array]'
Object.prototype.toString.call(() => {})// '[object Function]'
Object.prototype.toString.call('abc')// '[object String]'

傳入原始類型卻可以斷定出結果是由於對值進行了包裝。

「那麼,什麼是包裝對象:」

所謂「包裝對象」,指的是與數值、字符串、布爾值分別相對應的Number、String、Boolean三個原生對象。這三個原生對象能夠把原始類型的值變成(包裝成)對象。

詳細點擊這裏

3 js運算符:

3-1 delete 運算符

delete 運算符用來刪除對象屬性或者數組元素,若是刪除成功或所刪除的目標不存在,delete 將返回 true
然而,並非全部的屬性均可刪除:

  • 一些內置核心和客戶端屬性是不能刪除的
  • 經過 var 語句聲明的變量不能刪除
  • 經過 function 語句定義的函數也是不能刪除的。

例如:

var o = { x: 1, y: 2};          // 定義一個對象
console.log(delete o.x);        // true,刪除一個屬性
console.log(delete o.x);        // true,什麼都沒作,x 在已上一步被刪除
console.log("x" in o);          // false,這個屬性在對象中再也不存在
console.log(delete o.toString); // true,什麼也沒作,toString是繼承來的
console.log(delete 1);          // true,無心義

var a = [1,2,3];                // 定義一個數組
console.log(delete a[2]);       // true,刪除最後一個數組元素
console.log(2 in a);            // false,元素2在數組中再也不存在
console.log(a.length);          // 3,數組長度並不會因 delete 而改變
console.log(a[2]);              // undefined,元素2所在的位置被空了出來
console.log(delete a);          // false,經過 var 語句聲明的變量不能刪除

function f(args){}              // 定義一個函數
console.log(delete f);          // false,經過 function 語句聲明的函數不能刪除

3-2 void 運算符

void 運算符能夠應用於任何表類型的表達式,表達式會被執行,但計算結果會被忽略並返回undefined

例如:

void 0;
void "you are useless?";
void false;
void [];
void /(useless)/ig;
void function(){ console.log("you are so useless?"); }
void alert(1)
// always return undefined

3-3 ++ -- 運算符

++ -- 遞增遞減運算符借鑑自 C 語言,它們分前置型和後置型,做用是改變一個變量的值。

例如:

var a = 5;
console.log(a++);   // 5   後加表不加
console.log(a);     // 6  
console.log(++a);   // 7   先加,都有加
console.log(a)      // 7
console.log(a--);   // 7
console.log(a)      // 6
console.log(--a);   // 5
console.log(a)      // 5

奇淫技巧先家都有家,後家表不家 (加號在前面,自己和表達式都加1;加號在後面,表達式不加1,自己加1 ),減法同理。

3-4 valueOf

var a = '你好', b = 1, c = [], d = {}, e = function (){}
a.valueOf()  // '好'
b.valueOf()  // 1
c.valueOf()  //[]
d.valueOf()  // {}
e.valueOf()  //ƒ (){}

3-5 +和-

"+" 操做符,若是有一個爲字符串,那麼都轉化到字符串而後執行字符串拼接

"-" 操做符,轉換爲數字,相減 (-a, a * 1 a/1) 都能進行隱式強制類型轉換

[] + {}     // "[object Object]"
{} + []     // 0
1 + true    //2
1 + false   //1

4 內存

4-1 執行上下文

當代碼運行時,會產生一個對應的執行環境,在這個環境中,全部變量會被事先提出來(變量提高),有的直接賦值,有的爲默認值 undefined,代碼從上往下開始執行,就叫作執行上下文。

「執行環境有三種」:

  • 1.全局環境:代碼首先進入的環境
  • 2.函數環境:函數被調用時執行的環境
  • 3.eval函數

「執行上下文特色:」

  • 1.單線程,在主進程上運行
  • 2.同步執行,從上往下按順序執行
  • 3.全局上下文只有一個,瀏覽器關閉時會被彈出棧
  • 4.函數的執行上下文沒有數目限制
  • 5.函數每被調用一次,都會產生一個新的執行上下文環境

「執行3個階段:」

1.建立階段

  • (1).生成變量對象
  • (2).創建做用域鏈
  • (3).肯定 this 指向

2.執行階段

  • (1).變量賦值
  • (2).函數引用
  • (3).執行其餘代碼

3.銷燬階段

  • (1).執行完畢出棧,等待回收被銷燬

詳細點擊這裏

4-2 堆棧

「概念:」

  • 棧: 棧會自動分配內存空間,它由系統自動釋放;存放基本類型,簡單的數據段,佔據固定大小的空間
  • 堆: 動態分配的內存,大小不定也不會自動釋放。存放引用類型,那些可能由多個值構成的對象,保存在堆內存中

詳細點擊這裏

5 垃圾回收機制

MDN上有說: 從2012年起,全部現代瀏覽器都使用了 標記-清除垃圾回收算法。全部對於js垃圾回收算法的改進都是基於標記-清除算法的改進

「什麼是垃圾:」 通常來講,沒有引用的對象就是垃圾,就是要才清除的。但有個例外,若是幾個對象相互引用造成一個環,但根訪問不到他們,他們也是垃圾(引用計數法,沒法清除他們)

「垃圾回收的幾種算法:」

5-1引用計數法

概念: 記錄有多少「程序」在引用本身,當引用的數值爲0時,就開始清除它。

優點:

  • 立刻回收垃圾,當被引用數值爲0時,對象立刻會把本身做爲空閒空間連到空閒鏈表上,也就是說。在變成垃圾的時候就馬上被回收。
  • 由於是即時回收,那麼‘程序’不會暫停去單獨使用很長一段時間的GC,那麼最大暫停時間很短
  • 不用去遍歷堆裏面的全部活動對象和非活動對象

劣勢:

  • 計數器須要佔很大位置,由於不能預估被引用的上限,打個比方,可能出現32位即2的32次方個對象同時引用一個對象,那麼計數器就須要32位。
  • 最大的劣勢是沒法解決循環引用沒法回收的問題 這就是前文中IE9以前出現的問題

5-2 標記清除法

主要將GC的垃圾回收過程分爲兩個階段

標記階段:把全部活動對象作上標記

清除階段:把沒有標記(也就是非活動對象)銷燬

優點:

  • 實現簡單,打標記也就是打或者不打兩種可能,因此就一位二進制位就能夠表示
  • 解決了循環引用問題

缺點

  • 形成碎片化(有點相似磁盤的碎片化)
  • 再分配時遍次數多,若是一直沒有找到合適的內存塊大小,那麼會遍歷空閒鏈表(保存堆中全部空閒地址空間的地址造成的鏈表)一直遍歷到尾端

5-3 複製算法

  1. 將一個內存空間分爲兩部分,一部分是From空間,另外一部分是To空間
  2. From空間裏面的活動對象複製To空間
  3. 釋放掉整個From空間
  4. 再將From空間和To空間的身份互換,那麼就完成了一次GC。

詳細點擊這裏1

詳細點擊這裏2

6 內存泄漏

「概念:」 申請的內存沒有及時回收掉,形成系統內存浪費,致使程序運行速度減慢甚至系統崩潰等嚴重後果

「內存泄漏發生的場景:」

(1) 意外的全局變量

function leaks(){  
  leak = 'xxxxxx';//leak 成爲一個全局變量,不會被回收
}

(2) 遺忘的定時器

setTimeout 和 setInterval 是由瀏覽器專門線程維護它的生命週期,若是在某個頁面使用了定時器,當銷燬頁面時,沒有手動去釋放清理這些定時器的話,那麼這些定時器仍是存活着的

(3) 使用不當的閉包

var leaks = (function(){  
    var leak = 'xxxxxx';// 被閉包所引用,不會被回收
    return function(){
        console.log(leak);
    }
})()

(4) 遺漏的 DOM 元素

<div id="container">  
</div>
$('#container').bind('click', function(){
    console.log('click');
}).remove();//dom移除了,可是js還持有對它的引用

解決:

$('#container').bind('click', function(){
    console.log('click');
}).off('click').remove();
//把事件清除了,便可從內存中移除

(5) 網絡回調

「如何監控內存泄漏」

  • 使用控制檯
  • 詳細點擊這裏

7 做用域

擴展

JavaScript是門動態語言,跟Java不同,JavaScript能夠隨意定義全局變量和局部變量,每個函數都是一個做用域,當函數執行時會優先查找當前做用域,而後逐級向上。

JavaScript是靜態做用域,在對變量進行查詢時,變量值由函數定義時的位置決定,和執行時的所處的做用域無關。

ES6已經有塊級做用域了,並且用 let 和 const 定義的變量不會提高。

概念

做用域:變量或者函數的有效做用範圍

做用域鏈:咱們須要查找某個變量值,會先在當前做用域查找,若是找不到會往上一級查,若是找到的話,就返回中止查找,返回查找的值,這種向上查找的鏈條關係,叫做用域

7-1 相關案例

(1)變量提高/變量由函數定義時的位置決定

var a = 1;
function fn() {
  console.log('1:' + a);
  var a = 2;
  bar()
  console.log('2:' + a)
}
function bar() {
  console.log('3:' + a)
}
fn()

分別打印:1:undefined 3:1 2:2

「解析:」

第一個 a 打印的值是 1:undefined 而不是 1。由於咱們在 fn() 中定義了變量 a,用 var 定義的變量會提高到當前做用域的頂部(即當前函數做用域頂部),可是賦值還在原地,因此是undefined。

第二個a 打印的值是 3:1 而不是 2。由於函數 bar 是定義在全局做用域中的,因此做用域鏈是 bar -> global,bar 裏面沒有定義a,因此就會順着做用域鏈向上找,而後在 global 中找到了 a。注意:查找是在其定義的執行上下文環境中查找。

第三個 a 打印的值是 2:2。這句話所在的做用域鏈是 fn -> global,執行 console.log('2:' + a) 會首先在 fn 做用域裏查找 a,找到有 a,而且已經賦值爲2,因此結果就是2。

(2)變量賦值

var a = 1;
function fn() {
  console.log('1:' + a);
  a = 2
}
a = 3;
function bar() {
  console.log('2:' + a);
}
fn();
bar();

分別打印:1:3 2:2

「解析:」

第一個 打印的值是 1:3。首先, fn 中的 a = 2 是給變量 a 賦值,並非聲明變量。而後,執行函數 fn,此時 a 已經賦值爲3了,注意,fn()是在a=3後面執行。

第二個 打印的值是 2:2。函數 bar 所能訪問的做用域鏈爲 bar->global,在執行函數 bar 時,因爲在bar前執行了fn()將a修改成2了,因此這個時候拿到的a爲2。

(3)全局變量聲明提早

if(!(a in window)){
    var a = 10;
}
console.log(a);

打印:undefined

「解析:」

至關於:

var a;
if(!(a in window)){
    a = 10;
}
console.log(a);

用 var 定義的變量會提高到當前做用域的頂部(即當前全局做用域), 因此a會聲明提早到window中,但值仍是在原地,即爲undefined。 因此if獲得是a in window是ture 故不走裏面賦值 console.log(a) == undefined

上一個例子的變種:

(function(){
 var  x = c =  b = {a:1}
})()
console.log(c,b) // {a: 1} {a: 1}
console.log(x.a); // error , x is not defined

注意: x是在函數中聲明的,是局部變量,c和b未聲明,直接賦值,因此是全局變量。 賦值過程是從右往左的,即b={a:1},c=b,x=c

(4)變量提高/運算符順序

(function(){
  var a = b = 3;
})()
console.log(typeof a === "undefined"); // true
console.log(typeof b === "undefined"); // false
console.log(typeof b === "number" && b ===3); // true

// 這裏涉及的就是當即執行和閉包的問題,還有變量提高,運算符執行方向(=號自右向左)

// 那個函數能夠拆成這樣

(function()
  var a; /* 局部變量,外部無法訪問*/
  b = 3; /* 全局變量,so . window.b === 3 , 外部能夠訪問到*/
  a = b;
})()

(5)變量提高/運算符順序

var x = 1;
if (function f(){console.log(2)}) {
x += typeof f;  
}
console.log(x);  // 1undefined

//由於函數體在()中會以表達式去運行,fn函數不起做用,函數不會執行。

//最後表達式轉換爲true,f未聲明(上面的函數沒起做用),值爲undefined

「知識點:」

(1) 在JavaScript中,經過 let 和 const 定義的變量具備塊級做用域的特性。

(2) 經過 var 定義的變量會在它自身的做用域中進行提高,而 let 和 const 定義的變量不會。

(3) 每一個JavaScript程序都具備一個全局做用域,每建立一個函數都會建立一個做用域。

(4) 在建立函數時,將這些函數進行嵌套,它們的做用域也會嵌套,造成做用域鏈,子做用域能夠訪問父做用域,可是父做用域不能訪問子做用域。

(5) 在執行一個函數時,若是咱們須要查找某個變量值,那麼會去這個函數被 定義 時所在的做用域鏈中查找,一旦找到須要的變量,就會中止向上查找。

(6) 「變量的值由函數定義時的位置決定」這句話有歧義,準確說是查找變量時,是去定義這個函數時所在的做用域鏈查找。

8 閉包

「概念:」

閉包就是一個函數,這個函數可以訪問其餘函數做用域中的變量

「應用場景:」

  • 函數防抖
  • 封裝私有變量

JavaScript代碼的整個執行過程,分爲兩個階段,代碼編譯階段代碼執行階段。編譯階段由編譯器完成,將代碼翻譯成可執行代碼,這個階段做用域規則會肯定。執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段建立。

image.png

9 this

9-1 this的指向:

ES5中:

this 永遠指向最後調用它的那個對象

ES6箭頭函數:

箭頭函數的 this 始終指向函數定義時的 this,而非執行時

9-2 怎麼改變this的指向:

  • 使用 ES6 的箭頭函數
  • 在函數內部使用 _this = this
  • 使用 apply、call、bind
  • new 實例化一個對象
    案例1:

    var name = "windowsName";
      var a = {
          name : "Cherry",
          func1: function () {
              console.log(this.name)     
          },
          func2: function () {
              setTimeout(  function () {
                  this.func1()
              },100);
          }
      };
      a.func2()     // this.func1 is not a function

    在不使用箭頭函數的狀況下,是會報錯的,由於最後調用 setTimeout 的對象是
    window,可是在 window 中並無 func1 函數。能夠看作window.setTimeout

案例2:

var webName="long";
let func=()=>{
  console.log(this.webName);
}
func();//long

//箭頭函數在全局做用域聲明,因此它捕獲全局做用域中的this,this指向window對象

案例3:

var webName = "long";
function wrap(){
  let func=() => {
    console.log(this.webName);
  }
  func();
}
wrap();//long

//wrap函數執行時,箭頭函數func定義在wrap中,func會找到它最近一層非箭頭函數的this

//也就是wrap的this,而wrap函數做用域中的this指向window對象。

9-3 箭頭函數:

「Tips:」 衆所周知,ES6 的箭頭函數是能夠避免 ES5 中使用 this 的坑的。箭頭函數的 this 始終指向函數定義時的 this,而非執行時

箭頭函數須要記着這句話:「箭頭函數中沒有 this 綁定,必須經過查找做用域鏈來決定其值(箭頭函數自己沒有this,可是在它聲明時能夠捕獲別人的this供本身使用。),若是箭頭函數被非箭頭函數包含,則 this 綁定的是最近一層非箭頭函數的 this,不然,this 爲 undefined」。

特色

  • 沒有this
  • 沒有arguments
  • 不能經過new關鍵字調用
  • 沒有new.target
  • 沒有原型
  • 沒有super

詳細點擊這裏

10 原型和原型鏈

10-1 背景:

一個函數能夠當作一個類,原型是全部類都有的一個屬性,原型的做用就是給這個類的每個對象都添加一個統一的方法

10-2基本概念

「prototype :」 每一個函數都會這個屬性,這裏強調,是函數,普通對象沒有這個屬性的(這裏爲何說普通對象呢,由於JS裏面,一切皆爲對象,因此這裏的普通對象不包括函數對象)。它是構造函數的原型對象;

「「proto」 :」 每一個對象這個屬性,這裏強調,是對象,一樣,由於函數也是對象,因此函數也有這個屬性。它指向構造函數的原型對象;

「constructor :」 這是原型對象上的一個指向構造函數的屬性。

var webName = "long";
// Pig的構造函數
function Pig(name, age) {
    this.name = name;
    this.age = age;
}
// 建立一個Pig的實例,小豬佩奇
var Peppa = new Pig('Peppa', 5);
Peppa.__proto__ === Pig.prototype。 //true
Pig.__proto__ === Function.prototype //true
Pig.prototype.constructor === Pig //true

奇淫技巧: 韓信對飲(函數的顯示原型 = 對象的隱士原型)
詳細點擊這裏

10-3 什麼是原型繼承

一個對象可使用另外一個對象的屬性或者方法,就稱之爲繼承

具體是經過將這個對象的原型設置爲另一個對象,這樣根據原型鏈的規則,若是查找一個對象屬性且在自身不存在時,就會查找另一個對象,至關於一個對象可使用另一個對象的屬性和方法了。

11 深淺拷貝

11-1 淺克隆

function shallowClone(obj) {
  let cloneObj = {};
  
  for (let i in obj) {
    cloneObj[i] = obj[i];
  }
  
  return cloneObj;
}

11-2 深克隆

深克隆:

  • 考慮基礎類型
  • 引用類型
  • RegExp、Date、函數 不是 JSON 安全的
  • 會丟失 constructor,全部的構造函數都指向 Object
  • 破解循環引用

    function deepCopy(obj) {
    if (typeof obj === 'object') {
      var result = obj.constructor === Array ? [] : {};
      
      for (var i in obj) {
        result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
      }
    } else {
      var result = obj;
    }
    
    return result;
    }

詳細點擊這裏

12 數組

12-1數組去重

1. es6的new Set

let arr=[1,2,2,3]
let arrNew=[...new Set(arr)]
console.log(arrNew)//[1,2,3]

2. 遍歷舊數組往新數組中添加惟一的元素

function unique(arr) {
   var newArr = []
   for (var i = 0; i < arr.length; i++) {
      f (newArr.indexOf(arr[i])===-1) {//方式1
          newArr.push(arr[i])
       }
       // if (!newArr.includes(arr[i])) {//方式2
         // newArr.push(arr[i])
       //}
    }
    return newArr
    }
   console.log(unique(arr))//[1,2,3]

3. 利用Map數據結構去重

function unique(){
  let map =new Map()
  let arr=new Array()
  for(let i=0;i<arr.length;i++){
   if(map.has(arr[i])){//若是有key值,它是把元素值做爲key
      map.set(arr[i],true)
   }else{
     map.set(arr[i],false)//若是沒有該key值
     array.push(arr[i])
   }
 }
 return array
}
console.log(unique([1,2,2,3]))

解析:

把每個元素做爲key值存到Map中,因爲Map不會出現相同的key,因此最終獲得去重後的結果。

12-2數組展開

1. flat方法

let arr = [1,2,[3,4],[5[6,7]]]
  let arr1 = arr.flat(Infinity)//[1,2,3,4,5,6,7]

2.join,split

let arr = [1,2,[3,4],[5[6,7]]]
let arr1 = arr.join().split(",")  //["1", "2", "3", "4", ""]

3.toString,split

let arr = [1,2,[3,4],[5[6,7]]]
let arr1 = arr.toString().split(",")   //["1", "2", "3", "4", ""]

12-3數組合並

1. es6展開合併

let arr1 = [1,2]
  let arr2 = [3,4]
  let arr = [...arr1,...arr2]//[1,2,3,4]

2. concat

let arr = arr1.concat(arr2)

12-4判斷數組

instanceof

console.log(arr instanceof Array)

constructor

console.log(arr.constructor === Array)

Array.isArray

console.log(Array.isArray(arr))

toString

console.log(Object.prototype.toString.call(arr) === "[object Array]")

判斷是否有數組的push等方法

console.log(!!arr.push && !!arr.concat)

13 對象

13-1 如何判斷一個對象是否是空對象

1.將json對象轉化爲json字符串,再判斷該字符串是否爲"{}"

var data = {};

var b = (JSON.stringify(data) == "{}"); //true

2.for in 循環判斷

var obj = {};
var b = function() {
    for(var key in obj) {
        return false;
    }
    return true;
}

b();//true

3.jquery的isEmptyObject方法

此方法是jquery將2方法(for in)進行封裝,使用時須要依賴jquery

var data = {};

var b = $.isEmptyObject(data);

alert(b);//true

4.Object.getOwnPropertyNames()方法

此方法是使用Object對象的getOwnPropertyNames方法,獲取到對象中的屬性名,存到一個數組中,返回數組對象,咱們能夠經過判斷數組的length來判斷此對象是否爲空

注意:此方法不兼容ie8,其他瀏覽器沒有測試

var data = {};

var arr = Object.getOwnPropertyNames(data);

alert(arr.length == 0);//true

5.使用ES6的Object.keys()方法

與4方法相似,是ES6的新方法, 返回值也是對象中屬性名組成的數組

var data = {};

var arr = Object.keys(data);

alert(arr.length == 0);//true

13 v8引擎

13-1 v8垃圾回收

「背景:」

V8的垃圾回收策略基於分代回收機制,該機制又基於 世代假說

該假說有兩個特色

  • 大部分新生對象傾向於早死
  • 不死的對象,會更久

基於這個理論,現代垃圾回收算法根據對象的存活時間將內存進行了分代,並對不一樣分代的內存採用不一樣的高效算法進行垃圾回收。

「V8的內存分代」

在V8中,將內存分爲了新生代(new space)和老生代(old space)。

它們特色以下:

新生代:對象的存活時間較短。新生對象或只通過一次垃圾回收的對象。

老生代:對象存活時間較長。經歷過一次或屢次垃圾回收的對象。

「V8堆的空間」

V8堆的空間等於新生代空間加上老生代空間。咱們能夠經過 --max-old-space-size命令設置老生代空間的最大值,--max-new-space-size 命令設置新生代空間的最大值。老生代與新生代的空間大小在程序初始化時設置,一旦生效則不能動態改變。

  • node --max-old-space-size=1700 test.js // 單位爲 MB
  • node --max-new-space-size=1024 test.js // 單位爲KB

默認設置下,64位系統的老生代大小爲1400M,32位系統爲700M。

對於新生代,它由兩個 reserved_semispace_size 組成。每一個reserved_semispace_size 的大小在不一樣位數的機器上大小不一樣。默認設置下,在64位與32位的系統下分別爲16MB和8MB。咱們將新生代、老生代、reserved_semispace_size 空間大小總結以下表。

類型\系統位數 64位 32位
老生代 1400MB 700MB
reserved_semispace_size 16MB 8MB
新生代 32MB 16MB

詳細點擊這裏

14 event loop

14-1 什麼是事件循環

詳細點擊這裏1

詳細點擊這裏2

15 嚴格模式的優缺點

15-1 概念

ECMAScript 5 中引入的一種將更好的 錯誤檢查引入代碼中的方法, 如今已經被大多瀏覽器實現. 這種模式使得Javascript在 更嚴格條件下運行

15-2 優勢

  • 沒法再意外建立全局變量。
  • 會使引發靜默失敗(silently fail,即:不報錯也沒有任何效果)的賦值操拋出異常。
  • 試圖刪除不可刪除的屬性時會拋出異常(以前這種操做不會產生任何效果)。
  • 要求函數的參數名惟一。
  • 全局做用域下,this的值爲undefined。
  • 捕獲了一些常見的編碼錯誤,並拋出異常。
  • 禁用使人困惑或欠佳的功能。

15-3 缺點

  • 缺失許多開發人員已經習慣的功能。
  • 沒法訪問function.caller和function.arguments。
  • 以不一樣嚴格模式編寫的腳本合併後可能致使問題。

16 ES6

16-1 背景

EC版本 發佈時間 新增特性
2009(ES5) 2009年11月 擴展了Object、Array、Function的功能等新增特性
2015(ES6) 2015年6月 類,模塊化,箭頭函數,函數參數默認值等
2016(ES7) 2016年3月 includes,指數操做符
2017(ES8) 2017年6月 sync/await,Object.values(),Object.entries(),String padding等

16-2 經常使用特性

  • 模塊化
  • 箭頭函數
  • 函數參數默認值
  • 模板字符串
  • 解構賦值
  • 延展操做符
  • 對象屬性簡寫
  • Promise
  • Let與Const

「(1) 類(class)」

傳統的javascript中只有對象,沒有類的概念。

它是基於原型的面嚮對象語言。原型對象特色就是將自身的屬性共享給新對象。
這樣的寫法相對於其它傳統面嚮對象語言來說,頗有一種獨樹一幟的感腳!很是容易讓人困惑!
若是要生成一個對象實例,須要先定義一個構造函數,而後經過new操做符來完成。

下面用一個例子演示構造函數到class的演變:

構造函數--

function Person(name,age) {
    this.name = name;
    this.age=age;
}
Person.prototype.say = function(){
    return "個人名字叫" + this.name+"今年"+this.age+"歲了";
}
var obj=new Person("laotie",88);
//經過構造函數建立對象,必須使用new 運算符
console.log(obj.say());//個人名字叫laotie今年88歲了

ES6引入了Class(類)這個概念,經過class關鍵字能夠定義類。

該關鍵字的出現使得其在對象寫法上更加清晰,更像是一種面向對象的語言。

若是將以前的代碼改成ES6的寫法就會是這個樣子:

class--

class Person{//定義了一個名字爲Person的類
    constructor(name,age){//constructor是一個構造方法,用來接收參數
        this.name = name;//this表明的是實例對象
        this.age=age;
    }
    say(){//這是一個類的方法,注意千萬不要加上function和逗號
        return "個人名字叫" + this.name+"今年"+this.age+"歲了";
    }
}
var obj=new Person("laotie",88);
console.log(obj.say());//個人名字叫laotie今年88歲了
console.log(typeof Person);//function--類實質上就是一個函數
console.log(Person===Person.prototype.constructor);//true
//類自身指向的就是構造函數。因此能夠認爲ES6中的類其實就是構造函數的另一種寫法!

注意項:

  1. 在類中聲明方法的時候,千萬不要給該方法加上function關鍵字
  2. 方法之間不要用逗號分隔,不然會報錯
  3. class不存在變量提高,因此須要先定義再使用。由於ES6不會把類的聲明提高到代碼頭部,可是ES5就不同,ES5存在變量提高,能夠先使用,而後再定義。

    //ES5能夠先使用再定義,存在變量提高
    new A();
    function A(){
    }
    //ES6不能先使用再定義,不存在變量提高 會報錯
    new B();//B is not defined
    class B{
    }

    「(2) 模塊化(Module)」

背景

在以前的javascript中是沒有模塊化概念的。若是要進行模塊化操做,須要引入第三方的類庫。隨着技術的發展,先後端分離,前端的業務變的愈來愈複雜化。直至ES6帶來了模塊化,才讓javascript第一次支持了module。ES6的模塊化分爲導出(export)導入(import)兩個模塊。

export的用法

在ES6中每個塊便是一個文件,在文件中定義的變量,函數,對象在外部沒法獲取的。若是你但願外部能夠讀取模塊當中的內容,就必須使用export來對其進行暴露(輸出)。

先來看個例子,來對一個變量進行模塊化。

//咱們先來建立一個test.js文件,來對這一個變量進行輸出:
export let myName="laowang";
//而後能夠建立一個index.js文件,以import的形式將這個變量進行引入:
import {myName} from "./test.js";
console.log(myName);//laowang

若是要輸出多個變量能夠將這些變量包裝成對象進行模塊化輸出:

let myName="laowang";
let myAge=90;
let myfn=function(){
    return "我是"+myName+"!今年"+myAge+"歲了"
}
export {
    myName,
    myAge,
    myfn
}

******************************接收的代碼調整爲********************* 

import {myfn,myAge,myName} from "./test.js";
console.log(myfn());//我是laowang!今年90歲了
console.log(myAge);//90
console.log(myName);//laowang

若是你不想暴露模塊當中的變量名字,能夠經過as來進行操做:

let myName="laowang";
let myAge=90;
let myfn=function(){
    return "我是"+myName+"!今年"+myAge+"歲了"
}
export {
    myName as name,
    myAge as age,
    myfn as fn
}
/******************************接收的代碼調整爲**********************/
import {fn,age,name} from "./test.js";
console.log(fn());//我是laowang!今年90歲了
console.log(age);//90
console.log(name);//laowang

也能夠直接導入整個模塊,將上面的接收代碼修改成:

import * as info from "./test.js";//經過*來批量接收,as 來指定接收的名字
console.log(info.fn());//我是laowang!今年90歲了
console.log(info.age);//90
console.log(info.name);//laowang

默認導出(default export) 一個模塊只能有一個默認導出,對於默認導出,導入的名稱能夠和導出的名稱不一致。

/******************************導出**********************/
export default function(){
    return "默認導出一個方法"
}
/******************************引入**********************/
import myFn from "./test.js";//注意這裏默認導出不須要用{}。
console.log(myFn());//默認導出一個方法

能夠將全部須要導出的變量放入一個對象中,而後經過default export進行導出

/******************************導出**********************/
export default {
    myFn(){
        return "默認導出一個方法"
    },
    myName:"laowang"
}
/******************************引入**********************/
import myObj from "./test.js";
console.log(myObj.myFn(),myObj.myName);//默認導出一個方法 laowang

一樣也支持混合導出

/******************************導出**********************/
export default function(){
    return "默認導出一個方法"
}
export var myName="laowang";
/******************************引入**********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);//默認導出一個方法 laowang

重命名export和import 若是導入的多個文件中,變量名字相同,即會產生命名衝突的問題,爲了解決該問題,ES6爲提供了重命名的方法,當你在導入名稱時能夠這樣作:

/******************************test1.js**********************/
export let myName="我來自test1.js";
/******************************test2.js**********************/
export let myName="我來自test2.js";
/******************************index.js**********************/
import {myName as name1} from "./test1.js";
import {myName as name2} from "./test2.js";
console.log(name1);//我來自test1.js
console.log(name2);//我來自test1.js

「(3) 箭頭(Arrow)函數」

查看1-5-3 箭頭函數小節:

箭頭函數的 this 始終指向函數定義時的 this,而非執行時。箭頭函數須要記着這句話:「箭頭函數中沒有 this 綁定,必須經過查找做用域鏈來決定其值,若是箭頭函數被非箭頭函數包含,則 this 綁定的是最近一層非箭頭函數的 this,不然,this 爲 undefined」。

「(4) 函數參數默認值」

ES6支持在定義函數的時候爲其設置默認值:

function foo(height = 50, color = 'red')
{
    // ...
}

不使用默認值:

function foo(height, color)
{
    var height = height || 50;
    var color = color || 'red';
    //...
}

這樣寫通常沒問題,但當參數的布爾值爲false時,就會有問題了。
好比,咱們這樣調用foo函數: foo(0, "") 由於0的布爾值爲false,這樣height的取值將是50。同理color的取值爲‘red’。 因此說,函數參數默認值不只能是代碼變得更加簡潔並且能規避一些問題。

「(5) 模板字符串」

ES6支持在定義函數 ES6支持模板字符串,使得字符串的拼接更加的簡潔、直觀。

不使用模板字符串:

var name = 'Your name is ' + first + ' ' + last + '.'

使用模板字符串:

var name = `Your name is ${first} ${last}.`

在ES6中經過${}就能夠完成字符串的拼接,只須要將變量放在大括號之中。

「(6) 解構賦值」

解構賦值語法是JavaScript的一種表達式,能夠方便的從數組或者對象中快速提取值賦給定義的變量

獲取數組中的值

//從數組中獲取值並賦值到變量中,變量的順序與數組中對象順序對應。
var foo = ["one", "two", "three", "four"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
//若是你要忽略某些值,你能夠按照下面的寫法獲取你想要的值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"
//你也能夠這樣寫
var a, b; //先聲明變量
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
//若是沒有從數組中的獲取到值,你能夠爲變量設置一個默認值。
var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
//經過解構賦值能夠方便的交換兩個變量的值。
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

獲取對象中的值

const student = {
  name:'Ming',
  age:'18',
  city:'Shanghai'  
};
const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"

「(7) 延展操做符」

在ECMAScript 2018中延展操做符增長了對對象的支持,用於對像和數組的拆解

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆後的對象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合併後的對象: { foo: "baz", x: 42, y: 13 } 相同屬性會進行覆蓋
//咱們能夠這樣合併數組:
var arr1=['a','b','c'];
var arr2=[...arr1,'d','e']; //['a','b','c','d','e']
//展開運算符也能夠用在push函數中,能夠不用再用apply()函數來合併兩個數組:
var arr1=['a','b','c'];
var arr2=['d','e'];
arr1.push(...arr2); //['a','b','c','d','e']
//用於解構賦值
let [arg1,arg2,...arg3] = [1, 2, 3, 4];
arg1 //1
arg2 //2
arg3 //['3','4']
//展開運算符既然能合併數組,天然也能解構數組,不過要注意,解構賦值中【展開運算符】只能用在【最後】:
let [arg1,...arg2,arg3] = [1, 2, 3, 4]; //報錯

「(8) 對象屬性簡寫」

在ES6中容許咱們在設置一個對象的屬性的時候不指定屬性名。

不使用ES6

const name='Ming',age='18',city='Shanghai';
const student = {
    name:name,
    age:age,
    city:city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
//對象中必須包含屬性和值,顯得很是冗餘。

使用ES6

const name='Ming',age='18',city='Shanghai'; 
const student = {
    name,
    age,
    city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
//對象中直接寫變量,很是簡潔。

「(9) Promise」 什麼是 Promise

Promise 是異步編程的一種解決方案,比傳統的異步解決方案回調函數事件更合理、更強大。現已被 ES6 歸入進規範中。

下面經過幾個案例來加深promise的瞭解:

(1) Promise 構造函數是同步執行的.then是異步的

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
//運行結果:1 2 4 3

解析:Promise 構造函數是同步執行的,promise.then 中的函數是異步執行的。

(2) promise 狀態一旦改變則不能再變

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})
promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//運行結果:then: success1

解析:構造函數中的 resolve 或 reject 只有第一次執行有效屢次調用沒有任何做用,promise 狀態一旦改變則不能再變。

(3) .then 或者 .catch 都會返回一個新的 promise

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
//運行結果:1 2

解析:promise 能夠鏈式調用。提起鏈式調用咱們一般會想到經過 return this 實現,不過 Promise 並非這樣實現的。promise 每次調用 .then 或者 .catch 都會返回一個新的 promise,從而實現了鏈式調用。

(4) Promise 構造函數只執行一次

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})
//運行結果:once
          success 1005
          success 1007

解析:promise 的 .then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變(第一次調用.then就改變了),而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。

(5) .then 或者 .catch 都會返回一個新的 promise

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//運行結果:then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

解析:.then 或者 .catch 中 return 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch 捕獲,須要改爲其中一種:

  1. return Promise.reject(new Error('error!!!'))
  2. throw new Error('error!!!')

由於返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價於 return Promise.resolve(new Error('error!!!'))。

(6) .then 或 .catch 返回的值不能是 promise 自己

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)
//運行結果:TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3

解析:.then 或 .catch 返回的值不能是 promise 自己,不然會形成死循環。

(7) .then函數返回值類型與參數傳遞

Promise.resolve(2) // resolve(2) 函數返回一個Promise<number>對象
.then(x=>{
   console.log( x ); // 輸出2, 表示x類型爲number且值爲2,也就是上面resolve參數值
   return "hello world"; // 回調函數返回字符串類型,then函數返回Promise<string>
}) // then函數返回Promise<string>類型
.then(x=>{
   console.log( x ); // 輸出hello world,也就是上一個then回調函數返回值,代表上一個then的返回值就是下一個then的參數
}) // then函數回調函數中沒有返回值,則爲Promise<void>
.then(x=>{ // 前面的then的回調函數沒有返回值因此這個x是undefined
   console.log( x ); // undefined
}) // Promise<void>
.then(()=>{ // 前面沒有返回值,這裏回調函數能夠不加返回值
   return Promise.resolve("hello world"); // 返回一個Promise<string>類型
}) // 這裏then的返回值是Promise<string>
.then(x=>{ // 雖然上面的then中回調函數返回的是Promise<string>類型可是這裏x並非Promise<string>類型而是string類型
   console.log(x); // hello world
   return Promise.resolve(2); // 返回一個Promise<number>類型對象
}) // 返回Promise<number>類型

(8) .then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)  //1

解析:.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。

(9) .catch 至關於.then的簡寫(省略了.then的第二個參數)

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
//運行結果:fail2: Error: error
          at success (...)
          at ...

解析:.then 能夠接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。.catch也至關因而一個.then,只不過把.then的第二個參數省略了,可是它們用法上有一點須要注意:.then 的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,然後續的 .catch 能夠捕獲以前的錯誤。

(10) 微任務宏任務執行順序

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
//運行結果:end
          nextTick
          then
          setImmediate

解析:process.nextTick 和 promise.then 都屬於 microtask,而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每一個階段(macrotask)之間都會執行 microtask,事件循環的開始會先執行一次 microtask。

「(10) let 和 const」

查看1-1js變量聲明

17 事件流

事件流是網頁元素接收事件的順序,"DOM2級事件"規定的事件流包括三個階段:事件捕獲階段處於目標階段事件冒泡階段

  • 首先發生的事件捕獲,爲截獲事件提供機會。
  • 而後是實際的目標接受事件。
  • 最後一個階段是時間冒泡階段,能夠在這個階段對事件作出響應。

雖然捕獲階段在規範中規定不容許響應事件,可是實際上仍是會執行,因此有兩次機會獲取到目標對象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p id="parEle">我是父元素    
            <span id="sonEle">我是子元素</span>
        </p>
    </div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');

parEle.addEventListener('click', function () {
    alert('父級 冒泡');
}, false);
parEle.addEventListener('click', function () {
    alert('父級 捕獲');
}, true);

sonEle.addEventListener('click', function () {
    alert('子級冒泡');
}, false);
sonEle.addEventListener('click', function () {
    alert('子級捕獲');
}, true);

</script>

當容器元素及嵌套元素,即在捕獲階段又在冒泡階段調用事件處理程序時:事件按DOM事件流的順序執行事件處理程序:

父級捕獲=》子級捕獲=》子級冒泡=》父級冒泡

  • 點擊【我是父元素】,依次彈出('父級 捕獲'=》'父級 冒泡')
  • 點擊【我是父元素】,依次彈出('父級 捕獲'=》'子級 捕獲'=》'子級 冒泡'=》'父級 冒泡')

奇淫技巧:三個階段能夠這麼記 捕母貓(捕獲,目標,冒泡)

18 new

構造調用:

  • 創造一個全新的對象
  • 這個對象會被執行 [[Prototype]] 鏈接,將這個新對象的 [[Prototype]] 連接到這個構造函數.prototype 所指向的對象
  • 這個新對象會綁定到函數調用的 this
  • 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象

19 手寫promise

20 js腳本加載問題,async、defer

20-1 正常模式

這種狀況下 JS 會阻塞瀏覽器,瀏覽器必須等待 index.js 加載和執行完畢才能去作其它事情。

<script src="index.js"></script>

20-2 async(異步) 模式

async 模式下,JS 不會阻塞瀏覽器作任何其它的事情。它的加載是異步的,當它加載結束,JS 腳本會當即執行。

<script async src="index.js"></script>

20-3 defer(延緩) 模式

defer 模式下,JS 的加載是異步的,執行是被推遲的。等整個文檔解析完成、DOMContentLoaded 事件即將被觸發時,被標記了 defer 的 JS 文件纔會開始依次執行。

<script defer src="index.js"></script>

從應用的角度來講,通常當咱們的腳本與 DOM 元素和其它腳本之間的依賴關係不強時,咱們會選用 async;當腳本依賴於 DOM 元素和其它腳本的執行結果時,咱們會選用 defer。

21 性能優化

什麼是性能優化,爲何要優化,咱們優化的對象是什麼,考慮清楚這幾個問題,咱們才能找到對應的解決辦法。

什麼是性能優化?

在不影響系統運行正確性的前提下,使之運行地 更快完成特定 功能所需的時間 更短。簡而言之,就是讓咱們的程序更快的運行。

爲何要性能優化?

性能是留住用戶很重要的一環,通常人們能忍受一個網頁的加載時長是少於5秒,性能優化也是程序高效運行的保障。

咱們優化的對象是什麼?

優化對象是 程序,以及程序所運行在的載體(如瀏覽器)

咱們已經知道了性能優化的對象是什麼了,那麼接下來就能夠根據優化對象分開幾個大類總結,對於前端來講,程序和載體無非如下5點:

  1. html
  2. css
  3. js
  4. 程序相關的工具
  5. 瀏覽器

這樣咱們再展開去說就能說的清楚,不會遺漏。

總結每一個大類的時候,先從整個文檔格式開始,再到外部資源,再到代碼層面

1. html

html應該首先想到語義化標籤,正確的語義化能夠咱們的文檔結構更加清晰。
js文件寫在body標籤以後,可讓防止阻塞頁面的加載。

  • 語義化標籤,結構清晰
  • js文件正確放置,防止阻塞

2.css

  • css文件應該放在body標籤頂部,防止頁面二次渲染而抖動。
  • 當小圖片多的時候可使用雪碧圖,減小頁面請求。
  • 小圖標能夠用base64格式,減小頁面請求。
  • 公共的css抽離,代碼複用
  • 多個css合併,減小HTTP請求
  • 選擇器優化嵌套,儘可能避免層級過深,縮短查找過程
  • 充分利用css繼承屬性,減小代碼量
  • 減小頁面的重繪,能夠先用一個變量操做全部樣式後,最後一步把這個變量關聯到dom上

3.js

  • 抽離公共的js,代碼複用
  • 抽離公共的組件,代碼複用
  • 定時器記得清除,較少內存的耗用
  • v-if和v-show正確使用,較少頁面dom的構建
  • 節流、防抖,防止意外的觸發
  • 長列表滾動到可視區域動態加載(大數據渲染)
  • computed 和 watch 區分使用場景,減小性能消耗
  • v-for 遍歷必須爲 item 添加 key,且避免同時使用 v-if

4.webpack

  • 去除代碼註釋,壓縮程序
  • Webpack 對圖片進行壓縮-------先引入npm install image-webpack-loader --save-dev,而後在 webpack.config.js 中配置
  • 減小 ES6 轉爲 ES5 的冗餘代碼
  • 提取公共代碼
  • 模板預編譯
  • 優化 SourceMap
  • 構建結果輸出分析
  • 使用webpack-bundle-analyzer查看項目全部包及體積大小

5. 瀏覽器

  • 首屏加載loading,優化體驗
  • 使用緩存,減小重複請求
  • 啓用gzip壓縮,減小請求
  • 使用cnd,縮短請求鏈
  • 使用圖片懶加載,組件懶加載,路由懶加載,減小請求
  • 第三方插件的按需引入
  • 服務端渲染 SSR or 預渲染
  • 使用 Chrome Performance 查找性能瓶頸,針對性的優化

掘金主頁
csdn主頁

相關文章
相關標籤/搜索