第二章. js進階【必備篇】

整理中高級前端系列,能夠看成面試複習,也能夠看成實戰來看,分享一下 方便本身,方便他人。有不足的地方歡迎評論~javascript

第一趴:css進階css

第二趴:js進階前端

第三趴:vue框架進階vue

第四趴:工程化java

專業術語

  • 常量、變量、數據類型
  • 形參、實參
  • 匿名函數、具名函數、⾃執⾏函數
  • 函數聲明、函數表達式
  • 堆、棧
  • 同步、異步、進程、線程

執行上下文

當函數執行時,去建立一個稱爲「執行上下文(execution contex)」的環境,分爲 建立和執行 兩個階段node

建立階段

是指 函數被調用但未被執行任何代碼時,此時建立了一個擁有3個屬性的對象:git

代碼執行階段

主要工做:一、分配變量、函數的飲用、賦值   二、執行代碼es6

做用域

js中有全局做用域、函數做用域,es6中又增長了 塊級做用域。 做用域最大的用途就是 隔離變量或函數,並控制他們的生命週期做用域是在函數執行上下文建立時定義好的不是函數執行時定義的。github

做用域鏈

當一個塊或函數嵌套在另外一個塊或函數中時,就發生了做用域的嵌套。若是在當前做用域沒法找到會向上一級做用域尋找,直到找到或抵達全局做用域,這樣的鏈式關係就是 做用域鏈(Scope Chain)web

閉包

能夠訪問其餘函數做用域中(內部)變量的函數

  • 能夠讀取函數內部的變量
  • 讓這些變量始終保持再內存中,即:閉包可使它誕生的環境一直存在。

平時怎麼用,應用場景連接

什麼節點循環綁定事件,解決方案 當即執行函數、var => let、

this的5種場景

一、函數直接調用時

function myfunc() {
	console.log(this) // this是widow
}
var a = 1;
myfunc();

// 常考點
function show () {
    console.log('this:', this); // this是window
}
var obj = {
    show: function() {
        show(); // 函數被直接調用了
    }
};
obj.show()
複製代碼

二、函數被別人調用時

function myfunc(){
	console.log(this) // this是對象a
}
var a = { myfunc: myfunc }
a.myfunc();
複製代碼

三、new一個實例時(構造函數)

function Penson(name) {
  this.name = name;
	console.log(this); // this是實例p
}
var p = new Penson('this:::');
複製代碼

四、apply、call、bind

function getColor(color) {
  this.color = color;
  console.log(this);
}
function Car(name, color){
  this.name = name; // this指的是實例car
  getColor.call(this, color); // 這⾥的this從本來的getColor,變成了car
}
var car = new Car('卡⻋', '綠⾊');
複製代碼

五、箭頭函數時

// 箭頭函數
var a = {
  myfunc: function() {
    setTimeout(() => {
    console.log(this); // this是a
    }, 0)
  }
};
a.myfunc();
複製代碼

最強總結:一字一句都是重點

一、對於直接調⽤的函數來講,無論函數被放在了什麼地⽅this都是window 二、對於被別⼈調⽤的函數來講,被誰點出來的,this就是誰 三、在構造函數中,類中(函數體中)出現的 this.xxx = xxx 中的 this是當前類的⼀個實例 四、call、apply時,this是第⼀個參數。bind要優於call/apply哦,call參數多,apply參數少 五、箭頭函數沒有⾃⼰的this,須要看其外層是否有函數,若是有,外層函數的this就是內部箭頭函數 的this,若是沒有,則this是window

call、bind、apply

call()、apply()、bind() 都是用來重定義 this 這個對象的!

  • call:的參數是直接放進去obj.myFun.call(db,'string1', ... ,'stringN' )
  • apply:參數都放在一個數組裏面傳進去 obj.myFun.apply(db,['成都',...,'string' ])
  • **bind:返回是函數,須要本身再執行,它 的參數和 call **同樣

面向對象

構造函數

自己就是一個函數,爲了規範通常將首字母大寫,區別在於 使用 new 生成實例的函數就是構造函數,直接調用的就是 普通函數。

prototype原型

每一個對象都有其原型對象,對象從原型那繼承屬性和方法,這些屬性和方法定義在 對象的構造函數的 prototype 屬性上,而非對象實例自己。

原型鏈繼承

深拷貝 和 淺拷貝

來源於賦值(址)

  • 基本數據類型 **賦值 **以後兩個變量互不影響
  • 引用類型 賦址 ,兩個變量具備相同得引用,指向同一個對象,相互影響(因此纔出現了 深拷貝和淺拷貝)

淺拷貝

簡單來說,淺拷貝 之解決了 第一層問題 基本類型值和引用類型地址

  • Object.assign() :

展開語法 Spread ...

let a = {
    name: "ceshi",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = {...a};
console.log(b);
// {
// name: "ceshi",
// book: {title: "You Don't Know JS", price: "45"}
// }
複製代碼

深拷貝

拷貝全部屬性以及指向得動態分配的內存。拷貝先後兩個 對象互不影響。

  • JSON.parse(JSON.stringify(object)) 拷貝對象沒問題,可是 數組有問題:undefined、symbol 和函數這三種狀況,會直接忽略。
  • 因此 誕生了 lodash.cloneDeep()

總結

-- 和原數據是否指向同一對象 第一層數據爲基本數據類型 原數據中包含子對象
賦值 改變會使原數據一同改變 改變使原數據一同改變
淺拷貝 改變不會使原數據一同改變 改變使原數據一同改變
深拷貝 改變不會使原數據一同改變 改變不會使原數據一同改變

如何實現一個深拷貝

思路:淺拷貝 + 遞歸,淺拷貝時 判斷屬性值是不是對象,是對象就進行遞歸操做。

export function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = source.constructor === Array ? [] : {}
  Object.keys(source).forEach(keys => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys])
    } else {
      targetObj[keys] = source[keys]
    }
  })
  return targetObj
}
複製代碼

高階

防抖和節流

防抖:debounce

某個函數在一段時間內不管被觸發了多少次,都只執行最後一次。(會延長觸發時間)

實現原理是利用定時器,函數第一次執行時設置一個定時器,調用時 發現已有定時器那麼清空以前定時器重新設置一個新的定時器,直到最後一個定時器到時觸發函數執行

function debounce(fn, awit = 50) {
  let timer = null
  return function(...args) {
    // 存在定時器則清空
    if (timer)  clearTimeout(timer)
    // 第一次設置定時器
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, awit);
  }
}
const ceshiFnDeb = debounce(()=> console.log('ceshiFnDeb防抖函數執行了'), 1000)
document.addEventListener('scroll', ceshiFnDeb)
複製代碼

節流:throttle 適合用於 函數被頻繁調用的場景

函數節流指的是 某個函數在 指定時間內(n秒)只執行一次,無論後面函數如何調用請求,不會延長時間間隔,n秒間隔結束後 第一次遇到 新的函數調用會觸發執行,以此類推。

// 節流 throttle: 固定間隔時間內只調用一次函數
// 思路1:根據兩個時間戳來對比
function throttle (fn, awit = 50) {
  let startTime = 0
  return function(...args){
    let nowTime = +new Date()
    // 當前時間和上次時間作對比,大於間隔時間的話,就能夠執行函數fn
    if (nowTime - startTime > awit) {
      // 執行完函數以後重置初始時間,等於最後一次觸發的時間
      startTime = nowTime
      fn.apply(this, args)
    }
  }
}
// 執行 throttle 函數返回新函數
const ceshiFn = throttle(()=> console.log('ceshiFn節流函數執行了'), 2000)
// 每 10 毫秒執行一次 betterFn 函數,可是隻有時間差大於 1000 時纔會執行 fn
setInterval(ceshiFn, 10);
複製代碼

不過市面上比較全的仍是 lodash的防抖節流函數 **

事件循環Event Loop

宏任務 MacroTask

script所有代碼、setTimeOut、setInterval、I/O、

微任務MicroTask

Promise、Process.nextTick(Node獨有)

這裏概括3個重點

  1. 首先執行宏任務隊列script,一次只從隊列中去一個任務執行,執行完後 去執行微任務隊列中的任務;
  2. 微任務隊列中全部的任務會被 依次取出來執行,直到microtask queue爲空
  3. 期間要執行 UI rendering,它的節點是在執行完全部 微任務以後,下一個宏任務以前,緊跟着執行UI render

Promise對象 - 手寫

自己是一個構造函數,自身有 all、reject、resolve方法,原型上有 then、catch方法

promise的then能夠接受兩個函數,第一個resolve,第二個參數爲reject

  • resolve:將promise的狀態padding變爲 fullfiled已完成,通常用來進行業務代碼處理
  • reject:將狀態padding變爲 rejected已拒絕,通常用來進行攔截報錯處理
  • then:就是把原來的回調寫法分離出來,在異步操做Async執行完後,用鏈式調用的方式執行回調函數
  • all:接受一個數組參數,並行 執行異步操做的能力,在全部異步操做執行完以後才執行回調then,all會把全部異步操做的結果放進一個數組中傳給then。

async 和 await

函數返回一個 Promise對象,若是內部發生異常則會致使返回的 Promise 對象狀態變爲reject狀態。拋出的錯誤會被catch方法接收到。

async表示函數裏有異步操做

async函數內部 return 返回的值。會成爲then方法回調函數的參數。

await 表示緊跟在後面的表達式須要等待結果(同步)

正常狀況下,await 命令後面跟着的是Promise對象,返回對象的結果,若是不是的話,就直接返回對應的值。

Proxy與Object.definproperty()

Object.defineProperty()

Vue 2.x 利用 Object.defineProperty(),而且把內部解耦爲 Observer, Dep, 並使用 Watcher 相連

  1. 不能監聽數組的變化,這些方法沒法觸發set:push、pop、shift、unshift、splice、sort、
  2. 必須遍歷對象的每一個屬性
  3. 必須深層遍歷嵌套的對象

Proxy

Vue 在 3.x 版本以後改用 Proxy 進行實現

  1. 針對整個對象而不是對象的某個屬性
  2. 支持數組
  3. 嵌套支持 是 get裏面遞歸調用Proxy 並返回

模塊化編程

爲了解決兩大痛點: 一、每一個模塊都要有本身的 變量做用域,兩個模塊之間的內部變量不會產生衝突。 二、不一樣模塊之間保留相互 導入和導出的方式方法,模塊之間可以相互通訊,模塊的執行與加載遵循必定的規範,能保證彼此之間的依賴關係

模塊化開發的4個好處:

  1. 避免變量污染,命名衝突
  2. 提升代碼複用率
  3. 提升 維護性
  4. 方便管理依賴關係

CommonJS

是服務器端模塊的規範,Node.js採用了這個規範。

每一個JS文件就是一個模塊(module),每一個模塊內部使用 require 函數和 module.exports 對象來對模塊進行導入和導出

AMD 異步模塊定義

適合web開發的模塊化規範

模塊文件中,咱們使用 define 函數定義一個模塊,在回調函數中接受定義組件內容。這個回調函數接受一個 require 方法,可以在組件內部加載其餘模塊,這裏咱們分別傳入 模塊ID,就能加載對應文件內的AMD模塊。

// moduleA.js
define(function(require) {
var m = require('moduleB');
setTimeout(() => console.log(m), 1000);
});

// moduleB.js
define(function(require) {
var m = new Date().getTime();
return m;
});

// index.js
require(['moduleA', 'moduleB'], function(moduleA, moduleB) {
console.log(moduleB);
});
複製代碼

CMD 通用模塊定義

UMD

同時被 CommonJs規範和AMD規範加載的UMD模塊,一套同時適用於node.js和web環境

UMD先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式。 在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。

原生JS模塊化(ES6)

ES6 爲導入(importing)導出(exporting)模塊帶來了不少可能性

與前二者的最大區別在於,ESModule是由JS解釋器實現,然後二者是 在宿主環境中運行時實現。ESModule導入實際上實在語法層面新增了一個語句,而AMD和ComminJs加載模塊其實是調用了 require函數。

image.png

正則表達式

正則表達式能夠從一個基礎字符串中根據必定的匹配模式替換文本中的字符串、驗證表單、提取字符串等等。

最全正則表達式連接

正則表達式主要依賴於元字符。 元字符不表明他們自己的字面意思,他們都有特殊的含義。一些元字符寫在方括號中的時候有一些特殊的意思。如下是一些元字符的介紹:

元字符 描述
. 句號匹配任意單個字符除了換行符。
[ ] 字符種類。匹配方括號內的任意字符。
[^ ] 否認的字符種類。匹配除了方括號裏的任意字符
* 匹配>=0個重複的在*號以前的字符。
+ 匹配>=1個重複的+號前的字符。
? 標記?以前的字符爲可選.
{n,m} 匹配num個大括號之間的字符 (n <= num <= m).
(xyz) 字符集,匹配與 xyz 徹底相等的字符串.
| 或運算符,匹配符號前或後的字符.
\ 轉義字符,用於匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
^ 從開始行開始匹配.
$ 從末端開始匹配.

ES6篇請移步-連接

第一趴:css進階

第二趴:js進階

第三趴:vue框架進階

第四趴:工程化

同窗以爲有幫助的能夠點個贊,以示鼓勵~ 😊

相關文章
相關標籤/搜索