閉包、做用域、原型小知識點等面試小知識點

在JavaScript中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能。致使這一問題的緣由是多方面的。首先,每一個函數都是對象,都會佔用內存;內存中的對象越多,性能就越差。其次,必須事先指定全部事件處理程序而致使的DOM訪問次數,會延遲整個頁面的交互就緒時間。javascript

對「事件處理程序過多」問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。例如,click事件會一直冒泡到document層次。也就是說,咱們能夠爲整個頁面指定一個onclick事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。html

 

事件委託(事件代理)是利用事件的冒泡原理來實現的,何爲事件冒泡呢?就是事件從最深的節點開始,而後逐步向上傳播事件,舉個例子:頁面上有這麼一個節點樹,div>ul>li>a;好比給最裏面的a加一個click點擊事件,那麼這個事件就會一層一層的往外執行,執行順序a>li>ul>div,有這樣一個機制,那麼咱們給最外面的div加點擊事件,那麼裏面的ul,li,a作點擊事件的時候,都會冒泡到最外層的div上,因此都會觸發,這就是事件委託委託它們父級代爲執行事件前端

當用事件委託的時候,根本就不須要去遍歷元素的子節點,只須要給父級元素添加事件就行了,其餘的都是在js裏面的執行,這樣能夠大大的減小dom操做,這纔是事件委託的精髓所在。vue

 

遞歸調用

本身調用本身,稱爲遞歸調用java

注意:下面這段代碼運行會報錯:Maximum call stack size exceedednode

錯誤直譯過來就是「棧溢出」,出現這個錯誤的緣由是由於我進行了遞歸運算,可是忘記添加判斷條件,致使遞歸無限循環下去jquery

function fun()
{
    // 本身調用本身,稱爲遞歸調用
    fun();
    console.log("m2");
}
fun();

解決辦法以下:es6

(function a(x) {
    // The following condition 
    // is the base case.
    if ( ! x) {
        return;
    }
    a(--x);
})(10);

 

事件委託

http://www.javashuo.com/article/p-tsnncwfy-ec.htmlweb

每一個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,天然性能就越差了(內存不夠用,是硬傷,哈哈),好比上面的100個li,就要佔用100個內存空間,若是是1000個,10000個呢,那隻能說呵呵了,若是用事件委託,那麼咱們就能夠只對它的父級(若是隻有一個父級)這一個對象進行操做,這樣咱們就須要一個內存空間就夠了,是否是省了不少,天然性能就會更好。面試

Event對象提供了一個屬性叫target,能夠返回事件的目標節點,咱們成爲事件源,也就是說,target就能夠表示爲當前的事件操做的dom,可是不是真正操做dom,固然,這個是有兼容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這裏咱們用nodeName來獲取具體是什麼標籤名,這個返回的是一個大寫的,咱們須要轉成小寫再作比較(習慣問題):

     window.onload = function(){
              var oUl = document.getElementById("ul1");   oUl.onclick = function(ev){     var ev = ev || window.event;    var target = ev.target || ev.srcElement;     if(target.nodeName.toLowerCase() == 'li'){          alert(123);         alert(target.innerHTML);     }   } }

http://www.javashuo.com/article/p-tsnncwfy-ec.html

 

IE使用的是事件冒泡,其餘瀏覽器是事件捕獲 

IE提出的是冒泡流,而網景提出的是捕獲流,後來在W3C組織的統一之下,JS支持了冒泡流和捕獲流,可是目前低版本的IE瀏覽器仍是隻能支持冒泡流(IE6,IE7,IE8均只支持冒泡流),因此爲了可以兼容更多的瀏覽器,建議你們使用冒泡流。

 

由此能夠知道
  一、一個完整的JS事件流是從window開始,最後回到window的一個過程
  二、事件流被分爲三個階段(1~5)捕獲過程、(5~6)目標過程、(6~10)冒泡過程

 

閉包就是可以讀取其餘函數內部變量的函數。

閉包就是一個函數引用另一個函數的變量,由於變量被引用着因此不會被回收,所以能夠用來封裝一個私有變量。這是優勢也是缺點,沒必要要的閉包只會徒增內存消耗!

因爲在javascript中,只有函數內部的子函數才能讀取局部變量,因此說,閉包能夠簡單理解成「定義在一個函數內部的函數「。

因此,在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑

閉包的使用:函數做爲返回值,函數做爲參數傳遞

// 函數做爲返回值
function fn() { var max = 10; return function bar(x) {     if (x > max) {       console.log(x);     };   }; }; var abc = fn(); abc(12);

 

原型也是對象叫原型對象。

每一個對象都有一個__proto__屬性,指向建立該對象的函數的prototype

每一個函數function都有一個prototype,即原型。每一個對象都有一個__proto__,可成爲隱式原型。

做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突

http://www.javashuo.com/article/p-xddmkdtm-n.html

http://www.cnblogs.com/wangfupeng1988/p/3977924.html    最全的理解JavaScript原型閉包等概念,一共16篇文章

 

一切(引用類型)都是對象,對象是屬性的集合

 

對象都是經過函數來建立的

 

每一個函數都有一個屬性叫作prototype,這個prototype的屬性值是一個對象(屬性的集合,再次強調!),默認的只有一個叫作constructor的屬性,指向這個函數自己。每一個對象都有一個__proto__,可成爲隱式原型,指向建立該對象的函數的prototype

 

對象的__proto__指向的是建立它的函數的prototype,就會出現:Object.__proto__ === Function.prototype

 

instanceof表示的就是一種繼承關係,或者原型鏈的結構

 

訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,再沿着__proto__這條鏈向上找,這就是原型鏈

 

那麼咱們在實際應用中如何區分一個屬性究竟是基本的仍是從原型中找到的呢?你們可能都知道答案了——hasOwnProperty

 

全部的對象的原型鏈都會找到Object.prototype,所以全部的對象都會有Object.prototype的方法。這就是所謂的「繼承」

 

在一段js代碼拿過來真正一句一句運行以前,瀏覽器已經作了一些「準備工做」,其中就包括對變量的聲明,而不是賦值。變量賦值是在賦值語句執行的時候進行的。

 

在「準備工做」中完成了哪些工做:

  • 變量、函數表達式——變量聲明,默認賦值爲undefined;
  • this——賦值;
  • 函數聲明——賦值;

這三種數據的準備狀況咱們稱之爲「執行上下文」或者「執行上下文環境」。

 

函數每被調用一次,都會產生一個新的執行上下文環境。由於不一樣的調用可能就會有不一樣的參數。

 

函數在定義的時候(不是調用的時候),就已經肯定了函數體內部自由變量的做用域

 

在執行代碼以前,把將要用到的全部的變量都事先拿出來,有的直接賦值了,有的先用undefined佔個空。

 

在函數中this到底取何值,是在函數真正被調用執行的時候肯定的,函數定義的時候肯定不了。由於this的取值是執行上下文環境的一部分,每次調用函數,都會產生一個新的執行上下文環境。

其實,不只僅是構造函數的prototype,即使是在整個原型鏈中,this表明的也都是當前對象的值。

this指向的是函數在運行時的上下文,既不是函數對象自己,也不是函數聲明時所在做用域

 

執行全局代碼時,會產生一個執行上下文環境,每次調用函數都又會產生執行上下文環境。當函數調用完成時,這個上下文環境以及其中的數據都會被消除,再從新回到全局上下文環境。處於活動狀態的執行上下文環境只有一個。

其實這是一個壓棧出棧的過程——執行上下文棧

 

javascript除了全局做用域以外,只有函數能夠建立的做用域。

 

因此,咱們在聲明變量時,全局代碼要在代碼前端聲明,函數中要在函數體一開始就聲明好。除了這兩個地方,其餘地方都不要出現變量聲明。並且建議用「單var」形式。

 

做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突

 

做用域在函數定義時就已經肯定了。而不是在函數調用時肯定。

 

做用域只是一個「地盤」,一個抽象的概念,其中沒有變量。要經過做用域對應的執行上下文環境來獲取變量的值。

 

同一個做用域下,不一樣的調用會產生不一樣的執行上下文環境,繼而產生不一樣的變量的值。因此,做用域中變量的值是在執行過程當中產生的肯定的,而做用域倒是在函數建立時就肯定了。

 

若是要查找一個做用域下某個變量的值,就須要找到這個做用域對應的執行上下文環境,再在其中尋找變量的值

 

在A做用域中使用的變量x,卻沒有在A做用域中聲明(即在其餘做用域中聲明的),對於A做用域來講,x就是一個自由變量。

 

自由變量的取值:要到建立這個函數的那個做用域中取值——是「建立」,而不是「調用」,切記切記——其實這就是所謂的「靜態做用域」。

 

閉包應用的兩種狀況便可——函數做爲返回值,函數做爲參數傳遞。

 

使用閉包會增長內容開銷

 

多態意味着同名方法的實現依據類型有所改變,在JS中只須要在「子類」Student的prototype定義同名方法便可,由於原型鏈是單向的,不會影響上層的原型。

 

instanceof從字面意上來講就是判斷當前對象是不是後面的實例, 實際上其做用是判斷一個函數的原型是否在對象的原型鏈上

 

構造函數自己其實就是普通的函數,只是咱們專門用它來實現了構造的功能,因此專門起了一個名字叫構造函數,任何函數均可以成爲構造函數,這取決於你調用函數的方式,當使用了New的方式調用就成了構造函數。

 

原型:每一個函數都有一個 prototype屬性,它是一個 對象,也稱做原型對象,咱們能夠把方法和屬性寫在它上面(不過原型對象不只僅有咱們寫的屬性和方法,還有別的,下面會介紹),而經過這個函數建立出來的實例對象,都能 共享這個原型對象下的方法和屬性。因此咱們只須要把想要共享的東西放在函數的prototype下,不想共享的東西經過構造函數來建立就能夠了。
 
每一個實例化對象都有_proto_屬性,它是一個指針,指向函數的prototype,也就是保存了它的地址。( JS中任何對象的值都是保存在堆內存中,咱們聲明的變量只是一個指針,保存了這個對象的實際地址,因此有了地址就能找到對象),
因此總得來講,每一個實例化對象都有_proto_屬性,保存了構造函數的原型對象的地址,經過這個屬性就能夠擁有原型對象下的全部屬性和方法, _proto_屬性實際就是實例化對象和原型對象之間的鏈接
 
繼承:在不改變源程序的基礎上進行擴充,原功能得以保存,而且對子程序進行擴展,避免重複代碼編寫
 
原生JS是弱類型語言,沒有多態概念
 
構造函數的方法有一些規範:
1)函數名和實例化構造名相同且大寫,(PS:非強制,但這麼寫有助於區分構造函數和
普通函數);
2)經過構造函數建立對象,必須使用new 運算符。
 
構造函數和普通函數的惟一區別,就是他們調用的方式不一樣。只不過,構造函數也是函數,必須用new 運算符來調用,不然就是普通函數。
 

原型prototype解決了消耗內存問題。固然它也能夠解決this做用域等問題。

咱們常常把屬性(一些在實例化對象時屬性值改變的),定義在構造函數內;把公用的方法添加在原型上面,也就是混合方式構造對象(構造方法+原型方式)

 

咱們把這個有__proto__串起來的直到Object.prototype.__proto__爲null的鏈叫作原型鏈。以下圖:

2dbd5870d84a471896d69f7d1980ae63

 

繼承是面向對象中一個比較核心的概念。其餘正統面嚮對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承。而ECMAScript 只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成。

在JavaScript 裏,被繼承的函數稱爲超類型(父類,基類也行,其餘語言叫法),繼承的函數稱爲子類型(子類,派生類)

 

 

遞歸調用

函數的遞歸就是在函數中調用自身。使用遞歸函數必定要注意,處理不當就會進入死循環。遞歸函數只有在特定的狀況下使用 ,好比階乘問題

 

各類循環寫法

       var arr = [2,4,6,8,10,12,14,16,18,20];
for (var i = 0; i < arr.length; i++) { console.log(arr[i]); } for(var i in arr){ console.log(arr[i]); } arr.forEach(function (val,index) { console.log(val); });
       // jquery寫法 $.each(arr,
function(index,val) { console.log(val); });

 

數組去重

       Array.prototype.unique = function() {
                var result = [];
                this.forEach(function(v) {
                    if(result.indexOf(v) < 0) {
                        result.push(v);
                    }
                });
                return result;
            }

 

清除浮動

/* 1.添加新元素 */
<div class="outer">
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
  <div class="clearfix"></div>
</div>
.clearfix {
  clear: both;
}
/* 2.爲父元素增長樣式 */
.clearfix {
  overflow: auto;
  zoom: 1; // 處理兼容性
}
/* 3.:after 僞元素方法 (做用於父元素) */
.outer {
  zoom: 1;
  &:after {
    display: block;
    height: 0;
    clear: both;
    content: '.';
    visibillity: hidden;
  }
}

 

面試題

http://www.javashuo.com/article/p-tqzaceew-bx.html

 

漸進加強(Progressive Enhancement):一開始就針對低版本瀏覽器進行構建頁面,完成基本的功能,而後再針對高級瀏覽器進行效果、交互、追加功能達到更好的體驗。

優雅降級(Graceful Degradation):一開始就構建站點的完整功能,而後針對瀏覽器測試和修復。好比一開始使用 CSS3 的特性構建了一個應用,而後逐步針對各大瀏覽器進行 hack 使其能夠在低版本瀏覽器上正常瀏覽。

 

MVC

http://www.javashuo.com/article/p-tbszcdib-a.html

咱們讓每個層次去關注並作好一件事情,層與層之間保持鬆耦合,咱們能夠對每個層次單獨作好測試工做。如此,咱們可讓代碼更具可維護性。

使用MVC的設計思想,編寫出高維護性的前端程序,主要目的是分離視圖和模型

MVC是一種設計模式,它將應用劃分爲3個部分:數據(模型)、展示層(視圖)和用戶交互(控制器)。換句話說,一個事件的發生是這樣的過程:
  1. 用戶和應用產生交互。
  2. 控制器的事件處理器被觸發。
  3. 控制器從模型中請求數據,並將其交給視圖。
  4. 視圖將數據呈現給用戶。
咱們不用類庫或框架就能夠實現這種MVC架構模式。關鍵是要將MVC的每部分按照職責進行劃分,將代碼清晰地分割爲若干部分,並保持良好的解耦。這樣能夠對每一個部分進行獨立開發、測試和維護。

 

Vue

http://www.javashuo.com/article/p-brudryzf-hq.html   剖析vue原理

http://www.javashuo.com/article/p-ttwlcyrf-be.html      vue雙向綁定原理

實現數據綁定的作法有大體以下幾種:

發佈者-訂閱者模式(backbone.js)

髒值檢查(angular.js) 

數據劫持(vue.js)

數據劫持: vue.js 則是採用數據劫持結合發佈者-訂閱者模式的方式,經過Object.defineProperty()來劫持各個屬性的settergetter,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調。

 

Object.defineProperty

http://www.javashuo.com/article/p-wkgxxnxj-es.html

 

ES6

https://blog.csdn.net/qq_35480270/article/details/53978449

Babel是一個普遍使用的ES6轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。

ES5只有全局做用域和函數做用域,沒有塊級做用域,這帶來不少不合理的場景。第一種場景就是你如今看到的內層變量覆蓋外層變量。而let則實際上爲JavaScript新增了塊級做用域。用它所聲明的變量,只在let命令所在的代碼塊內有效。

另一個var帶來的不合理場景就是用來計數的循環變量泄露爲全局變量

const有一個很好的應用場景,就是當咱們引用第三方庫的時聲明的變量,用const來聲明能夠避免將來不當心重命名而致使出現bug

 

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        console.log(this.type + ' says ' + say)
    }
}
 
let animal = new Animal()
animal.says('hello') //animal says hello
 
class Cat extends Animal {
    constructor(){
        super()
        this.type = 'cat'
    }
}
 
let cat = new Cat()
cat.says('hello') //cat says hello

上面代碼首先用class定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。簡單地說,constructor內定義的方法和屬性是實例對象本身的,而constructor外定義的方法和屬性則是全部實力對象能夠共享的。

Class之間能夠經過extends關鍵字實現繼承,這比ES5的經過修改原型鏈實現繼承,要清晰和方便不少。上面定義了一個Cat類,該類經過extends關鍵字,繼承了Animal類的全部屬性和方法。

super關鍵字,它指代父類的實例(即父類的this對象)。子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。

ES6的繼承機制,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。

 

箭頭函數:

它簡化了函數的書寫。操做符左邊爲輸入的參數,而右邊則是進行的操做以及返回的值Inputs=>outputs。

  • 不須要 function 關鍵字來建立函數
  • 省略 return 關鍵字
  • 繼承當前上下文的 this 關鍵字
class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        setTimeout( () => {
            console.log(this.type + ' says ' + say)
        }, 1000)
    }
}
 var animal = new Animal()
 animal.says('hi')  //animal says hi

當咱們使用箭頭函數時,函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,它的this是繼承外面的,所以內部的this就是外層代碼塊的this。

 

解構賦值:

數組和對象是JS中最經常使用也是最重要表示形式。爲了簡化提取信息,ES6新增瞭解構,這是將一個數據結構分解爲更小的部分的過程

// 數組的解構賦值
let [name,age,sex] = ['牛牛',24,'女'] console.log(name + '--' + age + '---' + sex)

// 多層數組解構賦值
let [arr1,arr2,[arr3,arr4,[arr5,arr6]]] = [1,2,[3,4,[5,6]]];
console.log(arr1)
// 對象的解構賦值,注意名稱必須和對象key一致
let {name,sex,age} = {name : '東東', sex : '男', age : 23}
console.log(name + '--' + age + '---' + sex)

//基本類型的解構賦值
let [a,b,c,d,e] = '我是中國人'
console.log(a)

 

展開運算符

ES6中另一個好玩的特性就是Spread Operator 也是三個點兒...接下來就展現一下它的用途。

組裝對象或者數組

   //數組
    const color = ['red', 'yellow']
    const colorful = [...color, 'green', 'pink']
    console.log(colorful) //[red, yellow, green, pink]
    
    //對象
    const alp = { fist: 'a', second: 'b'}
    const alphabets = { ...alp, third: 'c' }
    console.log(alphabets) //{ "fist": "a", "second": "b", "third": "c" }

 set、map數據集合

set集合和數組的區別就是數組能夠有重複數據,可是set集合是沒有重複數據的。

獲取集合長度size屬性

       let set = new Set(['張三','李四','王五']);
            
            // 集合長度
            console.log(set.size);
            
            // 添加
            set.add('牛雨晴');
            console.log(set);
            
            set.delete('張三');
            console.log(set);
            
            // has
            console.log(set.has('王五'))
            
            // 清空集合
            set.clear();
            console.log(set);

 

map也是沒有重複數據的

map的話存放的是對象

       let obj1 = {'a':'1','b':'2'}
            let map = new Map([
                ['name','張三'],
                ['age',20],
                ['sex','男'],
                [obj1,'今每天氣很好'],
                [[1,2,3],'適合敲代碼']
            ]);
            console.log(map);
            console.log(map.size);
            
            // set方法
            map.set('friends','小花花')
            console.log(map)
            
            // get方法
            console.log(map.get('name'));
            
            // delete方法
            map.delete('name');
            console.log(map);
            
            // keys方法
            console.log(map.keys());
            
            // values方法
            console.log(map.values());
            
            // entries方法,獲得鍵值對
            console.log(map.entries());
            
            // 對map進行遍歷
            map.forEach(function (value,index) {
                console.log(index + ':' + value)
            })

 

symbol解決命名衝突問題

 

基於原型,經過構造函數實現面向對象

es6經過class實現面向對象

es6的class其實就是一個語法糖,底層仍是基於原型和構造函數實現面向對象

 

模板字符串 ` ` 包裹

       // 模板字符串 ``包裹
            // 內容使用${}
            let str = 'hello world';
            let className = 'text';
            let html = `
                        <html>
                            <head></head>
                            <body>
                                <p class='${className}'>${str}</p>
                            </body>
                        </html>
                        
            `;
            console.log(html);

 

新增字符串方法

1.includes,是否包含某個字符串

2.startsWith,是不是以某個字母開頭

console.log('hello'.includes('o'));

console.log('hello'.startsWith('o'));

 

異步執行能夠用回調函數來實現。

 

Promise

https://www.jianshu.com/p/c98eb98bd00c

Promises是處理異步操做的一種模式,以前在不少三方庫中有實現,好比jQuery的deferred 對象。當你發起一個異步請求,並綁定了.when(), .done()等事件處理程序時,其實就是在應用promise模式。

Promise是異步編程的一種解決方案,它有三種狀態,分別是pending-進行中、resolved-已完成、rejected-已失敗,相比傳統回調函數更合理

所謂Promise ,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise是一個對象,從它能夠獲取異步操做的消息。 
Promise 對象的狀態不受外界影響

古人云:「君子一言既出;駟馬難追」,這種「承諾未來會執行」的對象在JavaScript中稱爲Promise對象。

 

postMessage(iframe間的跨域通訊)

http://www.javashuo.com/article/p-rvabgadb-bs.html

http://www.webhek.com/post/postmessage-cross-domain-post.html

HTML5給咱們帶來了安全的跨域通訊接口,即window.postMessage()方法。

它方法原型是:window.postMessage(msg, domain),咱們能夠給指定的domain發送msg。而接收msg的iframe只要註冊一個監聽事件就能夠了。

相關文章
相關標籤/搜索