JavaScript面試題整理

😂這只是我的筆記……我沒想到竟然有人看到……javascript

題目來源: 前端開發面試題html

答案基本是本身整理的。並非所有題目都有。前端

會有不少本身的囉嗦,還有不少亂七八糟的補充,請見諒。java

介紹js的基本數據類型(原始數據類型)

Undefined、Null
 BooleanNumberString
 
 //ECMAScript 2015新增:
 Symbol //建立後獨一無二且不可變的數據類型
複製代碼

Symbol

參考:git

  1. ES6入門之Symbol
  2. Symbol

如下多引自:MDNgithub

Symbol 實例是惟一且不可改變的。web

Symbol 對象是 Symbol原始值的封裝。面試

Symbol 的描述是可選的,但僅用於調試目的ajax

Symbol("foo") !== Symbol("foo")
const foo = Symbol()
const bar = Symbol()
typeof foo === "symbol"
typeof bar === "symbol"
複製代碼
  • 爲何要引入symbol?

ES5 的對象屬性名都是字符串,這容易形成屬性名的衝突正則表達式

好比,你使用了一個他人提供的對象,但又想爲這個對象添加新的方法(mixin 模式),新方法的名字就有可能與現有方法產生衝突。

若是有一種機制,保證每一個屬性的名字都是獨一無二的就行了,這樣就從根本上防止屬性名的衝突。

ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值

  • 生成方式:

Symbol 值經過Symbol函數生成。

let s = Symbol()
typeof s // "symbol"
複製代碼

Symbol函數前不能使用new命令,不然會報錯。這是由於生成的Symbol是一個原始類型的值,不是對象

  • 參數

description: 可選。string。對Symbol實例的描述,僅用於調試。

  • 遍歷

ES6以前的方法,沒法獲取。

const foo = Symbol()
const bar = Symbol()

let obj = {}
obj[foo] = "foo"
obj[bar] = "bar"

JSON.stringify(obj) // {}
Object.keys(obj) // []
Object.getOwnPropertyNames(obj) // []
Object.getOwnPropertySymbols(obj) // [ Symbol(), Symbol() ]
Reflect.ownKeys(obj) // [Symbol(), Symbol()]
複製代碼

js有哪些內置對象?

劃分一

↪ MDN reference

基本對象:Object、Function、Boolean、Symbol、Error

數字和日期對象:Number、Math、Date

字符串:String、RegExp

集合對象:Array、Map、Set

結構化數據:JSON

控制抽象對象:Promise

其餘:arguments

劃分二

參考:瞭解Javascript裏的內置對象

Object 是 JavaScript 中全部對象的父對象。

數據封裝類對象:Object、Array、Boolean、Number 和 String

單例內置對象:Math、Global

其餘對象:Function、Arguments、Date、RegExp、Error

說幾條寫JavaScript的基本規範?

  1. 不要在同一行聲明多個變量。

  2. 使用 ===/!==來比較true/false或者數值

    1. 對於原始數據類型,==和===是有區別的
      • 不一樣類型間比較,==只比較「轉化成同一類型後的值」看「值」是否相等,===若是類型不一樣,其結果就是不等
      • 同類型比較,直接進行「值」比較,二者結果同樣
    2. 對於Object,==和===沒有區別, 進行「指針地址」比較
    3. 基礎類型與Object,==和===有區別
      • ==將Object轉化爲基礎類型,進行「值」比較
      • 由於類型不一樣,===結果爲false
  3. 使用對象字面量替代new Array這種形式

  4. 不要使用全局函數

  5. Switch語句必須帶有default分支

  6. 函數不該該有時候有返回值,有時候沒有返回值

  7. For循環必須使用大括號

  8. If語句必須使用大括號

  9. for-in循環中的變量應該使用var關鍵字明確限定做用域,從而避免做用域污染。

JavaScript原型,原型鏈 ? 有什麼特色?

原型

ecma 262:

All ordinary objects have an internal slot called [[Prototype]].

The value of this internal slot is either null or an object and ++is used for implementing inheritance++.

Data properties of the [[Prototype]] object are inherited (are visible as properties of the child object) for the purposes of get access, but not for set access. Accessor properties are inherited for both get access and set access.

每一個對象有一個私有屬性,記做[[prototype]]

本質:一個連接到其餘對象的引用

做用:用於繼承。

被連接的對象,是該對象的原型對象

原型鏈

而原型對象又會有本身的原型對象,這就造成了原型鏈

原型鏈的頂端Object.prototype.(內置了不少功能和方法,包括.toString(), .valueOf(), .hasOwnProperty(), .isPrototypeOf()等等。)

當咱們訪問一個對象的屬性a時,若是這個對象內部不存在a這個屬性,那麼就會去原型對象裏找a這個屬性。若是原型對象裏還找不到,則會沿着原型鏈一路向上,直到找到這個a爲止;若到達原型鏈頂端,仍未找到,則中止並返回undefined。

疑問:Object.prototype VS null, 哪一個纔是原型鏈的末端?

MDN上提到:

By definition, null has no prototype, and acts as the final link in this prototype chain.

You Don't Know JS 又說,Object.prototype是原型鏈的頂端。

參考:Stack-Overflow

這兩種其實本質是同樣的,只是看的方式不一樣。

就像俄羅斯套娃,最後一個的內部是空的,因此能夠判定它是「最後一個」。若是用這種眼光去看,那麼Object.prototype就是頂端。

而若是把null也當成是一個節點,那麼「空」纔是原型鏈的頂端。

但也有人提出:

ECMA262這麼寫道:

The value of the [[Prototype]] internal slot of the Object prototype object is null.

我不是很懂,由於這個初始值是null,因此就天然成爲原型鏈的頂端了嗎

function的prototype屬性

參考:MDN(中文版)

  • 關係:

    instance.constructor.prototype === instance.__proto__

    這個關係不必定正確!!!

    好比:

    let a = Object.create(null)
    a.constructor //undefined
    let b = Object.create(a)
    b.constructor //undefined
    複製代碼

    Object.create的對象,原型鏈上不必定有Object.prototype,因此也沒有默認的.constructor.__proto__等等。

  • 是否全部函數都有.prototype屬性

    ECMA262:

    Function instances that ++can be used as a constructor++ have a prototype property. Whenever such a function instance is created another ordinary object is also created and is the initial value of the function’s prototype property. Unless otherwise specified, the value of the prototype property is used to initialize the [[Prototype]] internal slot of the object created when that function is invoked as a constructor.

    This property has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }.

    NOTE:

    //下面寫了沒有這個屬性的三種狀況

    Function objects created using Function.prototype.bind, or by evaluating a MethodDefinition (that are not a GeneratorMethod) or an ArrowFunction grammar production do not have a prototype property.

    能夠看成構造函數的function都有一個屬性prototype,這個屬性默認指向一個JS私有對象%FunctionPrototype%

  • .prototype屬性和[[prototype]]的關係

  • 也是

  • 改變[[prototype]]的兩種方法

    ecma 262:

    When a constructor creates an object, that object implicitly references the constructor’s prototype property for the purpose of resolving property references. The constructor’s prototype property can be referenced by the program expression constructor.prototype, and properties added to an object’s prototype are shared, through inheritance, by all objects sharing the prototype.

    Alternatively, a new object may be created with an explicitly specified prototype by using the Object.create built-in function.

    一、使用構造函數建立obj,會隱式地引用構造函數的prototype屬性,做爲obj[[prototype]]

    (能夠這麼理解:obj.[[prototype]] = objConstructor.prototype

    二、Object.create(O)可建立一個obj,並指定它的[[prototype]]O

    • 另外兩種改變[[prototype]]的方法:詳見MDN
      1. ES6: Object.setPrototypeOf
      2. proto(不推薦使用這個屬性)

特色:

JavaScript對象是經過引用來傳遞的,咱們建立的每一個新對象實體中並無一份屬於本身的原型副本。當咱們修改原型時,與之相關的對象也會繼承這一改變。

當咱們須要一個屬性的時,Javascript引擎會先看當前對象中是否有這個屬性, 若是沒有的話, 就會查找他的Prototype對象是否有這個屬性,如此遞推下去,一直檢索到 Object 內建對象。

function Func() {}
Func.prototype.name = "Sean";
Func.prototype.getInfo = function() { return this.name; }

// pre ES5.1
var person = new Func();
//ES5.1
// var person = Object.create(Func.prototype);
//ES6+ 
// Object.setPrototypeOf(person, Func.prototype);

//它擁有了Func的屬性和方法
console.log(person.getInfo()); //"Sean"
console.log(Func.prototype); // Func { name="Sean", getInfo=function()}
複製代碼

JavaScript有幾種類型的值?你能畫一下他們的內存圖嗎?

  • 原始數據類型(Undefined,Null,Boolean,Number、String)
  • 引用數據類型(對象、數組和函數)

兩種類型的區別:存儲位置不一樣。

區別 原始數據類型 引用數據類型
存儲位置 棧(stack) 堆(heap)
佔據空間 小,大小固定 大,大小不固定

引用數據類型若是存儲在棧中,將會影響程序運行的性能;

引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體。

Stated Clearly Image

如何將字符串轉化爲數字,例如'12.3b'?

1. 轉換函數

// radix = 0, 2, 3, ..., 36
// radix表示的是string的進制,而非轉換出來的結果的進制
parseInt(string, radix)
parseFloat(value)
複製代碼

都爲全局函數,不屬於任何對象。(但Number裏有同名函數)

能轉換的數據類型:

  1. String
  2. 定義了 toString 或者 valueOf 方法的對象

Examples:

parseInt('36', 36) // 114
parseInt('36', 37) // NaN
parseInt('36', 1) // NaN
parseInt('36', 0) // 36。radix爲0或null,則默認爲十進制

let obj = {}
obj.toString = function () { return '3'; }
parseInt(obj) // 3
複製代碼

2. 強制類型轉換(type casting)

Number(value)
複製代碼

在非構造器上下文中 (如:沒有 new 操做符),Number 能被用來執行類型轉換。

3. 正則表達式 + 隱式轉換

'12.3b'.match(/(\d)+(\.)?(\d)+/g)[0] * 1
複製代碼

可是這個不太靠譜,提供一種思路而已。

如何將浮點數點左邊的數每三位添加一個逗號,如12000000.11轉化爲『12,000,000.11』?

方法1、toLocaleString

參考:

  1. Javascript Formatting numbers with commas,
  2. ↪ MDN
var number = 12345.543;
number.toLocaleString('en'); // "12,345.543"

number = 123.1;
number.toLocaleString('en'); // "123.1"

number = 12345.54321;
number.toLocaleString('en'); // "12,345.543"
複製代碼

侷限:只顯示三位小數,可經過maximumSignificantDigits這個參數調整,但老的瀏覽器不支持此參數。

方法2、正則表達式

參考:javascript 正則(將數字轉化爲三位分隔的樣式)

不易懂,但兼容性強。

寫法一:

// 匹配邊界,直接可替換成逗號
function commafy (num) {
    return num
          .toString()
          .replace(/\B(?=(?:\d{3})+\.)/g, ',')
}
複製代碼

寫法二:

// 匹配邊界前的那個數字,因此不能直接替換,
// 要保留數字,在數字後加一個逗號。

function commafy(num){
	return num && num
		.toString()
		.replace(/(\d)(?=(\d{3})+\.)/g, function($1, $2){
			return $2 + ',';
		});
}

// 也能夠這麼寫(不用function用string)
replace(/(\d)(?=(\d{3})+\.)/g, '$1,')
複製代碼

如何實現數組的隨機排序?

參考:stack overflow

方法一:Knuth-Fisher-Yates shuffle algorithm

The de-facto(事實上) unbiased(無偏的) shuffle algorithm.(參考

function shuffle(array) {
  var currentIndex = array.length,
    temporaryValue,
    randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

// Used like so
var arr = [2, 11, 37, 42];
arr = shuffle(arr);
console.log(arr);
複製代碼

!!!注意:The Danger of Naïveté

注意如下二者的區別!

// naïve algorithm
// i = 0, 1, ..., len-1 (循環len次)
// 每次rand.Next()的值不變,都爲cards.Length
for (int i = 0; i < cards.Length; i++) {
  int n = rand.Next(cards.Length);
  Swap(ref cards[i], ref cards[n]);
}

// correct Knuth-Fisher-Yates shuffle algorithm
// i = 1, 2, ..., len-1 (循環len-1次)
// 每次rand.Next()的值都不一樣,爲 i + 1 (即2, 3, ..., len)
for (int i = cards.Length - 1; i > 0; i--) {
  int n = rand.Next(i + 1);
  Swap(ref cards[i], ref cards[n]);
}
複製代碼

The naive shuffle results in 33 (27) possible deck combinations. That's odd, because the mathematics tell us that there are really only 3! or 6 possible combinations of a 3 card deck.

In the KFY shuffle, we start with an initial order, swap from the third position with any of the three cards, then swap again from the second position with the remaining two cards.

naive shuffle: 3次rand.Next(3),也就是說,每次都是 0, 1, 2三個數中,隨機選一個。也就是總的結果有==3^3=27==種。

KFY shuffle:只有是rand.Next(3)和是rand.Next(2),第一次是 0, 1, 2三個數中,隨機選一個,第二次是 0, 1兩個數中選一個。結果爲==3!=6==種。

KFY shuffle

本質:完美洗牌的算法問題。

這個問題須要剔除給定的array的初始順序的影響

不管怎麼洗,出現各類結果的機率必須是同樣的。

這樣其實問題就簡單:只要考慮,每一個index的位置,有多少種可能性就好了。

這就像是:餐桌四個座位,有幾種坐法?

其實就是array的排列問題。

方法二:push + 刪減

var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){
	var mixedArray = [];
	while(arr.length > 0){
		var randomIndex = parseInt(Math.random()*arr.length);
		mixedArray.push(arr[randomIndex]);
		// 刪除已push的項目
		arr.splice(randomIndex, 1);
	}
	return mixedArray;
}
console.log(randSort2(arr));
複製代碼

方法三:sort

Array.prototype.sort([compareFunction]): 根據所提供的排序算法,進行排序。

compareFunction(a, b)接受兩個參數,即兩個比較的item。

函數返回一個number:

  • 結果爲0則保持不變
  • 小於0,a在前,b在後
  • 大於0,a在後,b在前
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
	return Math.random() - 0.5;
})
console.log(arr);
複製代碼
  • 缺陷:

    參考:stack overflow

    這個方法產生的結果,並非均勻的。

    測試以下:

    function randSort(){
    	var sum = Array.apply(null, { length: 10 }).fill(0);
    	var AVG_TIMES = 1000;
    	var SHUFFLE_TIMES = 10000;
    	var avgTimes = AVG_TIMES;
    	while (avgTimes) {
    		var times = SHUFFLE_TIMES;
    		var counts = Array.apply(null, { length: 10 }).fill(0); // 計算index0裏各個數字的出現機率
    		while (times) {
    			var someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    			someArray.sort(() => Math.random() - 0.5);
    			counts[someArray[0]]++;
    			times--;
    		}
    		//console.log(counts);
    		sum = sum.map(function (v, i) {
    			return v + counts[i];
    		});
    		avgTimes--;
    	}
    	console.log(sum.map(function (v, i) {
    		return v / AVG_TIMES;
    	}));
    }
    複製代碼

    測試了shuffle後的array[0]的取值中,各個數字在10000次測試中的平均出現次數。

    1000 * 10000 次的平均結果是這樣:

    [1953.077, 725.763, 963.728, 1287.405, 829.722, 893.497, 990.991, 1115.342, 599.359, 641.116]
    複製代碼

    result

javascript建立對象的幾種方式?

參考:高程三 6.2

0、Object 構造函數或對象字面量

能夠用來建立單個對象。

但要建立不少對象,就會產生大量的重複代碼。

// 建立 Object 的實例
var person = new Object();
person.firstname = 'Captain';
person.lastname = 'Flag';
person.sayName = function sayName () {
    console.log(this.firstname + ' ' + this.lastname);
};

// 字面量
var person = {
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    }
};
複製代碼

一、工廠模式

工廠模式是軟件工程領域一種廣爲人知的設計模式,這種模式抽象了建立具體對象的過程(本書後 面還將討論其餘設計模式及其在 JavaScript 中的實現)。考慮到在 ECMAScript 中沒法建立類,開發人員 就發明了一種函數,用函數來封裝以特定接口建立對象的細節

function createPerson (firstname, lastname) {
    var o = new Object ();
    o.firstname = firstname;
    o.lastname = lastname;
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    return o;
}

var cap = createPerson('Captain', 'Flag');
複製代碼
  • 優勢:解決了建立多個類似對象的問題。
  • 缺點:沒有解決對象識別的問題(即怎樣知道一個對象的類型)。

二、構造函數模式

function Person (firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
    this.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
}

var cap = new Person('Captain', 'Flag');
複製代碼

new 操做符調用構造函數的過程:

  1. 建立一個新對象;
  2. 將新對象的[[protorype]]設置爲Person.prototype
  3. 構造函數Person被傳入參數並調用(執行構造函數中的代碼,爲這個新對象添加屬性),關鍵字this被設定指向新對象;
  4. 除非函數Person顯性返回一個對象,不然自動返回新對象。
  • 優勢:

    有解決對象識別的問題,能夠將實例標識爲一種特定的類型。

  • 缺點:

    沒法進行函數複用。(以這種方式建立函數,會致使不一樣的做用域鏈和標識符解析,但建立 Function 新實例的機制仍然是相同的。所以,不一樣實例上的同名函數是不相等的。但建立兩個完成一樣任務的 Function 實例的確沒有必要。)

    解決:把函數定義轉移到構造函數外部。

    function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    }
    function Person (firstname, lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.sayName = sayName;
    }
    複製代碼

    新的問題:破壞封裝。

三、原型模式

能夠看成構造函數的function都有一個屬性prototype,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法

function Person () {}
Person.prototype.firstname = 'Captain';
Person.prototype.lastname = 'Flag';
Person.prototype.sayName = function sayName () {
    console.log('Captain Flag');
};

var cap = new Person();

// 更簡單的原型語法
function Person () {}
Person.prototype = {
    // 這種寫法會將默認的Person.prototype徹底覆蓋重寫,
    // Person也不會有`constructor`屬性
    // `Person.prototype.hasOwnProperty('constructor')`的結果爲false
    // 若是仍須要`constructor`屬性,自行增長
    constructor: Person,
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log('Captain Flag');
    }
};

// 以上一種方式重設 constructor 屬性會致使它的[[Enumerable]]特性被設置爲 true
// 默認狀況下,原生的 constructor 屬性是不可枚舉的
// 兼容 ECMAScript 5 的 JavaScript 引擎上,可用 Object.defineProperty() 修復
function Person () {}
Person.prototype = {
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log('Captain Flag');
    }
};
    // 重設constructor屬性,只適用於 ECMAScript 5 兼容的瀏覽器
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
});
複製代碼

注:後二者寫法,都直接重寫了原型,若是有實例在原型重寫以前生成,這些實例與更改後的原型是沒有聯繫的。

  • 優勢:

    可讓全部對象實例共享原型對象所包含的屬性和方法(沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中)。

  • 缺點:

    1. 不能傳遞參數。
    2. 原型模式的最大問題是由其共享的本性所致使的。原型中全部屬性是被不少實例共享的。
      • 這種共享十分適用於函數
      • 對於原始數據類型尚可,
      • 引用類型在原型中也只是保留了一個指針,在實例中被修改後,會影響原型的值。
        function Person () {}
        Person.prototype.arr = [1,2,3];
        
        var cap = new Person();
        cap.arr.push('a');
        console.log(Person.prototype.arr); // [1, 2, 3, "a"]
        複製代碼

四、組合使用構造函數模式和原型模式

模式 所定義的內容
構造函數模式 實例屬性
原型模式 方法和共享的屬性

定義引用類型的一種默認模式。

function Person (firstname, lastname) {
    this.name = [firstname, lastname];
    
}
Person.prototype.sayName = function sayName () {
    console.log(this.name.join(' '));
};

var cap = new Person('Captain', 'Flag');
cap.name.push('1911');
cap.sayName(); // Captain Flag 1911
複製代碼
  • 優勢:

    1. 支持向構造函數傳遞參數。
    2. 每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存。

五、動態原型模式

function Person (firstname, lastname) {
    this.name = [firstname, lastname];
    // 只在 sayName() 方法不存在的狀況下,纔會將它添加到原型中。
    // 這段代碼只會在初次調用構造函數時纔會執行。
    // 此後,原型已經完成初始化,不須要再作什麼修改了。
    // 其中, if 語句檢查的,能夠是初始化以後應該存在的任何屬性或方法,
    // 而且沒必要檢查每一個屬性和每一個方法,只要檢查其中一個便可。
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function sayName () {
            console.log(this.name.join(' '));
        };
    }
}

var cap = new Person('Captain', 'Flag');
cap.name.push('1911');
cap.sayName(); // Captain Flag 1911
複製代碼
  • 優勢:

    有其餘 OO 語言經驗的開發人員在看到獨立的構造函數和原型時,極可能會感到很是困惑。動態原 型模式正是致力於解決這個問題的一個方案,它把全部信息都封裝在了構造函數中,而經過在構造函數 中初始化原型(僅在必要的狀況下),又保持了同時使用構造函數和原型的優勢。換句話說,能夠經過 檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。

    1. 對於採用這種模式建立的對象,還可使用 instanceof 操做符肯定它的類型

六、寄生構造函數模式

基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但從表面上看,這個函數又很像是典型的構造函數。

function Person (firstname, lastname) {
    var o = new Object ();
    o.firstname = firstname;
    o.lastname = lastname;
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    return o;
}

var cap = new Person('Captain', 'Flag');
複製代碼

工廠模式 + 構造函數(除了使用 new 操做符並把使用的包裝函數叫作構造函數以外,這個模式跟工廠模式實際上是如出一轍的。)

構造函數在不返回值的狀況下,默認會返回新對象實例。而經過在構造函數的末尾添加一個 return 語句,能夠重寫調用構造函數時返回的值。

  • 優勢:

    在特殊的狀況下用來爲對象建立構造函數。

    好比:建立一個具備額外方法的特殊數組。因爲不能直接修改 Array 構造函數,所以可使用這個模式。

  • 缺點:

    返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係。不能依賴 instanceof 操做符來肯定對象類型。

七、穩妥構造函數模式

所謂穩妥對象,指的是沒有公共屬性,並且其方法也不引用 this 的對象。穩妥對象最適合在一些安全的環境中(這些環境中會禁止使用 this 和 new),或者在防止數據被其餘應用程序(如 Mashup 程序)改動時使用。

穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:

  • 一是新建立對象的實例方法不引用 this;
  • 二是不使用 new 操做符調用構造函數。
function Person (firstname, lastname) {
    // 建立要返回的對象
    var o = new Object ();
    // 定義私有變量和函數
    o.firstname = firstname;
    o.lastname = lastname;
    // 添加方法
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    // 返回對象
    return o;
}

var cap = Person('Captain', 'Flag');
複製代碼
  • 優勢:

    安全性高,使得它很是適合在某些安全執行環境。

  • 缺點:

    與寄生構造函數模式相似,建立的對象與構造函數之間也沒有什麼關係,所以 instanceof 操做符對這種對象也沒有意義。

Javascript如何實現繼承?

參考:高程三 6.3

如今有一個"動物"對象的構造函數。

function Animal () {
    this.kingdom = 'animal'; // 動物界
}
複製代碼

還有一個"貓"對象的構造函數。

function Cat () {
    this.family = 'cat'; // 貓科
}
複製代碼

一、原型繼承

ECMAScript 中描述了原型鏈的概念,並將原型鏈做爲實現繼承的主要方法。其基本思想是利用原型 讓一個引用類型繼承另外一個引用類型的屬性和方法。

若是"貓"的prototype對象,指向一個Animal的實例,那麼全部"貓"的實例,就能繼承Animal了。

// 刪除了 prototype 對象原先的值
// 將Cat的prototype對象指向一個Animal的實例
Cat.prototype = new Animal();
// 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
// 若是沒有上一行,Cat.prototype.constructor 指向Cat
// 加了這一行之後,Cat.prototype.constructor指向Animal
// 這顯然會致使繼承鏈的紊亂(cat明明構造函數Cat生成的),所以咱們必須手動糾正
Cat.prototype.constructor = Cat;

var cat = new Cat();
console.log(cat.kingdom); // 'animal'
複製代碼

缺點:

  1. 須要執行和創建Animal的實例,消耗內存,效率較低;
  2. 原型屬性會被全部實例共享,在子類型中更改引用類型數據的值,會影響原型的值;
  3. 在建立子類型(Cat)的實例時,沒有辦法在不影響全部對象實例的狀況下,給超類型(Animal)的構造函數傳遞參數。

二、借用構造函數(constructor stealing)/僞造對象/經典繼承

在子類型構造函數的內部調用超類型構造函數。

函數只不過是在特定環境中執行代碼的對象,所以經過使用 apply()call() 方法也能夠在(未來)新建立的對象上執行構造函數。

使用call或apply方法,將父對象的構造函數綁定在子對象上。

即在子對象構造函數中加一行:

function Cat () {
    Animal.apply(this); // 「借調」超類型的構造函數
    // or
    // Animal.call(this);
    this.family = 'cat';
}
複製代碼
  • 優勢:

    1. 簡單。

    2. 能夠在子類型構造函數中向超類型構造函數傳遞參數,如:

      function Animal (name) {
          this.name = name;
      }
      function Cat (name) {
      	this.kind = 'cat';
          Animal.call(this, name);
      }
      複製代碼
  • 缺點:

    1. 若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式(建立對象的一種方式)存在的問題 —— 方法都在構造函數中定義,所以函數複用無從談起了。
    2. 在超類型的原型Animal.prototype中定義的方法(由於並無利用原型鏈),對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式。

三、組合繼承(combination inheritance)/僞經典繼承

JavaScript 中最經常使用的繼承模式。

結合前兩種方法,發揮兩者之長的一種繼承模式。

思路:

  1. 原型鏈:繼承 原型屬性和方法,
  2. 借用構造函數:繼承 實例屬性。(在子類型中更改其值,不會影響原型的值)

這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。

function Animal (name) {
    this.name = name;
}
Animal.prototype.sayNamw = function () {
    console.log(this.name);
};

function Cat (name) {
    Animal.call(this, name); // 第二次調用超類構造函數
}
Cat.prototype = new Animal(); // 第一次調用超類構造函數
Cat.prototype.constructor = Cat;

// 使用
var cat1 = new Cat('Meow');
cat1.name; // 'Meow'
cat1.name = 'Oops'; // 'Oops'
Animal.prototype.name; // undefined(改變子類的值,不會改變原型的值)
cat1.sayName(); // 'Oops'
Animal.prototype.sayName(); // undefined
複製代碼

instanceofisPrototypeOf() 可以識別 基於組合繼承建立的對象。

  • 缺點:

    1. 不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。

    2. 屬性也會建立兩遍:

      • 第一次調用Animal構造函數時,Cat.prototype 會獲得name屬性;
      • 第二次調用Animal構造函數,在新對象上cat1上建立了實例屬性name
      • 因而,這個屬性就屏蔽了原型中的同名屬性。

四、原型式繼承

這種方法並無使用嚴格意義上的構造函數。

藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。

  • ECMAScript 5 經過新增Object.create()方法規範化了原型式繼承。
function object (o) {
    function F () {}
    F.prototype = o;
    return new F();
}
複製代碼

object(o)的做用是,以o爲原型,建立一個新的對象。好比:

var oldObj = { a: 1 };
var newObj = object(oldObj);
複製代碼

ECMAScript 5 經過新增Object.create()方法規範化了原型式繼承。

這個方法接收兩個參數:一 個用做新對象原型的對象,(可選的)一個爲新對象定義額外屬性的對象。

  • 缺點:

    1. 包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式同樣。
      var arr = ['old'];
      var oldObj = { arr: arr };
      var newObj = object(oldObj);
      
      newObj.arr.push('new'); // 會更改原值
      console.log(arr); // ['old', 'new']
      
      newObj.arr = [0, 1]; // 不會更改原值
      console.log(newObj.arr); // [0, 1]
      console.log(arr); // ['old', 'new']
      複製代碼

五、寄生式(parasitic)繼承

與原型式繼承(上一種) 緊密相關的一種思路。

與寄生構造函數和工廠模式相似。

建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。

// 原型式繼承的主要方法
function object (o) {
    function F () {}
    F.prototype = o;
    return new F();
}

function createAnother (original) {
    // object()函數不是必需的;
    // 任何可以返回新對象的函數都適用於此模式。
    var clone = object(original); //經過調用函數建立一個新對象
    clone.sayHi = function(){ //以某種方式來加強這個對象
        console.log("hi");
    };
    return clone; //返回這個對象
}
複製代碼

在主要考慮對象不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。

  • 缺點:

    1. 使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用而下降效率;這一點與構造函數模式相似。

六、寄生組合式繼承

解決三、組合繼承的缺點 —— 調用兩次超類型構造函數 ∴ 屬性也建立了兩遍。

借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。

  • 基本思路:

    沒必要爲了 指定子類型的原型 而 調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。

  • 本質:

    使用 寄生式繼承 來 繼承超類型的原型,而後再將結果指定給子類型的原型。

/* * @param {constructor} subType 子類型構造函數 * @param {constructor} superType 超類型構造函數 */
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); //建立對象
    prototype.constructor = subType; //加強對象
    subType.prototype = prototype; //指定對象
}
複製代碼

第一步是建立超類型原型的一個副本。

第二步是爲建立的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性。

最後一步,將新建立的對象(即副本)賦值給子類型的原型。這樣,咱們就能夠用調用 inheritPrototype() 函數的語句,去替換前面例子中爲子類型原型賦值的語句了。

inheritPrototype替代三、組合繼承中,第一次調用構造函數時的Cat.prototype = new Animal();

function Animal (name) {
    this.name = name;
}
Animal.prototype.sayNamw = function () {
    console.log(this.name);
};

function Cat (name) {
    Animal.call(this, name); // 這裏保持不變,依舊會調用超類構造函數
}
//Cat.prototype = new Animal(); // 第一次調用超類構造函數
//Cat.prototype.constructor = Cat;
inheritPrototype(Cat, Animal);
複製代碼
  • 優勢:

    1. 高效率,體如今它只調用了一次超類構造函數,所以避免了在 Cat.prototype 上建立沒必要要的、多餘的屬性,而且原型鏈還能保持不變還可以正常使用 instanceof 和 isPrototypeOf()。

    2. 開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。

Javascript做用鏈域?

全局函數沒法查看局部函數的內部細節,但局部函數能夠查看其上層的函數細節,直至全局細節。

當須要從局部函數查找某一屬性或方法時,若是當前做用域沒有找到,就會上溯到上層做用域查找,直至全局函數。

這種組織形式就是做用域鏈。

談談This對象的理解。

  • this老是指向函數的直接調用者(而非間接調用者);
  • 若是有new關鍵字,this指向new出來的那個對象;
  • 在事件中,this指向觸發這個事件的對象,特殊的是,IE中的attachEvent中的this老是指向全局對象Window。

如下內容總結自You Don't Know JS:

規則1. 默認綁定(Default Binding)

其餘任何規則都沒法應用時,就會使用默認綁定。

默認綁定狀況下,this在非嚴格模式中指向global,嚴格模式中爲undefined

function foo () {
 'use strict';
    console.log(this.a);
}
foo(); //Uncaught TypeError: Cannot read property 'a' of undefined
複製代碼

規則2. 隱式綁定

考慮調用位置(call-site)是否有上下文對象(context object)。

this老是指向函數的直接調用者(而非間接調用者)。

var obj = {
    a: 1,
    foo: function () {
        console.log(this.a);
    }
};
obj.foo(); // 1
複製代碼

鏈式調用,調用的是最後一個對象的屬性:

var obj1 = {
    a: 1,
    obj2: obj2
};
var obj2 = {
    a: 2,
    foo: function foo () {
        console.log(this.a);
    }
};
obj1.obj2.foo(); // 2
複製代碼

隱式丟失(Implicitly Lost)

  1. function reference/alias(函數引用)

    var obj = {
        a: 1,
        foo: function () {
            console.log(this.a);
        }
    };
    var bar = obj.foo;
    var a = 'oops, global';
    bar(); // 'oops, global'
    複製代碼

    其實,bar就是一個函數foo的引用而已。

    它的調用位置實際上是全局環境(默認綁定規則被應用)。

  2. passing a callback function(傳遞迴調函數)

    function foo () {
        console.log(this.a);
    }
    function doSth (fn) {
        fn();
    }
    var obj = {
        a: 1,
        foo: foo
    };
    var a = 'oops, global';
    
    doSth(obj.foo); // 這裏其實就是對fn的賦值(LHS),本質同上一情形
    複製代碼

規則3. 顯式綁定(Explicit Binding)

使用call()apply()

function foo () {
    console.log(this.a);
}
var obj = {
    a: 2
};
foo.call( obj );
複製代碼

修正隱式綁定中的兩個例子: 1. javascript var obj = { a: 1, foo: function () { console.log(this.a); } }; var bar = obj.foo; var a = 'oops, global'; bar(); // 'oops, global' bar.call(obj); // 1 2. ```javascript function foo () { console.log(this.a); } function doSth (fn) { //fn(); fn.call(obj); // 顯式綁定 } var obj = { a: 1, foo: foo }; var a = 'oops, global';

doSth(obj.foo); // 這裏其實就是對fn的賦值(LHS),本質同上一情形
```
複製代碼

解決隱式綁定this丟失問題:Hard binding

上述的修正只是在調用的時候作的修正,沒有真正解決問題。

  1. 包裝成一個函數:

    function foo () {
        console.log(this.a);
    }
    var obj = {
        a: 2
    };
    /* hard binding */
    var bar = function () {
        foo.call( obj ); 
    };
    
    /* 如論怎麼調用bar,都會調用obj.foo */
    bar();// 2
    setTimeout(bar, 100);// 2
    bar.call(window);// 2
    複製代碼

    傳參:

    function foo (b) {
        console.log(this.a, b);
    }
    var obj = {
        a: 1
    };
    var bar = function () {
        foo.apply(obj, arguments);
    };
    
    bar(3); // 1 3
    複製代碼
  2. reusable helper

    function foo (b) {
        console.log(this.a, b);
    }
    var obj = {
        a: 1
    };
    // 可進行重複利用
    function bind (fn, obj) {
        return function () {
            fn.apply(obj, arguments);
        };
    }
    
    var bar = bind(foo, obj);
    bar(3); // 1 3
    複製代碼

    ES5內置Function.prototype.bind

    function foo (b) {
        console.log(this.a, b);
    }
    var obj = {
        a: 1
    };
    
    var bar = foo.bind(obj);
    bar(3); // 1 3
    複製代碼
  3. 一些API提供thisArg參數

    function foo (el) {
        console.log(el, this.a);
    }
    var obj = {
        a: 'a'
    
    [1, 2, 3].forEach(foo, obj);
    // 1 "a"
    // 2 "a"
    // 3 "a"
    複製代碼

規則4. new綁定(new Binding)

new操做符幹了些什麼?

將this指向新建的對象。

規則覆蓋順序

3顯式、4new > 2隱式 > 1基礎

例外

  1. this被忽略

    傳遞nullundefined做爲applycallbind的綁定參數,this綁定會被忽略,而應用默認綁定

    • 使用場景:

      function foo () {
          // arguments爲類數組方法,不能直接調用join方法
          console.log(Array.prototype.join.call(arguments, ', '))
      }
      
      // 數組轉化爲參數
      foo.apply(null, [1,2]); // 1, 2
      foo.apply(null, [1,2,3,4,5]); // 1, 2, 3, 4, 5
      複製代碼
    • 可能存在的問題:

      使用第三方庫時,可能內部存在this,從而不經意中指向了global

    • 更安全的this:

      使用DMZ對象(一個徹底空的、沒有任何繼承的對象)。

      function foo () {
      	console.log(this);
          console.log(Array.prototype.join.call(arguments, ', '));
      }
      // DMZ empty object
      var Ø = Object.create(null);
      
      foo.apply(Ø, [1,2])
      // {} No properties
      // 1, 2
      foo.apply(null, [1,2])
      // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
      // 1, 2
      複製代碼
  2. 間接引用

    退回默認綁定。

    • 知識點:

      變量聲明返回undefined,賦值返回等號右邊的值。

    • 例子:

      function foo () {
          console.log(this.a);
      }
      var a = 2;
      var o = { a: 3, foo: foo };
      var p = { a: 4 };
      (p.foo = o.foo)(); // 2
      複製代碼

      why? p.foo = o.foo默認返回foo函數,這個foo函數纔是當即執行的對象。

      p.foo = o.foo
      //ƒ foo () {
      // console.log(this.a);
      //}
      複製代碼
  3. 軟綁定(Softening Binding)

    複雜,暫時不想管。

詞法this(Lexical this)

this是在定義函數時綁定的,不是在執行過程當中綁定的。

  1. 方法一:詞法捕獲this

    function foo () {
        var self = this;
        return function () {
            console.log(self.a);
        };
    }
    var obj1 = { a: 1 };
    var obj2 = { a: 2 };
    var bar = foo.call(obj1);
    
    bar(); // 1
    bar.call(obj2); // 1
    複製代碼
  2. 方法二:ES6 箭頭函數

    幾乎與詞法捕獲相同。

    function foo () {
        return () => {
            console.log(this.a);
        };
    }
    var obj1 = { a: 1 };
    var obj2 = { a: 2 };
    var bar = foo.call(obj1);
    
    bar(); // 1
    bar.call(obj2); // 1
    複製代碼

注意:儘可能不要混用詞法方法默認的this機制(rule1-4)

new操做符具體幹了什麼呢?

  1. 建立一個新對象;
  2. 將新對象的[[protorype]]設置爲Base.prototype
  3. 構造函數Base被傳入參數並調用(執行構造函數中的代碼,爲這個新對象添加屬性),關鍵字this被設定指向新對象;
  4. 除非函數Base顯性返回一個對象,不然自動返回新對象。
new Base();
// 即
var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
複製代碼

Ajax 是什麼? 如何建立一個Ajax?

參考: MDN,高程三

ajax的全稱:Asynchronous Javascript And XML(異步傳輸+js+xml)。

其自己不是一種新技術,而是一個在 2005年被Jesse James Garrett提出的新術語,用來描述一種使用現有技術集合的‘新’方法。包括:

  • HTML or XHTML
  • CSS(Cascading Style Sheets)
  • JavaScript
  • DOM(The Document Object Model)
  • XML
  • XSLT
  • 最重要的 XMLHttpRequest object

AJAX主要特徵:

  • Make requests to the server without reloading the page
  • Receive and work with data from the server

AJAX可與服務器交流並交換數據,無需從新刷新頁面就能更新頁面。

所謂異步,在這裏簡單地解釋就是:向服務器發送請求的時候,咱們沒必要等待結果,而是能夠同時作其餘的事情,等到有告終果它本身會根據設定進行後續操做,與此同時,頁面是不會發生整頁刷新的,提升了用戶體驗。

ajax api整體代碼

function ajax (callback, requestMethod, url, isAsync, data) {
  // create an instance of XMLHttpRequest
  let xhr = createXHR()
  // handle the server response
  xhr.onreadystatechange = function handleRequest () {
    // check the request's state
    if (xhr.readyState === XMLHttpRequest.DONE) { // 4
      // when the response's received
      callback(xhr)
    } else {
      // not ready yet
    }
  }
  // actually make the request
  xhr.open(requestMethod, url, isAsync)
  if (requestMethod === 'POST') {
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.send(data)
  } else {
    xhr.send()
  }
}

function createXHR () {
  if (typeof XMLHttpRequest !== 'undefined') { // IE7+ and other
    return new XMLHttpRequest()
  } else if (typeof ActiveXObject !== 'undefined') { // IE6 and older
    // create the newest XHR object in MSXML
    if (typeof createXHR.activeXString !== 'string') {
      const versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']
      for (let i = 0, len = versions.length; i < len; i++) {
        try {
          new ActiveXObject(versions[i])
          createXHR.activeXString = versions[i]
          break
        } catch (e) {}
      }
    }
    return new ActiveXObject(createXHR.activeXString)
  } else {
    throw new Error('No XHR object available.')
  }
}
複製代碼

步驟:

  1. 建立XMLHttpRequest對象,也就是建立一個異步調用對象

    // Old compatibility code, no longer needed.
    function createXHR () {
      if (typeof XMLHttpRequest !== 'undefined') { // IE7+ and other
        return new XMLHttpRequest()
      } else if (typeof ActiveXObject !== 'undefined') { // IE6 and older
        // create the newest XHR object in MSXML
            // if中的語句只需運行一次
            // 第一次運行後,會在函數createXHR上建立一個屬性activeXString
            // 當第二次運行時,只要該屬性不被改寫
            // 便仍可以使用
            // 檢驗其是否存在,存在則直接跳過這段if
        if (typeof createXHR.activeXString !== 'string') {
          const versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']
          for (let i = 0, len = versions.length; i < len; i++) {
            // 檢驗建立ActiveXObject實例是否會報錯
            // 並非真正地建立
            try {
              new ActiveXObject(versions[i])
              createXHR.activeXString = versions[i]
              break
            } catch (e) {}
          }
        }
        // 此處根據activeXString屬性值,建立ActiveXObject實例
        return new ActiveXObject(createXHR.activeXString)
      } else {
        throw new Error('No XHR object available.')
      }
    }
    複製代碼
  2. 建立一個新的HTTP請求,並指定該HTTP請求的方法、URL及驗證信息

    // actually make the request
      xhr.open(requestMethod, url, isAsync)
    複製代碼
  3. 設置響應HTTP請求狀態變化的函數

    XHR 對象的 readyState 屬性,該屬性表示請求/響應過程的當前活動階段。這個屬性可取的值以下:

    • 0:未初始化。還沒有調用 open()方法。
    • 1:啓動。已經調用 open()方法,但還沒有調用 send()方法。
    • 2:發送。已經調用 send()方法,但還沒有接收到響應。
    • 3:接收。已經接收到部分響應數據。
    • 4:完成。已經接收到所有響應數據,並且已經能夠在客戶端使用了。

    必須在調用 open()以前指定 onreadystatechange 事件處理程序才能確保跨瀏覽器兼容性。(由於open()也會觸發onreadystatechange

    // handle the server response
    xhr.onreadystatechange = function handleRequest () {
        // check the request's state
        if (xhr.readyState === XMLHttpRequest.DONE) { // 4
          // when the response's received
          callback(xhr)
        } else {
          // not ready yet
        }
    }
    複製代碼
  4. 發送HTTP請求

    // actually make the request
    if (requestMethod === 'POST') {
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
        xhr.send(data)
    } else {
        xhr.send()
    }
    複製代碼
  5. 獲取異步調用返回的數據

    用回調的方式傳入api:

    function callback (res) {
        // check the HTTP response status codes of the HTTP response
        if (res.status >= 200 && res.status < 300 || res.status === 304) {
          // perfect!
          console.log('success')
          ajxDisplay.innerText = res.responseText
        } else {
          // something wrong
          console.log('fail')
        }
    }
    複製代碼
  6. 使用JavaScript和DOM實現局部刷新

    調用 ajax 封裝函數。

    btnAjax.onclick = function () {
      ajax(callback, 'GET', '/ajax', true)
      ajax(callback, 'POST', '/ajax', true, encodeURIComponent('name') + '=' + encodeURIComponent('hi'))
    }
    複製代碼

IE兼容性

參考:

看到有兩種不一樣寫法:

// MDN
new ActiveXObject('Microsoft.XMLHTTP')
// 高程三
new ActiveXObject('MSXML2.XMLHttp.6.0')
new ActiveXObject('MSXML2.XMLHttp.3.0')
new ActiveXObject('MSXML2.XMLHttp')
複製代碼
  • Msxml2.XMLHTTP是高版本,受msxml3.dll+支持
  • Microsoft.XMLHTTP是低本,通常是msxml2.6如下版本使用

[[0], 1].reduce((p, v) => p.push([v])).toString()的值

題目:

[[0], 1].reduce((p, v) => p.push([v])).toString() // "2"
複製代碼

Why?

基礎知識點:

  • reduce

    arr.reduce(callback[, initialValue])

    reduce返回值,是用的callback的返回值。

    callback的四個參數:

    • Accumulator (acc)
    • Current Value (cur)
    • Current Index (idx)
    • Source Array (src)
  • push:

    push的返回值,是數組的新長度。

  • 函數返回值:

    具體緣由不清楚。

    函數返回的數值,是number的包裝對象,而非原始數據類型,因此能用toString()而不會報錯。

    function foo () { return 1; }
    foo().__proto__; // Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
    1.__proto__; // Uncaught SyntaxError: Invalid or unexpected token
    複製代碼

題目分析:

  • 默認沒有initialValue的狀況下,p的初始值爲[0]v1
  • p.push([v]),即p.push([1]),因而p的現值爲[0, [1]]
  • reduce返回的值 爲 p.puh([1])的返回值,即數組的新長度,也就是2。
相關文章
相關標籤/搜索