JavaScript基礎知識彙總

1、原型/原型鏈/構造函數/實例/繼承

  js萬物皆對象,用  var a={} 或   var a = new Object() 或者用構造函數的形式:  var a = new A() 建立一個對象時,該對象不只能夠訪問它自身的屬性,還會根據  __proto__ 屬性找到它原型鏈上的屬性,直到找到  Object 上面的  null 。

  每一個函數都有 prototype  屬性,除了  Function.prototype.bind() ,該屬性指向原型。javascript

  每一個對象都有 __proto__  屬性,指向了建立該對象的構造函數的原型。其實這個屬性指向了  [[prototype]] ,可是  [[prototype]]  是內部屬性,咱們並不能訪問到,因此使用  __proto__  來訪問。php

  對象能夠經過 __proto__  來尋找不屬於該對象的屬性, __proto__  將對象鏈接起來組成了原型鏈。css

  實例指的就是實例對象,而實例對象能夠經過構造函數建立。實例對象自己就有着__proto__屬性,實例對象的__proto__屬性指向原型對象。html

  構造函數與通常函數的區別在於構造函數是用於建立實例對象來使用的,因此構造函數通常都是帶有new運算符的函數。構造函數有着全部函數都有的屬性:prototype。構造函數的prototype屬性指向原型對象。前端

  原型對象是由構造函數的prototype屬性和這個構造函數建立的實例對象的__proto__屬性共同指向的一個原型鏈上的對象。若是要判斷一個構造函數與實例對象是否有着共同指向的原型對象,能夠使用instanceof 來判斷,具體用法是 實例對象 instanceof 構造函數。好比引用上面構造函數建立實例對象的例子:obj2 instanceof people,結果返回true。java

  原型對象顧名思義它也是一個對象,因此它也有對象的__proto__屬性,那原型對象的__proto__屬性也一樣地會指向它上一層的原型對象,順着下去,原型對象的原型對象可能還有它的上一層原型對象,這樣一直到Object.prototype這個原型對象爲止,這就是整個原型鏈。node

  instanceof不只僅判斷實例對象與構造函數是否有着一樣指向,實際上,但凡在這個實例對象的原型鏈上的構造函數與對象之間,使用instanceof來判斷都會返回true,因此若是要找到實例對象是直接由哪一個構造函數建立的,使用instanceof不可行,這能夠使用constructor來替代。好比 obj2 constructor people 就是返回true。android

建立對象的幾種方法:

1. 工廠模式

function createPerson(name, age, job) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        console.log(this.name);
    }
    return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)

2. 構造函數模式

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name);
    }
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");

使用構造函數的主要問題,就是每一個方法都要在每一個實例上從新建立一遍 (person1.sayName !== person2.sayName)面試

3. 原型模式

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
person1.sayName();   //"Nicholas"

var person2 = new Person();
person2.sayName();   //"Nicholas"

alert(person1.sayName == person2.sayName);  //true

咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法,可是原型中全部屬性是被實例共享的, 引用類型的屬性會出問題。ajax

3. 組合使用構造函數模式和原型模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        console.log(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
console.log(person1.friends);    //"Shelby,Count,Van"
console.log(person2.friends);    //"Shelby,Count"
console.log(person1.friends === person2.friends);    //false
console.log(person1.sayName === person2.sayName);    //true

建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存。在這個例子中,實例屬性都是在構造函數中定義的,而由全部實例共享的屬性constructor和方法sayName()則是在原型中定義的。而修改了person1.friends(向其中添加一個新字符串),並不會影響到person2.friends,由於它們分別引用了不一樣的數組。 這種構造函數與原型混成的模式,是目前在ECMAScript中使用最普遍、認同度最高的一種建立自定義類型的方法。能夠說,這是用來定義引用類型的一種默認模式。

4. 動態原型模式

function Person(name, age, job){

    //屬性
    this.name = name;
    this.age = age;
    this.job = job;
    if (typeof this.sayName != "function"){
        console.log(1);
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer"); //1
var person2 = new Person("Greg", 27, "Doctor");

person1.sayName();
person2.sayName();

把全部信息都封裝在了構造函數中,而經過在構造函數中初始化原型(僅在必要的狀況下),又保持了同時使用構造函數和原型的優勢。換句話說,能夠經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。這裏只在sayName()方法不存在的狀況下,纔會將它添加到原型中。這段代碼只會在初次調用構造函數時纔會執行。此後,原型已經完成初始化,不須要再作什麼修改了。不過要記住,這裏對原型所作的修改,可以當即在全部實例中獲得反映。所以,這種方法確實能夠說很是完美其中,if語句檢查的能夠是初始化以後應該存在的任何屬性或方法——沒必要用一大堆if語句檢查每一個屬性和每一個方法;只要檢查其中一個便可。對於採用這種模式建立的對象,還能夠使用instanceof操做符肯定它的類型。

本部分參考博主連接:http://www.javashuo.com/article/p-wvzbwgee-t.html

2、有幾種方式能夠實現繼承

1. 原型鏈繼承

// 定義一個動物類
function Animal (name) {
  // 屬性
  this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; --原型鏈繼承 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:基於原型鏈,既是父類的實例,也是子類的實例
  • 缺點:沒法實現多繼承

2. 構造函數繼承

function Cat(name){
  Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:能夠實現多繼承
  • 缺點:只能繼承父類實例的屬性和方法,不能繼承原型上的屬性和方法。

3. 組合繼承(1和2的組合)

function Cat(name){
  Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat.name);
  • 特色:能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法
  • 缺點:調用了兩次父類構造函數,生成了兩份實例

4. 寄生組合繼承

組合繼承最大的問題就是不管什麼狀況下,都會調用兩次父類構造函數: 一次是在建立子類型原型的時候, 另外一次是在子類型構造函數內部. 寄生組合式繼承就是爲了下降調用父類構造函數的開銷而出現的.

其背後的基本思路是: 沒必要爲了指定子類型的原型而調用超類型的構造函數

function extend(subClass, superClass) {
  subClass.prototype = superClass.prototype; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; } } function Father(name){ this.name = name; this.colors = ["red","blue","green"]; } Father.prototype.sayName = function(){ alert(this.name); }; function Son(name,age){ Father.call(this,name);//繼承實例屬性,第一次調用Father() this.age = age; } extend(Son,Father)//繼承父類方法,此處並不會第二次調用Father() Son.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Son("louis",5); instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" instance1.sayName();//louis instance1.sayAge();//5 var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green" instance1.sayName();//zhai instance1.sayAge();//10

補充:new 操做符

爲了追本溯源, 我順便研究了new運算符具體幹了什麼?發現其實很簡單,就幹了三件事情.

var obj  = {};
obj.__proto__ = F.prototype; F.call(obj);

第一行,咱們建立了一個空對象obj;

第二行,咱們將這個空對象的__proto__成員指向了F函數對象prototype成員對象;

第三行,咱們將F函數對象的this指針替換成obj,而後再調用F函數.

咱們能夠這麼理解: 以 new 操做符調用構造函數的時候,函數內部實際上發生如下變化:

一、建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。

二、屬性和方法被加入到 this 引用的對象中。

三、新建立的對象由 this 所引用,而且最後隱式的返回 this.

本身實現 new 操做符

  • new 操做符會返回一個對象,因此咱們須要在內部建立一個對象
  • 這個對象,也就是構造函數中的 this,能夠訪問到掛載在 this 上的任意屬性
  • 這個對象能夠訪問到構造函數原型上的屬性,因此須要將對象與構造函數連接起來
  • 返回原始值須要忽略,返回對象須要正常處理
function create(Con, ...args) {
 let obj = {}
 Object.setPrototypeOf(obj, Con.prototype)
 let result = Con.apply(obj, args)
 return result instanceof Object ? result : obj
}

本部分參考博主連接:http://www.javashuo.com/article/p-zugxclij-h.html

3、DOM

文檔對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展標誌語言的標準編程接口。在網頁上,組織頁面(或文檔)的對象被組織在一個樹形結構中,用來表示文檔中對象的標準模型就稱爲DOM

1. DOM操做

createDocumentFragment()    //建立一個DOM片斷
createElement()   //建立一個具體的元素
createTextNode()   //建立一個文本節點
  • 添加:appendChild()

  • 移出:removeChild()

  • 替換:replaceChild()

  • 插入:insertBefore()

  • 複製:cloneNode(true)

節點變化觸發的事件

  • DOMSubtreeModified:在DOM結構中發生任何變化時觸發; 
  • DOMNodeInserted:在一個節點做爲子節點被插入到另外一個節點中時觸發; 
  • DOMNodeRemoved:在節點從其父節點中被移除時觸發; 
  • DOMNodeInsertedIntoDocument:在一個節點被直接插入文檔中或者經過子樹間接插入文檔後觸發。在DOMNodeInserted以後觸發; 
  • DOMNodeRemovedFromDocument:在一個節點被直接從文檔中刪除或經過子樹間接從文檔中移除以前觸發。在DOMNodeRemoved以後觸發。 
  • DOMAttrModified:在特性被修改以後觸發; 
  • DOMCharacterDataModified:在文本節點的值發生變化的時候觸發。 
//查找
getElementsByTagName()    //經過標籤名稱
getElementsByClassName()    //經過標籤名稱
getElementsByName()    //經過元素的Name屬性的值
getElementById()    //經過元素Id,惟一性

子節點

  • Node.childNodes //獲取子節點列表NodeList; 注意換行在瀏覽器中被算做了text節點,若是用這種方式獲取節點列表,須要進行過濾
  • Node.firstChild //返回第一個子節點
  • Node.lastChild //返回最後一個子節點

父節點

  • Node.parentNode // 返回父節點
  • Node.ownerDocument //返回祖先節點(整個document)

同胞節點

  • Node.previousSibling // 返回前一個節點,若是沒有則返回null
  • Node.nextSibling // 返回後一個節點

2. DOM事件

DOM事件模型分爲捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分紅三個階段。

  • 捕獲階段:事件從window對象自上而下目標節點傳播的階段;
  • 目標階段:真正的目標節點正在處理事件的階段;
  • 冒泡階段:事件從目標節點自下而上window對象傳播的階段。

2.1 事件捕獲

捕獲是從上到下,事件先從window對象,而後再到document(對象),而後是html標籤(經過document.documentElement獲取html標籤),而後是body標籤(經過document.body獲取body標籤),而後按照普通的html結構一層一層往下傳,最後到達目標元素。咱們只須要將addEventListener第三個參數改成true就能夠實現事件捕獲。

//摘自xyyojl的《深刻理解DOM事件機制》
<!-- CSS 代碼 -->
<style>
    body{margin: 0;}
    div{border: 1px solid #000;}
    #grandfather1{width: 200px;height: 200px;}
    #parent1{width: 100px;height: 100px;margin: 0 auto;}
    #child1{width: 50px;height: 50px;margin: 0 auto;}
</style>

<!-- HTML 代碼 -->
<div id="grandfather1">
    爺爺
    <div id="parent1">
        父親
        <div id="child1">兒子</div>
    </div>
</div>

<!-- JS 代碼 -->
<script>
    var grandfather1 = document.getElementById('grandfather1'),
        parent1 = document.getElementById('parent1'),
        child1 = document.getElementById('child1');
    
    grandfather1.addEventListener('click',function fn1(){
        console.log('爺爺');
    },true)
    parent1.addEventListener('click',function fn1(){
        console.log('爸爸');
    },true)
    child1.addEventListener('click',function fn1(){
        console.log('兒子');
    },true)

    /*
        當我點擊兒子的時候,觸發順序是爺爺 ——》父親——》兒子
    */
    // 請問fn1 fn2 fn3 的執行順序?
    // fn1 fn2 fn3 or fn3 fn2 fn1  
</script>

2.2 事件冒泡

所謂事件冒泡就是事件像泡泡同樣從最開始生成的地方一層一層往上冒。咱們只須要將addEventListener的第三個參數改成false就能夠實現事件冒泡。

//html、css代碼同上,js代碼只是修改一下而已
var grandfather1 = document.getElementById('grandfather1'),
    parent1 = document.getElementById('parent1'),
    child1 = document.getElementById('child1');

grandfather1.addEventListener('click',function fn1(){
    console.log('爺爺');
},false)
parent1.addEventListener('click',function fn1(){
    console.log('爸爸');
},false)
child1.addEventListener('click',function fn1(){
    console.log('兒子');
},false)

/*
   當點擊兒子的時候,觸發順序:兒子——》爸爸——》爺爺
*/
// 請問fn1 fn2 fn3 的執行順序?
// fn1 fn2 fn3 or fn3 fn2 fn1  

3. 事件代理(事件委託)

因爲事件會在冒泡階段向上傳播到父節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫作事件的代理(delegation)

3.1 優勢

  • 減小內存消耗,提升性能

若是給每一個列表項一一都綁定一個函數,那對於內存消耗是很是大的,效率上須要消耗不少性能。藉助事件代理,咱們只須要給父容器ul綁定方法便可,這樣無論點擊的是哪個後代元素,都會根據冒泡傳播的傳遞機制,把容器的click行爲觸發,而後把對應的方法執行,根據事件源,咱們能夠知道點擊的是誰,從而完成不一樣的事。

  • 動態綁定事件

在不少時候,咱們須要經過用戶操做動態的增刪列表項元素,若是一開始給每一個子元素綁定事件,那麼在列表發生變化時,就須要從新給新增的元素綁定事件,給即將刪去的元素解綁事件,若是用事件代理就會省去不少這樣麻煩。

3.2 跨瀏覽器處理事件程序

標準事件對象:

  • (1)type:事件類型
  • (2)target:事件目標
  • (3)stopPropagation()方法:阻止事件冒泡
  • (4)preventDefault()方法:阻止事件的默認行爲

IE中的事件對象:

  • (1)type:事件類型

  • (2)srcElement:事件目標

  • (3)cancelBubble屬性:阻止事件冒泡 true表示阻止冒泡,false表示不阻止

  • (4)returnValue屬性:阻止事件的默認行爲

本部分參考博客連接: http://www.javashuo.com/article/p-wvzbwgee-t.html

4、arguments

它是JS的一個內置對象,常被人們所忽略,但實際上確很重要,JS不像JAVA是顯示傳遞參數,JS傳的是形參,能夠傳也能夠不傳,若方法裏沒有寫參數卻傳入了參數,那麼就要用arguments來拿到這些參數了。每個函數都有一個arguments對象,它包括了函數所要調的參數,一般咱們把它看成數組使用,用它的length獲得參數數量,但它卻不是數組,若使用push添加數據將報錯。

在函數調用的時候,瀏覽器每次都會傳遞進兩個隱式參數:

1. 函數的上下文對象this

2. 封裝實參的對象arguments

arguments還有屬性callee,length和迭代器Symbol

1. 咱們發現callee的值是函數fun,而且callee指向函數fun

function fun(){
  // console.log(arguments);
  console.log('arguments.callee === fun的值:',arguments.callee === fun);
}
fun('tom',[1,2,3],{name:'Janny'});

2. 第二個屬性length,咱們常常在數組或者類數組中看到,能夠看到arguments的原型索引__proto__的值爲Object,故此咱們推測arguments不是數組,而是一個類數組對象。

把arguments轉換成一個真正的數組: var args = Array.prototype.slice.call(arguments); 

3. 第三個屬性是個Symbol類型的鍵,該類型的值都是獨一無二的,該鍵指向的值是一個values函數,該值是一個生成迭代器的函數。

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
 
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

在arguments中有一樣的做用

function fun(){
  console.log(arguments[Symbol.iterator]);
  let iterator = arguments[Symbol.iterator]();
  console.log('iterator:',iterator);
  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
  console.log(iterator.next());
}
fun('tom',[1,2,3],{name:'Janny'});
本部分參考博主連接: https://blog.csdn.net/zjy_android_blog/article/details/80934042

5、數據類型判斷

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定義)
  • Object

JS中的不可擴展對象、密封對象、凍結對象

JavaScript 中,能夠對對象的權限進行配置,經過配置,可將對象設置爲不可擴展對象、密封對象、凍結對象等,以達到保護對象屬性的目的。

  • 若是一個對象能夠添加新的屬性,則這個對象是可擴展的。Object.preventExtensions()將對象標記爲再也不可擴展,所以它將永遠不會具備超出它被標記爲不可擴展的屬性。注意,通常來講,不可擴展對象的屬性可能仍然可被刪除。嘗試將新屬性添加到不可擴展對象將靜默失敗或拋出TypeError(最多見但不排除其餘狀況,如在strict mode中)。
  • Object.preventExtensions()僅阻止添加自身的屬性。但屬性仍然能夠添加到對象原型。一旦使其不可擴展,就沒法再對象進行擴展。
  • 密封對象不可擴展,並且已有的屬性成員[[configurable]]特性將被設置成false(意味着不能刪除屬性和方法,可是可修改已有屬性值),使用Object.seal()能夠將對象密封
  • 最嚴格的防止篡改級別是凍結對象,凍結的對象既不能夠擴展,又是密封的,並且對象數據屬性的[[writable]]特性會被設置爲false。 若是定義[[Set]]函數,訪問器屬性仍然是可寫的,使用Object.freeze()方法能夠凍結對象

1. null和undefined的差別相同點:

  • 在 if判斷語句中,值都默認爲 false
  • 大致上二者都是表明無,具體看差別

差別:

  • null轉爲數字類型值爲0,而undefined轉爲數字類型爲NaN(Not a Number)
  • undefined是表明調用一個值而該值卻沒有賦值,這時候默認則爲undefined
  • null是一個很特殊的對象,最爲常見的一個用法就是做爲參數傳入(說明該參數不是對象)
  • 設置爲null的變量或者對象會被內存收集器回收

2. == 和 ===區別

==, 兩邊值類型不一樣的時候,要先進行類型轉換,再比較;===,不作類型轉換,類型不一樣的必定不等 。
===規則: 
一、若是類型不一樣,就[不相等] 
二、若是兩個都是數值,而且是同一個值,那麼[相等];(!例外)的是,若是其中至少一個是NaN,那麼[不相等]。(判斷一個值是不是NaN,只能用isNaN()來判斷) 
三、若是兩個都是字符串,每一個位置的字符都同樣,那麼[相等];不然[不相等]。 
四、若是兩個值都是true,或者都是false,那麼[相等]。 
五、若是兩個值都引用同一個對象或函數,那麼[相等];不然[不相等]。 
六、若是兩個值都是null,或者都是undefined,那麼[相等]。 
==規則: 
一、若是兩個值類型相同,進行 === 比較。 
二、若是兩個值類型不一樣,他們可能相等。根據下面規則進行類型轉換再比較: 
   a、若是一個是null、一個是undefined,那麼[相等]。 
   b、若是一個是字符串,一個是數值,把字符串轉換成數值再進行比較。 
   c、若是任一值是 true,把它轉換成 1 再比較;若是任一值是 false,把它轉換成 0 再比較。 
   d、若是一個是對象,另外一個是數值或字符串,把對象轉換成基礎類型的值再比較。對象轉換成基礎類型,利用它的toString或者valueOf方法。js核心內置類,會嘗試valueOf先於toString;例外的是Date,Date利用的是toString轉換。非js核心的對象,令說(比較麻煩,我也不大懂) 
   e、任何其餘組合,都[不相等]。 
null instanceof Object
null === undefined
null == undefined
NaN == NaN
0 == "0"
true == "20"
//答案是: false false true false true false
//加法運算
 console.dir(16+"5"); //156
 console.dir(5+"a");//5a
 console.dir(5+NaN);//NaN
 console.dir(5+null);//5
 console.dir('5'+null);//5null
 console.dir(5+undefined);//NaN
 console.dir(null+undefined);//NaN
 console.dir(5+5);//10
 console.dir("兩個數的和是"+5+5);//兩個數的和是55
 console.dir("兩個數的和是"+(5+5));//兩個數的和是10

補充:隱性轉換規則

首先看雙等號先後有沒有NaN,若是存在NaN,一概返回false。

再看雙等號先後有沒有布爾,有布爾就將布爾轉換爲數字。(false是0,true是1)

接着看雙等號先後有沒有字符串, 有三種狀況:

  • 對方是對象,對象使用toString()或者valueOf()進行轉換;
  • 對方是數字,字符串轉數字;(前面已經舉例)
  • 對方是字符串,直接比較;
  • 其餘返回false

若是是數字,對方是對象,對象取valueOf()或者toString()進行比較, 其餘一概返回false

null, undefined不會進行類型轉換, 但它們倆相等。

var undefined;
undefined == null; // true
1 == true; // true
2 == true; // false
0 == false; // true
0 == ' '; // true
NaN == NaN; // false
[] == false; // true
[] == ![]; // true

// alert(!![]) //true
// alert(![]) //false
// alert([] == 0) //true
// alert(false == 0) //true

如今來探討 [] == ! [] 的結果爲何會是true
①、根據運算符優先級 ,! 的優先級是大於 == 的,因此先會執行 ![]

!可將變量轉換成boolean類型,null、undefined、NaN以及空字符串('')取反都爲true,其他都爲false。

因此 ! [] 運算後的結果就是 false

也就是 [] == ! [] 至關於 [] == false

②、根據上面提到的規則(若是有一個操做數是布爾值,則在比較相等性以前先將其轉換爲數值——false轉換爲0,而true轉換爲1),則須要把 false 轉成 0

也就是 [] == ! [] 至關於 [] == false 至關於 [] == 0

③、根據上面提到的規則(若是一個操做數是對象,另外一個操做數不是,則調用對象的valueOf()方法,用獲得的基本類型值按照前面的規則進行比較,若是對象沒有valueOf()方法,則調用 toString())

而對於空數組,[].toString() ->  '' (返回的是空字符串)

也就是  [] == 0 至關於 '' == 0

④、根據上面提到的規則(若是一個操做數是字符串,另外一個操做數是數值,在比較相等性以前先將字符串轉換爲數值)

Number('') -> 返回的是 0

至關於 0 == 0 天然就返回 true了

總結一下:

[] == ! []   ->   [] == false  ->  [] == 0  ->   '' == 0   ->  0 == 0   ->  true

那麼對於 {} == !{} 也是同理的

關鍵在於  {}.toString() ->  NaN(返回的是NaN)

根據上面的規則(若是有一個操做數是NaN,則相等操做符返回 false)

總結一下:

{} == ! {}   ->   {} == false  ->  {} == 0  ->   NaN == 0    ->  false

關係類型:

 console.dir(16>"5"); //true
 console.dir("16">"5");//false
 console.dir(5<"a");//false
 console.dir(5>=NaN);//false
 console.dir(5<NaN);//false
 console.dir(NaN>=NaN);//false
 console.dir(5>=null);//true
 console.dir(5>=undefined);//false
 console.dir(5>=5);//true
 console.dir(5>=true);//true
 console.dir(5>="true");//false
 console.dir(5>="");//true 
 console.dir("Brick">"alphabet");//false  B的字符串編碼值是66 ,而a的字符串編碼是97.所以false
 console.dir("brick">"alphabet");//true 小寫字母b比a大,因此是true

3. 判斷數據類型

typeof:用來判斷各類數據類型。

typeof 2 //輸出 number 
typeof null //輸出 object 
typeof {} //輸出 object 
typeof [] //輸出 object 
typeof (function(){}) //輸出 function
typeof undefined //輸出 undefined 
typeof '222' //輸出 string 
typeof true //輸出 boolean

instanceof:判斷已知對象類型的方法.instanceof 後面必定要是對象類型,而且大小寫不能錯,該方法適合一些條件選擇或分支。

var c= [1,2,3]; 
var d = new Date(); 
var e = function(){alert(111);}; 
var f = function(){this.name="22";}; 
console.log(c instanceof Array) //true
console.log(d instanceof Date) //true
console.log(e instanceof Function) //true
// console.log(f instanceof function ) //false

instanceof是一個二元運算符,如:A instanceof B. 其中,A必須是一個合法的JavaScript對象,B必須是一個合法的JavaScript函數 (function)。若是函數B在對象A的原型鏈 (prototype chain) 中被發現,那麼instanceof操做符將返回true,不然返回false.

console.log(Array instanceof Function);//true
console.log(Object instanceof Function);//true
function Foo() {
}
var foo = new Foo();
alert(foo instanceof Foo);// true 
alert(foo instanceof Object);// true
alert(foo instanceof Function);// false
alert(Foo instanceof Function);// true
alert(Foo instanceof Object);// true

爲什麼Object instanceof Function和Function instanceof Object都返回true?

Object, Function, Array等等這些都被稱做是構造「函數」,他們都是函數。而全部的函數都是構造函數Function的實例。從原型鏈機制的的角度來講,那就是說全部的函數都能經過原型鏈找到建立他們的Function構造函數的構造原型Function.protorype對象,因此:

alert(Object instanceof Function);// return true

與此同時,又由於Function.prototype是一個對象,因此他的構造函數是Object. 從原型鏈機制的的角度來講,那就是說全部的函數都能經過原型鏈找到建立他們的Object構造函數的構造原型Object.prototype對象,因此:

alert(Function instanceof Object);// return true

有趣的是根據咱們經過原型鏈機制對instanceof進行的分析,咱們不可貴出一個結論:Function instanceof Function 依然返回true, 原理是同樣的

1. Function是構造函數,因此它是函數對象

2. 函數對象都是由Function構造函數建立而來的,原型鏈機制解釋爲:函數對象的原型鏈中存在Function.prototype

3. instanceof查找原型鏈中的每個節點,若是Function.prototype的構造函數Function的原型鏈中被查到,返回true

所以下面代碼依然返回true

alert(Function instanceof Function);// still true

instanceof部分摘自:https://www.cnblogs.com/objectorl/archive/2010/01/11/Object-instancof-Function-clarification.html

constructor:據對象的constructor判斷,返回對建立此對象的數組函數的引用。

var c= [1,2,3]; 
var d = new Date(); 
var e = function(){alert(111);}; 
alert(c.constructor === Array) //----------> true
alert(d.constructor === Date) //-----------> true
alert(e.constructor === Function) //-------> true
//注意: constructor 在類繼承時會出錯

prototype:全部數據類型都可判斷:Object.prototype.toString.call,這是對象的一個原生原型擴展函數,用來更精確的區分數據類型。

var gettype=Object.prototype.toString
gettype.call('aaaa') //輸出 [object String] 
gettype.call(2222) //輸出 [object Number] 
gettype.call(true) //輸出 [object Boolean] 
gettype.call(undefined) //輸出 [object Undefined] 
gettype.call(null) //輸出 [object Null] 
gettype.call({}) //輸出 [object Object] 
gettype.call([]) //輸出 [object Array] 
gettype.call(function(){}) //輸出 [object Function]

6、做用域鏈、閉包、做用域

1. 做用域/鏈

變量做用域:一個變量能夠使用的範圍

JS中首先有一個最外層的做用域:稱之爲全局做用域

JS中還能夠經過函數建立出一個獨立的做用域,其中函數能夠嵌套,因此做用域也能夠嵌套

詞法做用域就是在你寫代碼時將變量和塊做用域寫在哪裏來決定,也就是詞法做用域是靜態的做用域,在你書寫代碼時就肯定了。

做用域鏈是由當前做用域與上層一系列父級做用域組成,做用域的頭部永遠是當前做用域,尾部永遠是全局做用域。做用域鏈保證了當前上下文對其有權訪問的變量的有序訪問。

做用域鏈的意義:查找變量(肯定變量來自於哪裏,變量是否能夠訪問)

引擎會在解釋javascript代碼以前首先對其進行編譯。編譯階段中的一部分工做就是找到全部的聲明,並用合適的做用域將它們關聯起來,引擎查詢共分爲兩種:LHS查詢和RHS查詢 

從字面意思去理解,當變量出如今賦值操做的左側時進行LHS查詢,出如今右側時進行RHS查詢,更準確地講,RHS查詢與簡單地查找某個變量的值沒什麼區別,而LHS查詢則是試圖找到變量的容器自己,從而能夠對其賦值

function foo(a){
    console.log(a);//2
}
foo( 2 );

這段代碼中,總共包括4個查詢,分別是:

  一、foo(...)對foo進行了RHS引用

  二、函數傳參a = 2對a進行了LHS引用

  三、console.log(...)對console對象進行了RHS引用,並檢查其是否有一個log的方法

  四、console.log(a)對a進行了RHS引用,並把獲得的值傳給了console.log(...)

在當前做用域中沒法找到某個變量時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量,或抵達最外層的做用域(也就是全局做用域)爲止。

RHS查詢

【1】若是RHS查詢失敗,引擎會拋出ReferenceError(引用錯誤)異常

//對b進行RHS查詢時,沒法找到該變量。也就是說,這是一個「未聲明」的變量
function foo(a){
    a = b;  
}
foo();//ReferenceError: b is not defined

【2】若是RHS查詢找到了一個變量,但嘗試對變量的值進行不合理操做,好比對一個非函數類型值進行函數調用,或者引用null或undefined中的屬性,引擎會拋出另一種類型異常:TypeError(類型錯誤)異常

function foo(){
    var b = 0;
    b();
}
foo();//TypeError: b is not a function

LHS查詢

【1】當引擎執行LHS查詢時,若是沒法找到變量,全局做用域會建立一個具備該名稱的變量,並將其返還給引擎

function foo(){
    a = 1;  
}
foo();
console.log(a);//1

【2】若是在嚴格模式中LHS查詢失敗時,並不會建立並返回一個全局變量,引擎會拋出同RHS查詢失敗時相似的ReferenceError異常

function foo(){
    'use strict';
    a = 1;  
}
foo();
console.log(a);//ReferenceError: a is not defined

原理解析:

function foo(a){
    console.log(a);
}
foo(2);

【1】引擎須要爲foo(...)函數進行RHS引用,在全局做用域中查找foo。成功找到並執行

【2】引擎須要進行foo函數的傳參a=2,爲a進行LHS引用,在foo函數做用域中查找a。成功找到,並把2賦值給a

【3】引擎須要執行console.log(...),爲console對象進行RHS引用,在foo函數做用域中查找console對象。因爲console是個內置對象,被成功找到

【4】引擎在console對象中查找log(...)方法,成功找到

【5】引擎須要執行console.log(a),對a進行RHS引用,在foo函數做用域中查找a,成功找到並執行

【6】因而,引擎把a的值,也就是2傳到console.log(...)中

【7】最終,控制檯輸出2

2. 閉包

閉包就是一個函數,一個能夠訪問並操做其餘函數內部變量的函數。也能夠說是一個定義在函數內部的函數。由於JavaScript沒有動態做用域,而閉包的本質是靜態做用域(靜態做用域規則查找一個變量聲明時依賴的是源程序中塊之間的靜態關係),因此函數訪問的都是咱們定義時候的做用域,也就是詞法做用域。因此閉包纔會得以實現。
咱們常見的閉包形式就是a 函數套 b 函數,而後 a 函數返回 b 函數,這樣 b 函數在 a 函數之外的地方執行時,依然能訪問 a 函數的做用域。其中「b 函數在 a 函數之外的地方執行時」這一點,才體現了閉包的真正的強大之處。
function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    return fn2;
}
var fn3 = fn1();
fn3();
  • fn2的詞法做用域能訪問fn1的做用域

  • fn2當作一個值返回

  • fn1執行後,將fn2的引用賦值給fn3

  • 執行fn3,輸出了變量name

正常來講,當fn1函數執行完畢以後,其做用域是會被銷燬的,而後垃圾回收器會釋放那段內存空間。而閉包卻很神奇的將fn1的做用域存活了下來,fn2依然持有該做用域的引用,這個引用就是閉包。

閉包造成的條件

  • 函數嵌套
  • 內部函數引用外部函數的局部變量

閉包的內存泄漏

棧內存提供一個執行環境,即做用域,包括全局做用域和私有做用域,那他們何時釋放內存的?

  • 全局做用域----只有當頁面關閉的時候全局做用域纔會銷燬
  • 私有的做用域----只有函數執行纔會產生

通常狀況下,函數執行會造成一個新的私有的做用域,當私有做用域中的代碼執行完成後,咱們當前做用域都會主動的進行釋放和銷燬。但當遇到函數執行返回了一個引用數據類型的值,而且在函數的外面被一個其餘的東西給接收了,這種狀況下通常造成的私有做用域都不會銷燬。

所謂內存泄漏指任何對象在您再也不擁有或須要它以後仍然存在。閉包不能濫用,不然會致使內存泄露,影響網頁的性能。閉包使用完了後,要當即釋放資源,將引用變量指向null。
//經典面試題  
function outer(){
  var num=0;//內部變量
  return function add(){//經過return返回add函數,就能夠在outer函數外訪問了
  num++;//內部函數有引用,做爲add函數的一部分了
  console.log(num);
  };
 }
  var func1=outer();
  func1();//其實是調用add函數, 輸出1
  func1();//輸出2 由於outer函數內部的私有做用域會一直被佔用
  var func2=outer();
  func2();// 輸出1  每次從新引用函數的時候,閉包是全新的。
  func2();// 輸出2  

閉包的做用

  • 能夠讀取函數內部的變量。
  • 能夠使變量的值長期保存在內存中,生命週期比較長。所以不能濫用閉包,不然會形成網頁的性能問題
  • 能夠用來實現JS模塊。

JS模塊:具備特定功能的js文件,將全部的數據和功能都封裝在一個函數內部(私有的),只向外暴露一個包信n個方法的對象或函數,模塊的使用者,只須要經過模塊暴露的對象調用方法來實現對應的功能。

閉包的運用

應用閉包的主要場合是:設計私有的方法和變量。

補充:塊級做用域

一般是由於只想在for循環內部的上下文中使用變量i,但實際上i能夠在全局做用域中訪問,污染了整個做用域:

for (var i= 0; i<10; i++) {
     console.log(i);
}
console.log(i);//10
//當即執行匿名函數(IIFE)
(function(){
  var i = 1;  
})();
console.log(i);//ReferenceError: i is not defined

//for循環的代碼中變量i用let聲明,將會避免做用域污染問題
for (let i= 0; i<10; i++) {
     console.log(i);
}
console.log(i);////ReferenceError: i is not defined

下面代碼中,因爲閉包只能取得包含函數中的任何變量的最後一個值,因此控制檯輸出5,而不是0

var a = [];
for(var i = 0; i < 5; i++){
    a[i] = function(){
        return i;
    }
}
console.log(a[0]());//5

//能夠經過函數傳參,來保存每次循環的值
var a = [];
for(var i = 0; i < 5; i++){
    a[i] = (function(j){
        return function(){
            return j;
        }
    })(i);
}
console.log(a[0]());//0

//而使用let則更方便,因爲let循環有一個從新賦值的過程,至關於保存了每一次循環時的值
var a = [];
for(let i = 0; i < 5; i++){
    a[i] = function(){
        return i;
    }
}
console.log(a[0]());//0

補充:變量/函數提高

 var a = 2 ; 這個代碼片斷實際上包括兩個操做: var a  和  a = 2  ,第一個定義聲明是在編譯階段由編譯器進行的。第二個賦值操做會被留在原地等待引擎在執行階段執行。

console.log(a);
var a = 0;
function fn(){
    console.log(b);
    var b = 1;
    function test(){
        console.log(c);
        var c = 2;
    }
    test();
}
fn();
//變量聲明提高後,變成下面這樣
var a ;
console.log(a);
a = 0;
function fn(){
    var b;
    console.log(b);
    b = 1;
    function test(){
        var c ;
        console.log(c);
        c = 2;
    }
    test();
}
fn();

函數聲明會提高,但函數表達式卻不會提高

foo();
function foo(){
    console.log(1);//1
}

//提高後
function foo(){
    console.log(1);
}
foo();

//函數表達式不會提高
foo();
var foo = function(){
    console.log(1);//TypeError: foo is not a function
}

//變量提高後,代碼以下所示,依然會報錯:
var foo;
foo();
foo = function(){
    console.log(1);
}

函數聲明和變量聲明都會被提高。可是,函數聲明會覆蓋變量聲明

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

可是,若是變量存在賦值操做,則最終的值爲變量的值

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

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

注意:變量的重複聲明是無用的,但函數的重複聲明會覆蓋前面的聲明(不管是變量仍是函數聲明)

【1】變量的重複聲明無用

var a = 1;
var a;
console.log(a);//1

【2】因爲函數聲明提高優先於變量聲明提高,因此變量的聲明無做用

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

【3】後面的函數聲明會覆蓋前面的函數聲明

複製代碼
a();//2
function a(){
    console.log(1);
}
function a(){
    console.log(2);
}

 本部分參考博主連接:http://www.javashuo.com/article/p-fcyvnpug-cv.html

7、Ajax的原生寫法

1. Ajax介紹

  • 全稱Asynchronous JavaScript and XML
  • 異步的 JavaScript 和 XML;
  • 能夠在不從新加載整個頁面的狀況下,與服務器交換數據並更新部分網頁內容;
  • 可以實現局部刷新,大大下降了資源的浪費;
  • 不須要任何瀏覽器插件,但須要用戶容許JavaScript在瀏覽器上執行;
  • 是一門用於建立快速動態網頁的技術;
  • 傳統的網頁(不使用 AJAX)若是須要更新內容,必須重載整個網頁;
Ajax的工做原理至關於在用戶和服務器之間加了—箇中間層(AJAX引擎),使用戶操做與服務器響應異步化。並非全部的用戶請求都提交給服務器。像—些數據驗證和數據處理等都交給Ajax引擎本身來作,,只有肯定須要從服務器讀取新數據時再由Ajax引擎代爲向服務器提交請求。

2. XMLHttpRequest 對象的三個經常使用的屬性

  • onreadystatechange 屬性存有處理服務器響應的函數;
  • readyState 屬性存有服務器響應的狀態信息。每當 readyState 改變時,onreadystatechange 函數就會被執行;
  • 能夠經過 responseText 屬性來取回由服務器返回的數據。
狀態 描述
0 請求未初始化(在調用 open() 以前)
1 請求已提出(調用 send() 以前)
2 請求已發送(這裏一般能夠從響應獲得內容頭部)
3 請求處理中(響應中一般有部分數據可用,可是服務器尚未完成響應)
4 請求已完成(能夠訪問服務器響應並使用它)

3. xmlhttprequst的方法

  • open() 有三個參數。第一個參數定義發送請求所使用的方法,第二個參數規定服務器端腳本的URL,第三個參數規定應當對請求進行異步地處理。 xmlHttp.open("GET","test.php",true); 
  • send() 方法將請求送往服務器。若是咱們假設 HTML 文件和 PHP 文件位於相同的目錄,那麼代碼是這樣的: xmlHttp.send(null); 

4. 實現Ajax

  • 建立XMLHttpRequest對象。
  • 設置請求方式。
  • 調用回調函數。
  • 發送請求。
var Ajax = {
    get: function(url, fn) {
        //建立XMLHttpRequest對象
        var xhr = new XMLHttpRequest();
        //true表示異步
        xhr.open('GET', url, true);
        xhr.onreadystatechange = function() {
            // readyState == 4說明請求已完成
            if(xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) {
                //responseText:從服務器得到數據
                fn.call(this, xhr.responseText);
             }
         };
          xhr.send();
      },
     post: function(url, data, fn) { //datat應爲'a=a1&b=b1'這種字符串格式
         var xhr = new XMLHttpRequest();
         xhr.open("POST", url, true);
         // 添加http頭,發送信息至服務器時內容編碼類型
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         xhr.onreadystatechange = function() {
             if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
                fn.call(this, xhr.responseText);
             }
         };
             xhr.send(data);
     }
}

8、對象深拷貝、淺拷貝

淺拷貝就是把屬於源對象的值都複製一遍到新的對象,不會開闢二者獨立的內存區域;深度拷貝則是完徹底全兩個獨立的內存區域,互不干擾

//js的深拷貝
function deepCopy(obj){
    //判斷是不是簡單數據類型,
    if(typeof obj == "object"){ //複雜數據類型 var result = obj.constructor == Array ? [] : {}; for(let i in obj){ result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i]; } }else { //簡單數據類型 直接 == 賦值 var result = obj; } return result; }

 

/**
* deep clone
* @param  {[type]} parent object 須要進行克隆的對象
* @return {[type]}        深克隆後的對象
*/
const clone = parent => {
  // 維護兩個儲存循環引用的數組
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== 'object') return parent;

    let child, proto;

    if (isType(parent, 'Array')) {
      // 對數組作特殊處理
      child = [];
    } else if (isType(parent, 'RegExp')) {
      // 對正則對象作特殊處理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, 'Date')) {
      // 對Date對象作特殊處理
      child = new Date(parent.getTime());
    } else {
      // 處理對象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切斷原型鏈
      child = Object.create(proto);
    }

    // 處理循環引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 若是父數組存在本對象,說明以前已經被引用過,直接返回此對象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 遞歸
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

9、圖片懶加載、預加載

懶加載即延遲,對於圖片過多的頁面,爲了加快頁面加載速度,咱們須要將頁面內未出如今可視區域內的圖片先不作加載, 等到滾動到可視區域後再去加載。這樣一來頁面加載性能大幅提高,提升了用戶體驗。

第一種是純粹的延遲加載,使用setTimeOut或setInterval進行加載延遲,若是用戶在加載前就離開了頁面,那麼就不會加載。 
第二種是條件加載,符合某些條件,或觸發了某些事件纔開始異步下載。
第三種是可視區加載,即僅加載用戶能夠看到的區域,這個主要由監控滾動條來實現,通常會在距用戶看到某圖片前必定距離遍開始加載,這樣能保證用戶拉下時正好能看到圖片。
原理:在頁面載入時將img標籤內的src指向一個小圖片,即佔位圖或loading圖,將真實地址存放於一個自定義屬性data-src中。當頁面滾動時,遍歷有data-src的img標籤,判斷每一個img是否進入可視區,當某個img進入了可視區域,就將真實地址賦值給該img的src並將該img的data-src刪除以免重複判斷。
<img src="https://i.loli.net/2017/08/08/5989307b6c87b.gif" data-xxx="${data.content[i].url}">
let images = document.querySelectorAll('img[data-xxx]')
  for(let i = 0; i <images.length; i++){
    if(出如今屏幕裏(images[i])){
      images[i].src = images[i].getAttribute('data-xxx')
      images[i].removeAttribute('data-xxx')
    }

預加載:圖片預加載就是在網頁所有加載以前,提早加載圖片。當用戶須要查看時可直接從本地緩存中渲染,以提供給用戶更好的體驗,減小等待的時間。不然,若是一個頁面的內容過於龐大,沒有使用預加載技術的頁面就會長時間的展示爲一片空白,這樣瀏覽者可能覺得圖片預覽慢而沒興趣瀏覽,把網頁關掉,這時,就須要圖片預加載。固然這種作法實際上犧牲了服務器的性能換取了更好的用戶體驗。

實現預載的方法很是多,能夠用CSS(background)、JS(Image)、HTML(<img />)均可以。經常使用的是new Image();,設置其src來實現預載,再使用onload方法回調預載完成事件。只要瀏覽器把圖片下載到本地,一樣的src就會使用緩存,這是最基本也是最實用的預載方法。當Image下載完圖片頭後,會獲得寬和高,所以能夠在預載前獲得圖片的大小(我所知的方法是用記時器輪循寬高變化)。通常實現預載的工具類,都實現一個Array來存須要預載的URL,而後實現Finish、Error、SizeChange等經常使用事件,能夠由用戶選擇是順序預載或假併發預載。Jquery的PreLoad能夠用於預載。

JS獲取寬高的方式

獲取屏幕的高度和寬度(屏幕分辨率): window.screen.height/width 
獲取屏幕工做區域的高度和寬度(去掉狀態欄): window.screen.availHeight/availWidth 
網頁全文的高度和寬度: document.body.scrollHeight/Width 
滾動條捲上去的高度和向右卷的寬度: document.body.scrollTop/scrollLeft 
網頁可見區域的高度和寬度(不加邊線): document.body.clientHeight/clientWidth 
網頁可見區域的高度和寬度(加邊線): document.body.offsetHeight/offsetWidth 

10、實現頁面加載進度條

document.onreadystatechange頁面加載狀態改變時的事件;
document.readyState返回當前文檔的狀態(uninitialized--還未開始載入;loading--載入中;interactive--已加載,文檔與用戶能夠開始交互;complete--載入完成)

<script type="text/javascript">
//頁面加載狀態改變時的事件
    document.onreadystatechange = function () {
       if(document.readyState == 'complete'){   //判斷頁面加載完成,加載的圖標就隱藏
           $(".loading").fadeOut();
       }
    }
</script>

11、this關鍵字

在Javascript中,當一個函數被調用時,會建立一個活動記錄(也稱爲執行上下文)。它包含函數在哪裏調用、函數的調用方式、傳入的參數等信息。this就是這個記錄的一個屬性,會在函數執行的過程當中用到。this關鍵字是在運行時進行綁定的,與函數聲明的位置沒有任何關係,它指向什麼徹底取決於函數在哪裏被調用

1. this四大綁定規則

函數綁定(默認綁定) 

當直接調用函數時就是函數綁定模式。

function fn() {
    console.log( this.a );
}
var a = 2;
fn(); 
// 2 -- fn單獨調用,this引用window

注意:在非嚴格模式下,this將綁定到全局對象window。然而,在嚴格模式下,this將綁定到undefined

隱式綁定(方法調用)

當函數做爲一個對象的屬性被調用的時候就屬於隱式綁定模式,此時,this指向是調用這個函數的對象。

function test(){
 alert(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1

注意:被隱式綁定的函數會丟失綁定對象,此時,將會應用默認綁定,從而把this綁定到全局對象或undefined上

顯式綁定(硬綁定)

在Javascript中,一般使用call/apply/bind方法來進行顯示綁定。

var x = 0;
function test(){
 alert(this.x);
}

var obj={}; obj.x = 1; obj.m = test; obj.m.apply(); //0 //apply()的參數爲空時,默認調用全局對象。所以,這時的運行結果爲0,證實this指的是全局對象。若是把最後一行代碼修改成 obj.m.apply(o); //1

new綁定(構造器綁定) 

經過new關鍵字調用的函數,屬於new綁定模式。這時this關鍵字指向這個新建立的對象。

function test(){
  this.x = 1;
}
 var obj = new test();
 alert(obj.x); // 1
 //運行結果爲1。爲了代表這時this不是全局對象,我對代碼作一些改變:
 var x = 2;
 function test(){
   this.x = 1;
 }
 var obj = new test();
 alert(x); //2

this關鍵字綁定規則的斷定順序

  • 函數是不是new綁定?若是是,則this指向新建立的對象;
  • 函數是否經過call/apply/bind顯式綁定或硬綁定?若是是,則this指向指定的對象;
  • 函數是否在某個上下文對象中隱式調用?若是是,this綁定的是那個上下文對象;
  • 上述全不是,則使用默認綁定。若是在嚴格模式下,就綁定到undefined,不然綁定到全局window對象。

2. this綁定指向改變

call:

function.call(obj,[param1[,param2[,…[,paramN]]]])

obj:將代替function類裏的this對象

parms:這是一個參數列表

當即執行

apply:

function.apply(obj,args)

obj:將代替function類裏this對象

args:數組,它將做爲參數傳給function

當即執行

bind:

function.bind(obj,arg1,arg2,...)

不會當即執行,而是返回一個新的函數

3. 被忽略的this

若是將null或者undefined做爲this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。

很是常見的作法是使用apply來「展開」一個數組,並看成參數傳入一個函數如:求數組最大最小值,合併數組等,具體用法以下:

var min = Math.min.apply(null, arr);
var max = Math.max.apply(null, arr);
Array.prototype.push.apply(arrA, arrB);

箭頭函數不使用this的四種綁定規則,而是根據外層(函數或者全局)做用域來決定this的指向。

箭頭函數中的this只和定義它的做用域的this有關,而與在哪裏以及如何調用它無關,同時它的this指向是不能夠改變的。

本部分摘自:https://mp.weixin.qq.com/s/31HlZRug9RjcKBXCtfbBXA

12、函數式編程

函數式編程的歷史已經很悠久了,可是最近幾年卻頻繁的出如今大衆的視野,不少不支持函數式編程的語言也在積極加入閉包,匿名函數等很是典型的函數式編程特性。大量的前端框架也標榜本身使用了函數式編程的特性,好像一旦跟函數式編程沾邊,就很高大上同樣,並且還有一些專門針對函數式編程的框架和庫,好比:RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS等。函數式編程變得愈來愈流行,掌握這種編程範式對書寫高質量和易於維護的代碼都大有好處。

函數式編程經常使用核心概念

•純函數

•函數的柯里化(柯里化是一種「預加載」函數的方法,經過傳遞較少的參數,獲得一個已經記住了這些參數的新函數,某種意義上講,這是一種對參數的「緩存」,是一種很是高效的編寫函數的方法。)

•函數組合

•Point Free

•聲明式與命令式代碼

//命令式
let CEOs = [];
for (var i = 0; i < companies.length; i++) {
    CEOs.push(companies[i].CEO)
}
//聲明式
let CEOs = companies.map(c => c.CEO);

簡單來講,也就是當一個函數的輸出不受外部環境影響,同時也不影響外部環境時,該函數就是純函數,也就是它只關注邏輯運算和數學運算,同一個輸入總獲得同一個輸出。

javascript內置函數有很多純函數,也有很多非純函數。

純函數:

Array.prototype.slice

Array.prototype.map

String.prototype.toUpperCase

非純函數:

Math.random

Date.now

Array.ptototype.splice

調用數組的slice方法每次返回的結果徹底相同,同時數組不會被改變,而調用splice方法每次返回值都不同,同時會改變原數組。

這就是咱們強調使用純函數的緣由,由於純函數相對於非純函數來講,在可緩存性、可移植性、可測試性以及並行計算方面都有着巨大的優點。

把一個函數變純的基本手段是不要依賴系統狀態。

本部分參考連接:https://www.cnblogs.com/fengyuqing/p/functional_programming_1.html    http://www.javashuo.com/article/p-axsmkixw-ke.html

十3、手動實現parseInt

parseInt是ECMAScript核心的一個全局函數,能夠在實現了ECMAScript的宿主環境全局調用。

console.log(parseInt('12'));
console.log(parseInt('08'));
console.log(parseInt('0x16'));
console.log(parseInt('-12'));
console.log(parseInt('   -12'));
console.log(parseInt('   -  12'));
console.log(parseInt('124ref'));
console.log(parseInt('ref'));

parseInt(string, [int radix])第二個形參是能夠忽略的,忽略時默認賦值爲10也就是十進制。

radix就是指定第一個形參的進制類型,而後根據這個進制類型再轉換爲十進制整數

radix形參沒指定的時候是10,有效範圍:[2, 36]和特殊值0
1. 將第一個形參轉換爲字符串
2. 識別string轉換是否有code unit,若是有 -, -標記爲負數,0x或0X則把radix賦值爲16
3. radix形參(int類型)是否存在,存在則從新賦值(會對實參進行Int32轉化,沒法轉換成int類型則不會從新賦值radix)
4. radix爲0,則設置radix爲默認值10
5. 若是radix爲1,或者大於等於37,parseInt直接返回NaN
6. 若是radix爲[2, 36]時則表明,string參數分別是二進制,三進制(若是有得話~)…三十六進制類型
7. 而後對string進行的radix進制進行十進制轉換,例如,按二進制對string來進行十進制轉換

['1', '2', '3'].map(parseInt) //[1, NaN, NaN]

//內部執行的剖析
(function (){
        var ret = ['1', '2', '3'].map((value, index)=>{
            console.log(value, index);
            return parseInt(value, index);
        });
        console.log(ret);
    })();

//所以,其實是
parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);

parseInt('13', 2),這個結果是……1,由於string參數若是最開始的code符合radix進制的話是能夠進行解析轉換的,正如這裏’1’是符合二進制的,’3’是不符合二進制的,但1處於優先位置,因此能夠進行轉換解析,而3被無情地忽略~

function l(obj) {
   return console.log(obj)
}
function _parseInt(str,radix){
        var res = 0;
        if(typeof str !="string" && typeof str !="number"){
            return NaN;
        }
        str =String(str).trim().split(".")[0];
        // l(str)
        let len = str.length;
        if(!len){
            return NaN;
        }
        if(!radix){
            return radix = 10;
        }
        if(typeof radix !=="number" || radix < 2 || radix >36){
            return NaN;
        }
        for(let i = 0; i < len; i++){
            let arr = str.split("");
           l(arr instanceof Array)
            l(typeof arr)
            res += Math.floor(arr[i])*Math.pow(radix,i)
        }
        l(res);
}
_parseInt("654646",10)

十4、爲何會有同源策略

同源策略是瀏覽器的一個安全功能,不一樣源的客戶端腳本在沒有明確受權的狀況下,不能讀寫對方資源。只有同一個源的腳本賦予dom、讀寫cookie、session、ajax等操做的權限。url由協議、域名、端口和路徑組成、若是兩個url的協議、域名和端口相同,則這兩個url是同源的。限制來源不用源的「document」,對當前的「document」讀取或設置某些屬性。在不受同源策略限制,帶有「src」屬性的標籤加載是,其實是由遊覽器發起一次GET請求,不一樣於XMLHTTPRequest,它們經過src屬性加載的資源。但遊覽器限制了JavaScript的權限,使其不能讀,寫其中返回的內容。

若是沒有同源策略,不一樣源的數據和資源(如HTTP頭、Cookie、DOM、localStorage等)就能相互隨意訪問,根本沒有隱私和安全可言。爲了安全起見和資源的有效管理,瀏覽器固然要採用這種策略。

同源策略是一種約定,它是瀏覽器最核心和最基本的安全功能,能夠用於隔離潛在惡意文件,若是沒有了同源策略,瀏覽器的正常使用將受到影響。

瀏覽器採用同源策略,禁止頁面加載或執行與自身不一樣源的任何腳本。若是沒有同源策略,那麼惡意網頁能夠讀取銀行網站、網上商城等裏面的用戶信息,甚至篡改帳號密碼等。因此全部支持JavaScript的瀏覽器都採用了同源策略。

十5、怎麼判斷兩個對象是否相等

ES6有一個方法來判斷兩個對象是否相等 console.log(Object.is(a,b)) ,可是這個相等,和咱們平時要的相等可能不同,這個方法判斷的是a和b是否是同一個指針的對象。

var a = {
  id:1
};
var b = a;
console.log(Object.is(a,b));   //true

//當咱們只須要兩個對象的內容相同的時候,他就沒效果了
var a = {
  id:1
};
var b = {
  id:1
}
console.log(Object.is(a,b));   //false

思路:只要兩個對象的名和鍵值都相同。那麼兩個對象的內容就相同了(考慮若是鍵值也是對象的狀況——用遞歸,遞歸的時候要判斷prop是否是Object)

1.用Object.getOwnPropertyNames拿到對象的因此鍵名數組
2.比對鍵名數組的長度是否相等。否=>false。真=>3
3.比對鍵名對應的鍵值是否相等

function isObjectValueEqual(a, b) {
   var aProps = Object.getOwnPropertyNames(a);
   var bProps = Object.getOwnPropertyNames(b);
   if (aProps.length != bProps.length) {
      return false;
   }
   for (var i = 0; i < aProps.length; i++) {
      var propName = aProps[i]
      var propA = a[propName]
      var propB = b[propName]
      if (propA !== propB) {
        if ((typeof (propA) === 'object')) {
          if (this.isObjectValueEqual(propA, propB)) {
            return true
          } else {
            return false
          }
        } else {
          return false
        }
      } else {
        return false
      }
    }
        return true;
    }
    var a = {
        id:1,
        name:2,
        c:{
            age:3
        }
    };
    var b = {
        id:1,
        name:2,
        c:{
            age:3
        }
    }
    console.log(isObjectValueEqual(a,b));//true

本部分摘自:https://www.jianshu.com/p/7407bd65b15d

十6、事件模型

1.原始事件模型(DOM0級)

這是一種被全部瀏覽器都支持的事件模型,對於原始事件而言,沒有事件流,事件一旦發生將立刻進行處理,有兩種方式能夠實現原始事件:

(1)在html代碼中直接指定屬性值:<button id="demo" type="button" onclick="doSomeTing()" />  

(2)在js代碼中爲 document.getElementsById("demo").onclick = doSomeTing()

優勢:全部瀏覽器都兼容

缺點:1)邏輯與顯示沒有分離;2)相同事件的監聽函數只能綁定一個,後綁定的會覆蓋掉前面的,如:a.onclick = func1; a.onclick = func2;將只會執行func2中的內容。3)沒法經過事件的冒泡、委託等機制完成更多事情。

由於這些缺點,雖然原始事件類型兼容全部瀏覽器,但仍不推薦使用。

2.DOM2事件模型

此模型是W3C制定的標準模型,現代瀏覽器(IE6~8除外)都已經遵循這個規範。W3C制定的事件模型中,一次事件的發生包含三個過程:(1).事件捕獲階段,(2).事件目標階段,(3).事件冒泡階段

事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨着DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程當中,事件相應的監聽函數是不會被觸發的。

事件目標:當到達目標元素以後,執行目標元素該事件相應的處理函數。若是沒有綁定監聽函數,那就不執行。

事件冒泡:從目標元素開始,往頂層元素傳播。途中若是有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。

全部的事件類型都會經歷事件捕獲可是隻有部分事件會經歷事件冒泡階段,例如submit事件就不會被冒泡。 

事件的傳播是能夠阻止的:
  • 在W3c中,使用stopPropagation()方法
  • 在IE下設置cancelBubble = true;
  在捕獲的過程當中stopPropagation();後,後面的冒泡過程就不會發生了。

標準的事件監聽器該如何綁定:

addEventListener("eventType","handler","true|false");其中eventType指事件類型,注意不要加‘on’前綴,與IE下不一樣。第二個參數是處理函數,第三個即用來指定是否在捕獲階段進行處理,通常設爲false來與IE保持一致(默認設置),除非你有特殊的邏輯需求。監聽器的解除也相似:removeEventListner("eventType","handler","true!false");

3.IE事件模型

IE不把該對象傳入事件處理函數,因爲在任意時刻只會存在一個事件,因此IE把它做爲全局對象window的一個屬性,爲求證其真僞,使用IE8執行代碼alert(window.event),結果彈出是null,說明該屬性已經定義,只是值爲null(與undefined不一樣)。難道這個全局對象的屬性是在監聽函數裏才加的?因而執行下面代碼:

    window.onload = function (){alert(window.event);}

    setTimeout(function(){alert(window.event);},2000);

結果第一次彈出【object event】,兩秒後彈出依然是null。因而可知IE是將event對象在處理函數中設爲window的屬性,一旦函數執行結束,便被置爲null了。IE的事件模型只有兩步,先執行元素的監聽函數,而後事件沿着父節點一直冒泡到document。冒泡已經講解過了,這裏不重複。IE模型下的事件監聽方式也挺獨特,綁定監聽函數的方法是:attachEvent( "eventType","handler"),其中evetType爲事件的類型,如onclick,注意要加’on’。解除事件監聽器的方法是 detachEvent("eventType","handler" )

IE的事件模型已經能夠解決原始模型的三個缺點,但其本身的缺點就是兼容性,只有IE系列瀏覽器才能夠這樣寫。

以上就是3種事件模型,在咱們寫代碼的時候,爲了兼容ie,一般使用如下寫法:

var demo = document.getElementById('demo');
  if(demo.attachEvent){
     demo.attachEvent('onclick',func);
  }else{
      demo.addEventListener('click',func,false);
}

事件被封裝成一個event對象,包含了該事件發生時的全部相關信息(event的屬性)以及能夠對事件進行的操做(event的方法)。

1. 事件定位相關屬性

x/y與clientX/clientY值同樣,表示距瀏覽器可視區域(工具欄除外區域)左/上的距離;

pageX/pageY,距頁面左/上的距離,它與clientX/clientY的區別是不隨滾動條的位置變化;

screenX/screenY,距計算機顯示器左/上的距離,拖動你的瀏覽器窗口位置能夠看到變化;

layerX/layerY與offsetX/offsetY值同樣,表示距有定位屬性的父元素左/上的距離。

2.其餘經常使用屬性

target:發生事件的節點;

currentTarget:當前正在處理的事件的節點,在事件捕獲或冒泡階段;

timeStamp:事件發生的時間,時間戳。

bubbles:事件是否冒泡。

cancelable:事件是否能夠用preventDefault()方法來取消默認的動做;

keyCode:按下的鍵的值;

3. event對象的方法

event. preventDefault()//阻止元素默認的行爲,如連接的跳轉、表單的提交;

event. stopPropagation()//阻止事件冒泡

event.initEvent()//初始化新事件對象的屬性,自定義事件會用,不經常使用

event. stopImmediatePropagation()//能夠阻止掉同一事件的其餘優先級較低的偵聽器的處理(這貨表示沒用過,優先級就不說明了,谷歌或者問度娘吧。)

event.target與event.currentTarget他們有什麼不一樣?

target在事件流的目標階段;currentTarget在事件流的捕獲,目標及冒泡階段。只有當事件流處在目標階段的時候,兩個的指向纔是同樣的, 而當處於捕獲和冒泡階段的時候,target指向被單擊的對象而currentTarget指向當前事件活動的對象(通常爲父級)。

本部分摘自:http://www.javashuo.com/article/p-ghctezii-k.html

十7、window的onload事件和DOMContentLoaded

一、當 onload 事件觸發時,頁面上全部的DOM,樣式表,腳本,圖片,flash都已經加載完成了。

二、當 DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片,flash。

onload事件是DOM事件,onDOMContentLoaded是HTML5事件。

onload事件會被樣式表、圖像和子框架阻塞,而onDOMContentLoaded不會。

當加載的腳本內容並不包含當即執行DOM操做時,使用onDOMContentLoaded事件是個更好的選擇,會比onload事件執行時間更早。

十8、for...in迭代和for...of有什麼區別

1. for…in 語句以原始插入順序迭代對象的可枚舉屬性。

2. for…of 語句遍歷可迭代對象定義要迭代的數據。

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};
let iterable = [3, 5, 7];
iterable.foo = 'hello';
//for in 會繼承
for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}
// for of
for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

for...of循環是ES6引入的新的語法。

for...in遍歷拿到的x是鍵(下標)。而for...of遍歷拿到的x是值,但在對象中會提示不是一個迭代器報錯。

let x;
let a = ['A','B','C'];
let b = {name: '劉德華',age: '18'};

console.log(a.length);
for(x of a){
  console.log(x); //A,B,C
}
for(x in a){
  console.log(x+':'+a[x]); //0:A,1:B,2:C
}
/*for(x of b){
  console.log(x); //報錯
}*/
for(x in b){
  console.log(x); //name,age
}

a.name = "Hello";
for(x in a){
  console.log(x); //0,1,2,name
}
console.log(a.length); //3

for...in因爲歷史遺留問題,它遍歷的其實是對象的屬性名稱,一個Array數據也是一個對象,數組中的每一個元素的索引被視爲屬性名稱。

因此咱們能夠看到使用for...in循環Array數組時,拿到的實際上是每一個元素的索引。以下,把name包括在內,可是Array的length屬性卻不包括在內,for...of循環則徹底修復了這些問題,它只循環集合自己的元素。

十9、函數柯里化

將一個低階函數轉換爲高階函數的過程就叫柯里化。好比對於加法操做: var add = (x, y) => x + y ,咱們能夠這樣柯里化

function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數

 
 

// 經典面試題,實現一個add方法,使計算結果可以知足以下預期:

add(1)(2)(3) = 6;

add(1, 2, 3)(4) = 10;

add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次執行時,定義一個數組專門用來存儲全部的參數
    var _args = Array.prototype.slice.call(arguments);
    // 在內部聲明一個函數,利用閉包的特性保存_args並收集全部的參數值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

二10、call apply區別,原生實現bind

call,apply,bind 三者用法和區別:角度可爲參數、綁定規則(顯示綁定和強綁定),運行效率、運行狀況。

//bind的實現就是柯里化
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        return _this.apply(context, args)
    }
}

 

Function.prototype.myCall = function(context = window, ...rest) {
    context.fn = this; //此處this是指調用myCall的function
    let result = context.fn(...rest);
    //將this指向銷燬
    delete context.fn;
    return result;
};
 
 
Function.prototype.myApply = function(context = window, params = []) {
    context.fn = this; //此處this是指調用myCall的function
    let result
    if (params.length) {
        result = context.fn(...params)
    }else {
        result = context.fn()
    }
    //將this指向銷燬
    delete context.fn;
    return result;
};

 

Function.prototype.myBind = function(thisArg) {
  if (typeof this !== 'function') {
    return;
  }
  var _self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  var fnBound = function () {
    // 檢測 New
    // 若是當前函數的this指向的是構造函數中的this 則斷定爲new 操做
    var _this = this instanceof _self ? this : thisArg;
    return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  // 爲了完成 new操做
  // 還須要作一件事情 執行原型 連接 (思考題,爲何?
  fnBound.prototype = this.prototype;
  return fnBound;
} 

二11、async/await

async/await特色

  1. async/await更加語義化,async 是「異步」的簡寫,async function 用於申明一個 function 是異步的; await,能夠認爲是async wait的簡寫, 用於等待一個異步方法執行完成;

  2. async/await是一個用同步思惟解決異步問題的方案(等結果出來以後,代碼纔會繼續往下執行)

  3. 能夠經過多層 async function 的同步寫法代替傳統的callback嵌套

async function語法

  • 自動將常規函數轉換成Promise,返回值也是一個Promise對象

  • 只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數

  • 異步函數內部能夠使用await

await語法

  • await 放置在Promise調用以前,await 強制後面點代碼等待,直到Promise對象resolve,獲得resolve的值做爲await表達式的運算結果

  • await只能在async函數內部使用,用在普通函數裏就會報錯

const timeoutFn = function(timeout){ 
    return new Promise(function(resolve){
        return setTimeout(resolve, timeout);
               });
}

async function fn(){
    await timeoutFn(1000);
    await timeoutFn(2000);
    return '完成';
}

fn().then(success => console.log(success));
本部分參考連接: https://www.jianshu.com/p/1e75bd387aa0

二12、當即執行函數和使用場景

你的代碼在頁面加載完成以後,不得不執行一些設置工做,好比時間處理器,建立對象等等。全部的這些工做只須要執行一次,好比只須要顯示一個時間。可是這些代碼也須要一些臨時的變量,可是初始化過程結束以後,就不再會被用到,若是將這些變量做爲全局變量,不是一個好的注意,咱們能夠用當即執行函數——去將咱們全部的代碼包裹在它的局部做用域中,不會讓任何變量泄露成全局變量。

二十3、設計模式(要求說出如何實現,應用,優缺點)/單例模式實現

1. 單例模式

單例模式的定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現的方法爲先判斷實例存在與否,若是存在則直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。

適用場景:一個單一對象。好比:彈窗,不管點擊多少次,彈窗只應該被建立一次。

// 單例模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
}
// 獲取實例對象,代理實現單例模式
var getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();
// 測試單例模式的實例
var a = getInstance("aa");
var b = getInstance("bb");
// 實現單例模式彈窗
var createWindow = (function(){
    var div;
    return function(){
        if(!div) {
            div = document.createElement("div");
            div.innerHTML = "我是彈窗內容";
            div.style.display = 'none';
            document.body.appendChild(div);
        }
        return div;
    }
})();
document.getElementById("Id").onclick = function(){
    // 點擊後先建立一個div元素
    var win = createWindow();
    win.style.display = "block";
}

2. 策略模式

策略模式的定義:定義一系列的算法,把他們一個個封裝起來,而且使他們能夠相互替換。

一個基於策略模式的程序至少由兩部分組成。第一個部分是一組策略類(可變),策略類封裝了具體的算法,並負責具體的計算過程。第二個部分是環境類Context(不變),Context接受客戶的請求,隨後將請求委託給某一個策略類。要作到這一點,說明Context中要維持對某個策略對象的引用。
/*策略類*/
var levelOBJ = {
    "A": function(money) {
        return money * 4;
    },
    "B" : function(money) {
        return money * 3;
    },
    "C" : function(money) {
        return money * 2;
    } 
};
/*環境類*/
var calculateBouns =function(level,money) {
    return levelOBJ[level](money);
};
console.log(calculateBouns('A',10000)); // 40000

3. 代理模式

代理模式的定義:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。

經常使用的虛擬代理形式:某一個花銷很大的操做,能夠經過虛擬代理的方式延遲到這種須要它的時候纔去建立(例:使用虛擬代理實現圖片懶加載)

圖片懶加載的方式:先經過一張loading圖佔位,而後經過異步的方式加載圖片,等圖片加載好了再把完成的圖片加載到img標籤裏面。

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

4. 裝飾者模式

裝飾者模式的定義:在不改變對象自身的基礎上,在程序運行期間給對象動態地添加方法。

例如:現有4種型號的自行車分別被定義成一個單獨的類,若是給每輛自行車都加上前燈、尾燈、鈴鐺這3個配件,若是用類繼承的方式,須要建立4*3=12個子類。但若是經過裝飾者模式,只須要建立3個類。

裝飾者模式適用的場景:原有方法維持不變,在原有方法上再掛載其餘方法來知足現有需求;函數的解耦,將函數拆分紅多個可複用的函數,再將拆分出來的函數掛載到某個函數上,實現相同的效果但加強了複用性。

//用AOP裝飾函數實現裝飾者模式
Function.prototype.before = function(beforefn) {
    var self = this;    //保存原函數引用
    return function(){  //返回包含了原函數和新函數的 '代理函數'
        beforefn.apply(this, arguments);    //執行新函數,修正this
        return self.apply(this,arguments);  //執行原函數
    }
}
Function.prototype.after = function(afterfn) {
    var self = this;
    return function(){
        var ret = self.apply(this,arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}
var func = function() {
    console.log('2');
}
//func1和func3爲掛載函數
var func1 = function() {
    console.log('1');
}
var func3 = function() {
    console.log('3');
}
func = func.before(func1).after(func3);
func();

本部分摘自:http://www.javashuo.com/article/p-wzevjtae-dm.html    http://www.javashuo.com/article/p-ovztdblg-gm.html

二十4、iframe的缺點有哪些

iframe的優勢:
1.iframe可以原封不動的把嵌入的網頁展示出來。
2.若是有多個網頁引用iframe,那麼你只須要修改iframe的內容,就能夠實現調用的每個頁面內容的更改,方便快捷。
3.網頁若是爲了統一風格,頭部和版本都是同樣的,就能夠寫成一個頁面,用iframe來嵌套,能夠增長代碼的可重用。
4.若是遇到加載緩慢的第三方內容如圖標和廣告,這些問題能夠由iframe來解決。
iframe的缺點:
1.會產生不少頁面, 不容易管理
2.iframe框架結構有時會讓人感到迷惑,若是框架個數多的話,可能會出現上下、左右滾動條,會分散訪問者的注意力, 用戶體驗度差
3.代碼複雜,沒法被一些搜索引擎索引到,這一點很關鍵,如今的搜索引擎爬蟲還不能很好的處理iframe中的內容,因此使用iframe會 不利於搜索引擎優化
4.不少的移動設備(PDA手機)沒法徹底顯示框架, 設備兼容性差。
5.iframe框架頁面會 增長服務器的http請求,對於大型網站是不可取的。
分析了這麼多, 如今基本上都是用Ajax來代替iframe,因此iframe已經漸漸的退出了前端開發

二十5、數組問題

1. 數組操做

shift:刪除原數組第一項,並返回刪除元素的值;若是數組爲空則返回undefined

unshift:將參數添加到原數組開頭,並返回數組的長度

pop:刪除原數組最後一項,並返回刪除元素的值;若是數組爲空則返回undefined

push:將參數添加到原數組末尾,並返回數組的長度 

concat:返回一個新數組,是將參數添加到原數組中構成的

splice(start,deleteCount,val1,val2,...):從start位置開始刪除deleteCount項,並從該位置起插入val1,val2,..會改變原數組

reverse:將數組反序

sort(orderfunction):按指定的參數對數組進行排序

slice(start,end):返回從原數組中指定開始下標到結束下標之間的項組成的新數組

join(separator):將數組的元素組起一個字符串,以separator爲分隔符,省略的話則用默認用逗號爲分隔符

var a = [1,2,3,4,5];   
var b = a.shift(); //a:[2,3,4,5] b:1  

var a = [1,2,3,4,5];   
var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5] b:7 

var a = [1,2,3,4,5];   
var b = a.pop(); //a:[1,2,3,4] b:5

var a = [1,2,3,4,5];   
var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7

var a = [1,2,3,4,5];   
var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7]

var a = [1,2,3,4,5];   
var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4]   
var b = a.splice(0,1); //同shift   
a.splice(0,0,-2,-1); var b = a.length; //同unshift   
var b = a.splice(a.length-1,1); //同pop   
a.splice(a.length,0,6,7); var b = a.length; //同push 

var a = [1,2,3,4,5];   
var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1]

var a = [1,2,3,4,5];   
var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5] 

var a = [1,2,3,4,5];   
var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5]

var a = [1,2,3,4,5];   
var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5" 

2. 數組遍歷

for循環

使用臨時變量,將長度緩存起來,避免重複獲取數組長度,當數組較大時優化效果纔會比較明顯。

foreach循環

遍歷數組中的每一項,沒有返回值,對原數組沒有影響,不支持IE

//1 沒有返回值
arr.forEach((item,index,array)=>{
    //執行代碼
})
//參數:value數組中的當前項, index當前項的索引, array原始數組;
//數組中有幾項,那麼傳遞進去的匿名回調函數就須要執行幾回;
//基本用法
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.forEach(function(value, index, _ary) {
    console.log(index + ": " + value);
    return false;
});

使用some或every能夠中斷循環

//使用some函數
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.some(function (value, index, _ary) {
    console.log(index + ": " + value);
    return value === "CoffeeScript";
});
// logs:
//0: JavaScript 
//1: Java 
//2: CoffeeScript
//使用every
var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"];
ary.every(function(value, index, _ary) {
    console.log(index + ": " + value);
    return value.indexOf("Script") > -1;
});
// logs:
//0: JavaScript 
//1: Java

補充:如何中斷foreach循環呢?

//循環外使用try.. catch,當須要中斷時throw 一個異常,而後catch進行捕獲;
var BreakException = {};
try {
  [1, 2, 3].forEach(function(el) {
    console.log(el);
    if (el === 2) throw BreakException;
  });
} catch (e) {
  if (e !== BreakException) throw e;
}

map循環

有返回值,能夠return出來

map的回調函數中支持return返回值;return的是啥,至關於把數組中的這一項變爲啥(並不影響原來的數組,只是至關於把原數組克隆一份,把克隆的這一份的數組中的對應項改變了);

var ary = [12,23,24,42,1]; 
var res = ary.map(function (item,index,ary ) { 
    return item*10; 
}) 
console.log(res);//-->[120,230,240,420,10];  原數組拷貝了一份,並進行了修改
console.log(ary);//-->[12,23,24,42,1];  原數組並未發生變化

for of遍歷

能夠正確響應break、continue和return語句

for (var value of myArray) {
  console.log(value);
}
//基本用法
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let el of arr) {
  console.log(el);
  if (el === 5) {
    break;
  }
}

filter遍歷

不會改變原始數組,返回新數組

var arr = [
  { id: 1, text: 'aa', done: true },
  { id: 2, text: 'bb', done: false }
]
console.log(arr.filter(item => item.done))
//es5寫法
arr.filter(function (item) {
  return item.done;
});
var arr = [73,84,56, 22,100]
var newArr = arr.filter(item => item>80)   //獲得新數組 [84, 100]
console.log(newArr,arr)

reduce

reduce() 方法接收一個函數做爲累加器(accumulator),數組中的每一個值(從左到右)開始縮減,最終爲一個值。

var total = [0,1,2,3,4].reduce((a, b)=>a + b); //10
//reduce接受一個函數,函數有四個參數,分別是:上一次的值,當前值,當前值的索引,數組
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
 return previousValue + currentValue;
});
//reduce還有第二個參數,咱們能夠把這個參數做爲第一次調用callback時的第一個參數,上面這個例子由於沒有第二個參數,因此直接從數組的第二項開始,若是咱們給了第二個參數爲5,那麼結果就是這樣的:
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
 return previousValue + currentValue;
},5);

find

find()方法返回數組中符合測試函數條件的第一個元素。不然返回undefined 

stu.find((element) => (element.name == '李四'))
//es5
function getStu(element){
   return element.name == '李四'
}
stu.find(getStu)

keys,values,entries

ES6 提供三個新的方法 —— entries(),keys()和values() —— 用於遍歷數組。它們都返回一個遍歷器對象,能夠用for...of循環進行遍歷,惟一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷

for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

本部分參考連接:http://www.javashuo.com/article/p-qezqemuj-ek.html   http://www.javashuo.com/article/p-erqmascr-ee.html

對象遍歷方法

for...in...

Object.keys(obj)

Object.values(obj)

Object.getOwnPropertyNames(obj)

const obj = {
      id:1,
      name:'zhangsan',
      age:18
}

for(let key  in obj){
      console.log(key + '---' + obj[key])
}

console.log(Object.keys(obj))
console.log(Object.values(obj))

Object.getOwnPropertyNames(obj).forEach(function(key){
      console.log(key+ '---'+obj[key])
})

二十6、BOM屬性對象方法

 

二十7、服務端渲染

 

二十8、垃圾回收機制

什麼是垃圾:通常來講沒有被引用的對象就是垃圾,就是要被清除, 有個例外若是幾個對象引用造成一個環,互相引用,但根訪問不到它們,這幾個對象也是垃圾,也要被清除。

JavaScript 中的內存管理是自動執行的,並且是不可見的。咱們建立基本類型、對象、函數……全部這些都須要內存。

JavaScript 中內存管理的主要概念是可達性。

簡單地說,「可達性」 值就是那些以某種方式可訪問或可用的值,它們被保證存儲在內存中。

1. 有一組基本的固有可達值,因爲顯而易見的緣由沒法刪除。例如:

  • 本地函數的局部變量和參數
  • 當前嵌套調用鏈上的其餘函數的變量和參數
  • 全局變量
  • 還有一些其餘的,內部的

這些值稱爲根。

2. 若是引用或引用鏈能夠從根訪問任何其餘值,則認爲該值是可訪問的。

例如,若是局部變量中有對象,而且該對象具備引用另外一個對象的屬性,則該對象被視爲可達性, 它引用的那些也是能夠訪問的,詳細的例子以下。

JavaScript 引擎中有一個後臺進程稱爲垃圾回收器,它監視全部對象,並刪除那些不可訪問的對象。

// user 具備對象的引用
let user = {
  name: "John"
};
user = null; //此時,user 的值被覆蓋,則引用丟失
//如今 John 變成不可達的狀態,沒有辦法訪問它,沒有對它的引用。垃圾回收器將丟棄 John 數據並釋放內存。

內存生命週期

JS環境中分配的內存通常有以下生命週期:

  • 內存分配:當咱們申明變量、函數、對象,並執行的時候,系統會自動爲他們分配內存
  • 內存使用:即讀寫內存,也就是使用變量、函數等
  • 內存回收:使用完畢,由垃圾回收機制自動回收再也不使用的內存

基本的垃圾回收算法稱爲「標記-清除」,按期執行如下「垃圾回收」步驟:

  • 垃圾回收器獲取根並「標記」(記住)它們。
  • 而後它訪問並「標記」全部來自它們的引用。
  • 而後它訪問標記的對象並標記它們的引用。全部被訪問的對象都被記住,以便之後再也不訪問同一個對象兩次。
  • 以此類推,直到有未訪問的引用(能夠從根訪問)爲止。
  • 除標記的對象外,全部對象都被刪除。

一些優化:

  • 分代回收——對象分爲兩組:「新對象」和「舊對象」。許多對象出現,完成它們的工做並迅速結 ,它們很快就會被清理乾淨。那些活得足夠久的對象,會變「老」,而且不多接受檢查。
  • 增量回收——若是有不少對象,而且咱們試圖一次遍歷並標記整個對象集,那麼可能會花費一些時間,並在執行中會有必定的延遲。所以,引擎試圖將垃圾回收分解爲多個部分。而後,各個部分分別執行。這須要額外的標記來跟蹤變化,這樣有不少微小的延遲,而不是很大的延遲。
  • 空閒時間收集——垃圾回收器只在 CPU 空閒時運行,以減小對執行的可能影響。

本部分摘自:https://segmentfault.com/a/1190000018605776?utm_source=tag-newest

二十9、eventloop

 Javascript的事件分爲同步任務和異步任務.

 遇到同步任務就放在執行棧中執行.

 遇到異步任務就放到任務隊列之中,等到執行棧執行完畢以後再去執行任務隊列之中的事件。

JS調用棧

Javascript 有一個 主線程(main thread)和 調用棧(call-stack),全部的代碼都要經過函數,放到調用棧(也被稱爲執行棧)中的任務等待主線程執行。

JS調用棧採用的是後進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成後,就會從棧頂移出,直到棧內被清空。

WebAPIs

MDN的解釋: Web 提供了各類各樣的 API 來完成各類的任務。這些 API 能夠用 JavaScript 來訪問,令你能夠作不少事兒,小到對任意 window 或者 element作小幅調整,大到使用諸如 WebGL 和 Web Audio 的 API 來生成複雜的圖形和音效。

Web API 接口參考

總結: 就是瀏覽器提供一些接口,讓JavaScript能夠調用,這樣就能夠把任務甩給瀏覽器了,這樣就能夠實現異步了!

任務隊列(Task Queue)

"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。可是,若是存在"定時器",主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。

同步任務和異步任務

Javascript單線程任務被分爲同步任務和異步任務.

  • 同步任務會在調用棧 中按照順序等待主線程依次執行.
  • 異步任務會甩給在WebAPIs處理,處理完後有告終果後,將註冊的回調函數放入任務隊列中等待主線程空閒的時候(調用棧被清空),被讀取到棧內等待主線程的執行。

宏任務(MacroTask)和 微任務(MicroTask):在JavaScript中,任務被分爲兩種,一種宏任務(MacroTask)也叫Task,一種叫微任務(MicroTask)。

宏任務(MacroTask):script(總體代碼)setTimeoutsetIntervalsetImmediate(瀏覽器暫時不支持,只有IE10支持,具體可見MDN)、I/OUI Rendering

微任務(MicroTask):Process.nextTick(Node獨有)PromiseObject.observe(廢棄)MutationObserver(具體使用方式查看這裏

注意:只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
//script start
//async2 end
//Promise
//script end
//async1 end
//promise1
//promise2
//undefined
//setTimeout
  • 首先,打印script start,調用async1()時,返回一個Promise,因此打印出來async2 end
  • 每一個 await,會新產生一個promise,但這個過程自己是異步的,因此該await後面不會當即調用。
  • 繼續執行同步代碼,打印Promisescript end,將then函數放入微任務隊列中等待執行。
  • 同步執行完成以後,檢查微任務隊列是否爲null,而後按照先入先出規則,依次執行。
  • 而後先執行打印promise1,此時then的回調函數返回undefinde,此時又有then的鏈式調用,又放入微任務隊列中,再次打印promise2
  • 再回到await的位置執行返回的 Promiseresolve 函數,這又會把 resolve 丟到微任務隊列中,打印async1 end
  • 微任務隊列爲空時,執行宏任務,打印setTimeout

瀏覽器執行過程

執行完主執行線程中的任務。
取出Microtask Queue中任務執行直到清空。
取出Macrotask Queue中一個任務執行。
取出Microtask Queue中任務執行直到清空。
重複3和4。

即爲同步完成,一個宏任務,全部微任務,一個宏任務,全部微任務......

console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
//結果
//script start
//script end
//promise1
//promise2
//setTimeout

解析:一開始task隊列中只有script,則script中全部函數放入函數執行棧執行,代碼按順序執行。
接着遇到了setTimeout,它的做用是0ms後將回調函數放入task隊列中,也就是說這個函數將在下一個事件循環中執行(注意這時候setTimeout執行完畢就返回了)。
接着遇到了Promise,按照前面所述Promise屬於microtask,因此第一個.then()會放入microtask隊列。
當全部script代碼執行完畢後,此時函數執行棧爲空。開始檢查microtask隊列,此時隊列不爲空,執行.then()的回調函數輸出'promise1',因爲.then()返回的依然是promise,因此第二個.then()會放入microtask隊列繼續執行,輸出'promise2'。
此時microtask隊列爲空了,進入下一個事件循環,檢查task隊列發現了setTimeout的回調函數,當即執行回調函數輸出'setTimeout',代碼執行完畢。

以上即是瀏覽器事件循環的過程

Node.js執行過程

事件循環能讓 Node.js 執行非阻塞 I/O 操做,儘管JavaScript事實上是單線程的,經過在可能的狀況下把操做交給操做系統內核來實現。

因爲大多數現代系統內核是多線程的,內核能夠處理後臺執行的多個操做。當其中一個操做完成的時候,內核告訴 Node.js,相應的回調就被添加到輪詢隊列(poll queue)並最終獲得執行。

在node中事件每一輪循環按照順序分爲6個階段,來自libuv的實現:

  • timers 階段: 這個階段執行setTimeout(callback) and setInterval(callback)預約的callback;
  • I/O callbacks 階段:  是否有已完成的I/O操做的回調函數,來自上一輪的poll殘留;
  • idle, prepare 階段: 僅node內部使用;
  • poll 階段: 獲取新的I/O事件, 適當的條件下node將阻塞在這裏;
  • check 階段: 執行setImmediate() 設定的callbacks;
  • close callbacks 階段: 好比socket.on(‘close’, callback)的callback會在這個階段執行.

每個階段都有一個裝有callbacks的fifo queue(隊列),當event loop運行到一個指定階段時,node將執行該階段的fifo queue(隊列),當隊列callback執行完或者執行callbacks數量超過該階段的上限時,event loop會轉入下一下階段.

上面六個階段都不包括 process.nextTick(),process.nextTick不是基於libuv事件機制的,而timers一系列的api所有是基於libuv開放出來的api實現的。

定時器(timers)

定時器的用途是讓指定的回調函數在某個閾值後會被執行,具體的執行時間並不必定是那個精確的閾值。定時器的回調會在制定的時間事後儘快獲得執行,然而,操做系統的計劃或者其餘回調的執行可能會延遲該回調的執行。

輪詢(poll)

輪詢階段有兩個主要功能:
1,執行已經到時的定時器腳本
2,處理輪詢隊列中的事件

當事件循環進入到輪詢階段卻沒有發現定時器時:
若是輪詢隊列非空,事件循環會迭代回調隊列並同步執行回調,直到隊列空了或者達到了上限(前文說過的根據操做系統的不一樣而設定的上限)。
若是輪詢隊列是空的:
若是有setImmediate()定義了回調,那麼事件循環會終止輪詢階段並進入檢查階段去執行定時器回調;
若是沒有setImmediate(),事件回調會等待回調被加入隊列並當即執行。
一旦輪詢隊列空了,事件循環會查找已經到時的定時器。若是找到了,事件循環就回到定時器階段去執行回調。

I/O callbacks

這個階段執行一些諸如TCP錯誤之類的系統操做的回調。例如,若是一個TCP socket 在嘗試鏈接時收到了 ECONNREFUSED錯誤,某些 *nix 系統會等着報告這個錯誤。這個就會被排到本階段的隊列中。

檢查(check)

這個階段容許回調函數在輪詢階段完成後當即執行。若是輪詢階段空閒了,而且有回調已經被 setImmediate() 加入隊列,事件循環會進入檢查階段而不是在輪詢階段等待。
setImmediate() 是個特殊的定時器,在事件循環中一個單獨的階段運行。它使用libuv的API 來使得回調函數在輪詢階段完成後執行。

關閉事件的回調(close callbacks)

若是一個 socket 或句柄(handle)被忽然關閉(is closed abruptly),例如 socket.destroy(), 'close' 事件會被髮出到這個階段。不然這種事件會經過 process.nextTick() 被髮出。

setTimeout VS setImmediate

兩者很是類似,可是兩者區別取決於他們何時被調用.

setImmediate 設計在poll階段完成時執行,即check階段;
setTimeout 設計在poll階段爲空閒時,且設定時間到達後執行;但其在timer階段執行
其兩者的調用順序取決於當前event loop的上下文,若是他們在異步i/o callback以外調用,其執行前後順序是不肯定的

setTimeout(function timeout () {
  console.log('timeout');
},0);
setImmediate(function immediate () {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout

爲何結果不肯定呢?

解釋:setTimeout/setInterval 的第二個參數取值範圍是:[1, 2^31 - 1],若是超過這個範圍則會初始化爲 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。咱們知道 setTimeout 的回調函數在 timer 階段執行,setImmediate 的回調函數在 check 階段執行,event loop 的開始會先檢查 timer 階段,可是在開始以前到 timer 階段會消耗必定時間,因此就會出現兩種狀況:

timer 前的準備時間超過 1ms,知足 loop->time >= 1,則執行 timer 階段(setTimeout)的回調函數
timer 前的準備時間小於 1ms,則先執行 check 階段(setImmediate)的回調函數,下一次 event loop 執行 timer 階段(setTimeout)的回調函數
再看個例子:

setTimeout(() => {
  console.log('setTimeout')
}, 0)
setImmediate(() => {
  console.log('setImmediate')
})
const start = Date.now()
while (Date.now() - start < 10);
//結果
//setTimeout
//setImmediate

本部分摘自:http://www.javashuo.com/article/p-xacejgab-kn.html          淺析Nodejs Event Loop

三10、如何快速讓字符串變成以千爲精度的數字

toLocalString()

三11、正則表達式

 \ 作爲轉意,即一般在"\"後面的字符不按原來意義解釋,如/b/匹配字符"b",當b前面加了反斜杆後/\b/,轉意爲匹配一個單詞的邊界。 
-或- 
對正則表達式功能字符的還原,如"*"匹配它前面元字符0次或屢次,/a*/將匹配a,aa,aaa,加了"\"後,/a\*/將只匹配"a*"。 
^ 匹配一個輸入或一行的開頭,/^a/匹配"an A",而不匹配"An a" 
$ 匹配一個輸入或一行的結尾,/a$/匹配"An a",而不匹配"an A" 
* 匹配前面元字符0次或屢次,/ba*/將匹配b,ba,baa,baaa 
+ 匹配前面元字符1次或屢次,/ba*/將匹配ba,baa,baaa 
? 匹配前面元字符0次或1次,/ba*/將匹配b,ba 
(x) 匹配x保存x在名爲$1...$9的變量中 
x|y 匹配x或y 
{n} 精確匹配n次 
{n,} 匹配n次以上 
{n,m} 匹配n-m次 
[xyz] 字符集(character set),匹配這個集合中的任一一個字符(或元字符) 
[^xyz] 不匹配這個集合中的任何一個字符 
[\b] 匹配一個退格符 
\b 匹配一個單詞的邊界 
\B 匹配一個單詞的非邊界 
\cX 這兒,X是一個控制符,/\cM/匹配Ctrl-M 
\d 匹配一個字數字符,/\d/ = /[0-9]/ 
\D 匹配一個非字數字符,/\D/ = /[^0-9]/ 
\n 匹配一個換行符 
\r 匹配一個回車符 
\s 匹配一個空白字符,包括\n,\r,\f,\t,\v等 
\S 匹配一個非空白字符,等於/[^\n\f\r\t\v]/ 
\t 匹配一個製表符 
\v 匹配一個重直製表符 
\w 匹配一個能夠組成單詞的字符(alphanumeric,這是個人意譯,含數字),包括下劃線,如[\w]匹配"$5.98"中的5,等於[a-zA-Z0-9] 
\W 匹配一個不能夠組成單詞的字符,如[\W]匹配"$5.98"中的$,等於[^a-zA-Z0-9]。

var pattern = /s$/; //正則表達式直接量
var pattern = new RegExp("s$"); //構造函數RegExp()

匹配Email地址的正則表達式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*

評註:表單驗證時很實用

匹配網址URL的正則表達式:[a-zA-z]+://[^s]*

/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/

評註:網上流傳的版本功能頗有限,上面這個基本能夠知足需求

匹配賬號是否合法(字母開頭,容許5-16字節,容許字母數字下劃線):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

評註:表單驗證時很實用

匹配國內電話號碼:d{3}-d{8}|d{4}-d{7}

評註:匹配形式如 0511-4405222 或 021-87888822

匹配騰訊QQ號:[1-9][0-9]{4,}

評註:騰訊QQ號從10000開始

匹配中國郵政編碼:[1-9]d{5}(?!d)

評註:中國郵政編碼爲6位數字

匹配身份證:d{15}|d{18}

評註:中國的身份證爲15位或18位

手機號:/^1[3|4|5|8][0-9]\d{4,8}$/

^1表明以1開頭,如今中國的手機號沒有是其它開頭的,之後不必定啊 
[3|4|5|8] 緊跟上面的1後面,能夠是3或4或5或8的一個數字,若是之後出現190開始的手機號碼了,就須要以下[3|4|5|8|9] 
[0-9]表示0-9中間的任何數字,能夠是0或9 
\d{4,8} 這個\d跟[0-9]意思同樣,都是0-9中間的數字。{4,8}表示匹配前面的最低4位數字最高8位數字。這裏爲何不是直接的8呢,由於手機號碼歸屬地查詢的時候,根據前7位就能夠知道具體的地址了,後面的4位沒有影響的。

三12、toString()、toLocaleString()、valueOf()

Array、Boolean、Date、Number等對象都具備toString()、toLocaleString()、valueOf()三個方法

1. JS Array

var array = new Array("niu","li","na");
console.log(array.valueOf());//Array【3】
console.log(array.toString());//niu,li,na
console.log(array.toLocaleString());//niu,li,na
  • valueOf:返回數組自己

  • toString():把數組轉換爲字符串,並返回結果,每一項以逗號分割。

  • toLocalString():把數組轉換爲本地數組,並返回結果。

2. JS Boolean

var boolean = new Boolean();
console.log(boolean.valueOf());//false
console.log(boolean.toString());//false
  • valueOf:返回 Boolean 對象的原始值。

  • toString():根據原始布爾值或者 booleanObject 對象的值返回字符串 "true" 或 "false"。默認爲"false"。

  • toLocalString():Boolean對象沒有toLocalString()方法。可是在Boolean對象上使用這個方法也不會報錯。

3. JS Date

var date = new Date();
console.log(date.valueOf());
console.log(date.toString());
console.log(date.toLocaleString());

  • valueOf:返回 Date 對象的原始值,以毫秒錶示。

  • toString():把 Date 對象轉換爲字符串,並返回結果。使用本地時間表示。

  • toLocalString():可根據本地時間把 Date 對象轉換爲字符串,並返回結果,返回的字符串根據本地規則格式化。

4. JS Math

console.log(Math.PI.valueOf());//3.141592653589793
  • valueOf:返回 Math 對象的原始值。 

5. JS Number

var num = new Number(1337);
console.log(num.valueOf());//1337
console.log(num.toString());//1337
console.log(num.toLocaleString());//1,337
  • valueOf:返回一個 Number 對象的基本數字值。

  • toString():把數字轉換爲字符串,使用指定的基數。

  • toLocalString():把數字轉換爲字符串,使用本地數字格式順序。

6. JS String

var string = new String("abc");
console.log(string.valueOf());//abc
console.log(string.toString());//abc
  • valueOf:返回某個字符串對象的原始值。

  • toString():返回字符串。

toString()方法與toLocalString()方法區別:

  • toLocalString()是調用每一個數組元素的 toLocaleString() 方法,而後使用地區特定的分隔符把生成的字符串鏈接起來,造成一個字符串。

  • toString()方法獲取的是String(傳統字符串),而toLocaleString()方法獲取的是LocaleString(本地環境字符串)。

  • 若是你開發的腳本在世界範圍都有人使用,那麼將對象轉換成字符串時請使用toString()方法來完成。

  • LocaleString()會根據你機器的本地環境來返回字符串,它和toString()返回的值在不一樣的本地環境下使用的符號會有微妙的變化。

  • 因此使用toString()是保險的,返回惟一值的方法,它不會由於本地環境的改變而發生變化。若是是爲了返回時間類型的數據,推薦使用LocaleString()。如果在後臺處理字符串,請務必使用toString()。

本部分摘自:http://www.javashuo.com/article/p-fthvgxvs-cs.html

未完待續·······

相關文章
相關標籤/搜索