深入理解JS中的重點問題

1.模塊化如何實現的,請用閉包的方式實現一個簡單的module

模塊化可以幫助我們抽離公共的代碼,隔離作用域,避免命名衝突的問題

先看下面這個簡單的代碼,封裝一個函數,並立即執行,函數返回兩個對象字面量,我們可以在my變量訪問到它們

let my = (function myModule() {
  let myName = 'default';
  function setName(name) {
    myName = name;
  }
  function getName() {
    console.log(myName);
  }
  return {
    setName,getName
  }
})()
my.setName('renye')
my.getName()

每次實例化一個myModule都是一個新的實例,它們之間不共享屬性,閉包幫我們完成了局部變量myName在js內存中的駐留,不會被垃圾回收(js的垃圾回收用的是標記清除:變量進入作用域就標記,出作用域就清楚標記,每隔一段時間就執行一下jc)

 

2.this的4種綁定方式:

this有4種綁定方式:

默認綁定:我們經常使用的獨立調用一個函數,這種時候,函數內部的this就是使用默認綁定,綁定到windows,嚴格模式下和node環境下都是undefined

隱式綁定:這也是最常見的綁定方式,當函數的調用位置有上下文對象的時候,this會隱式綁定到這個對象上,但是這種綁定方式可能會出現綁定丟失的情況:最常見的情況是發生在傳入回調的時候,想一下我們在React的組件上綁定事件的時候,如果我們用的是函數聲明,而不是箭頭函數形式的函數表達式,那麼我們綁定在JSX語法寫的元素上的事件就會出現綁定this丟失的情況,因此我們在React組建的構造函數中,使用bind函數爲函數綁定上了組價的this,保證事件發生時候調用的回調函數可以綁定到我們的組件上。setTimeout也有類似的回調問題

顯示綁定:使用call,apply,同時bind函數可以解決綁定丟失的問題

new綁定:我們實現過new函數,被new標識符聲明的函數的調用中,函數的this指向其等號左側的對象

 

3.重繪(repaint)和重排(迴流/reflow)的觸發條件,vDOM如何避免重繪和重排,16.6ms這個數意味着什麼,vDOM的效率一定比DOM更高嗎?

要討論重繪和重排,我們首先應該瞭解一下瀏覽器渲染頁面的機制:瀏覽器採用Flow based Layout,也就是流式佈局,這個流就是我們常說的文檔流。瀏覽器會先解析HTML文件,生成對應的DOM樹,然後解析CSS文件,生成CSSOM樹,然後對這兩個樹進行合併,就生成了渲染樹,在這顆渲染樹上 ,我們知道DOM的結構和對應的樣式,然後瀏覽器計算DOM在牙面上的大小和位置,把DOM節點渲染到頁面上去。

重繪:由於DOM的color background-color等屬性發生變換,不影響DOM的佈局的時候,瀏覽器觸發重繪,將改變你的屬性重新渲染到頁面上

重排:由於DOM的尺寸(width,height),佈局(display postion float)發生改變的時候,會引起頁面的重排,重排大部分時候會導致頁面的重新渲染,因此我們需要優化,重排一定會重繪,重繪不一定重排

優化:

1.現代瀏覽器使用的一般是隊列的機制來實現批量的更新,我們知道瀏覽器爲了維持60HZ的刷新幀率,每隔至少一個16.6ms的時候可以渲染一次,但是當我們使用佈局的信息,比如window對象的offsetTop,scrollTop clientTop 的時候,瀏覽器爲了保證你可以拿到正確值,會強制去清空這個隊列,保證你拿到正確的值,因此我們不能頻繁訪問這個屬性

2.CSS優化:使用visibility:hidden代替display:none,前者只引發重繪,後者會重排,不要用css表達式,動畫效果一定要用在positon爲absolute和fixed上,這樣動畫就是重繪,要不會可能重排到懷疑人生

3.HTML優化:儘量不用table佈局,table大概要花其他元素3倍以上的事件去重排,避免無意義標籤,讓HTML結構儘量扁平,在觸發重排的時候,可以儘量影響少一些的DOM

4.vDOM:React使用了vDOM,幫助我們批量更新,vDOM的效率只有在涉及到很多DOM元素的變更的時候,效率纔可能更高,簡單的操作DOM,肯定是直接操作DOM的效率更高,因爲vDOM最終還是要對應到真正的DOM操作,React也只是對瀏覽器document的API的一些封裝。我們會一次性計算更多需要變更的節點,計算出變更後的結果,然後轉化成真實的DOM去渲染,避免直接操作真實DOM次數過多引起過多的重繪和重排

4.常見的js設計模式:發佈訂閱,觀察者模式,工廠模式,裝飾者模式,單例模式,很多很多,選幾個實際用的到的例子簡單介紹一下

單例模式:彈窗組件,比如點擊button彈出一個包含個人信息的彈窗,我們自然不能反覆的創建不同的彈窗,而是每次彈出同一個,避免佔用過多的內存,利用了閉包!這個例子特別好,重點理解

 

class CreateUser {
    constructor(name) {
        this.name = name;
        this.getName();
    }
    getName() {
         return this.name;
    }
}
// 代理實現單例模式
var ProxyMode = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new CreateUser(name);
        }
        return instance;
    }
})();
// 測試單體模式的實例
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
// 因爲單體模式是隻實例化一次,所以下面的實例是相等的
console.log(a === b);    //true

代理模式:圖片的懶加載:

var imgFunc = (function() {
  let imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  return {
    setSrc:function(src) {
      imgNode.src = src
    }
  }
})
var loadingImg = (function(){
  let img = new Image();
  img.onload = function() {
    imgFunc.setSrc(this.src)
  }
  return {
    setSrc: function(src) {
      imgFunc.setSrc('./loading.gif');
      img.src =src;
    }
  }
})()

loadingImg.setSrc('./pic.png')

中介者模式:由一箇中介類統一管理狀態,這可不就是我們的redux嘛~

發佈訂閱模式:可以說這是我們最熟悉的設計模式了,我們用的DOM的addEventListener就是這麼一種模式,這裏我們介紹一個Node環境下的Event訂閱模塊,我們實現一個簡單的自己的EventEmiiter

5 class是對ES5的原型繼承的語法糖,.ES6引入了的class和我們ES5前寫的class有什麼區別呢

首先class聲明沒有變量提升,class中的方法是不能使用new的,class是原型繼承(Object.create(),淺拷貝)的語法糖

6.js中異步的發展,從回調到Promise到generator到async/await 它們的提出分別是爲了什麼,請實現一個Promise

js是單線程執行,異步對於js來講是必不可少的。一開始我們使用的是回調函數的方式去實現異步,但是問題在於如果下一個回調函數需要的參數依賴於上一個回調函數返回的結果,在書寫代碼的時候,就會出現一層層的嵌套不利於閱讀和書寫,ES6正式提出了Promise,通過resolve和reject對應異步操作成功和失敗時候的返回值,在then方法(第一個回調參數處理resolve,第二個處理reject)和catch處理錯誤信息,藉由then方法的鏈式調用,我們可以寫出可讀性很強的代碼。並且Promise還爲我們提供了all race finally方法來增強我們處理多個Promise對象的方法。

generator函數:generator借鑑了協程的概念,當協程A執行到中途的時候,把程序的執行權轉移給協程B,協程B執行一段時間後,再把執行權交還給A,generator函數用function* gen(){}聲明,在需要暫停異步去執行的表達式和函數前,添加yield關鍵字,yield後面的代碼會等待被yield聲明的函數或表達式執行完返回結果後再執行

async/await:是對generator的封裝,用async聲明函數 async function asFun(){},在需要異步返回結果的的函數前添加await

7.JavaScript 中創建對象的方式有哪些,實現和區別?new和Object create

function myNew(P) {
  let o = {};
  o.__proto__ = P.prototype;
  P.prototype.constructor = P;
  P.apply(o,[].prototype.slice.call(arguments,1))
  return o;
}

//工廠方式,相當於實現了一個對象的淺拷貝
Object.prototype.myCreate = function(o) {
  let F = function() {}
  F.prototype = o;
  let newO = new F()
  return newO//新new出來的對象和傳進來的o具有一樣的父對象,o = F.prototype = newO.__proto__ 也就是newO.__proto__ = o
}

8.深入理解Symbol對象

//symbol的應用場景有哪些
let mySymbol1 = Symbol('renye');
let mySymbol2 = Symbol('renye');
console.log(mySymbol1 == mySymbol2);//false

let mySymbol3 = Symbol.for('renye');
let mySymbol4 = Symbol.for('renye');
console.log(mySymbol3 == mySymbol4);//true


var obj = {
  name:'ConardLi',
  [Symbol('name2')]:'renye'
}

for(let i in obj) {
  console.log(i)//name Symbol對象作爲對象屬性的時候不可枚舉
}
//創建私有屬性,利用閉包創建Symbol,利用
let Person = (function() {
  let _age = Symbol('age');
  function P(name,age) {
    this.name = name;
    this[_age] = age
  }
  P.prototype.say = function() {
    console.log (this[_age]);
  }
  return P
})()

let p1 = new Person('renye',18);
console.log(p1)//P { name: 'renye', [Symbol(age)]: 18 }

//防止XSS攻擊
var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element'))|| 0xeac7
REACT_ELEMENT_TYPE.isValidElement = function(object) {
  return typeof object === 'object' && object !== null && object.$$typeof === React_ELEMENT_TYPE
}

9.實現一個漂亮的深拷貝

10 隱式類型轉換的問題

Valueof和toString :https://www.cnblogs.com/imwtr/p/4392041.html

使用 == 進行比較比較的時候,有幾個重點

1.Boolean值會轉成數字

2.NaN == 任何值都是false包括NaN

3.Stringh和Number比,String轉成Number

 

一道經典的面試題,如何讓:a == 1 && a == 2 && a == 3

//重寫a對象的valueOf方法
const a = {
  num:0,
  valueOf:function() {
    return ++this.num
  }
}
let flag = a==1
flag = a==2;
flag = a==3;
console.log(flag)

11 類型檢測,Object.prototype.toString.call() typeof instance of

Object.prototype.toString.call()都適用

typeof 只能檢測基本類型 對於複雜類型和null都返回‘object’

p instance of P 會順着對象的原型鏈去看P是否在p對象的原型鏈上

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章