😂這只是我的筆記……我沒想到竟然有人看到……javascript
題目來源: 前端開發面試題html
答案基本是本身整理的。並非所有題目都有。前端
會有不少本身的囉嗦,還有不少亂七八糟的補充,請見諒。java
Undefined、Null
Boolean、Number、String
//ECMAScript 2015新增:
Symbol //建立後獨一無二且不可變的數據類型
複製代碼
參考:git
如下多引自:MDNgithub
Symbol 實例是惟一且不可改變的。web
Symbol 對象是 Symbol原始值的封裝。面試
Symbol 的描述是可選的,但僅用於調試目的。ajax
Symbol("foo") !== Symbol("foo")
const foo = Symbol()
const bar = Symbol()
typeof foo === "symbol"
typeof bar === "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()]
複製代碼
基本對象:Object、Function、Boolean、Symbol、Error
數字和日期對象:Number、Math、Date
字符串:String、RegExp
集合對象:Array、Map、Set
結構化數據:JSON
控制抽象對象:Promise
其餘:arguments
Object 是 JavaScript 中全部對象的父對象。
數據封裝類對象:Object、Array、Boolean、Number 和 String
單例內置對象:Math、Global
其餘對象:Function、Arguments、Date、RegExp、Error
不要在同一行聲明多個變量。
使用 ===
/!==
來比較true
/false
或者數值
==
將Object轉化爲基礎類型,進行「值」比較補充點:==
和類型轉換
參考:JavaScript "loose" comparison step by step ←文章還提供了一個很好用的過程展現工具。
判斷流程簡單總結:流程圖
注:關於其中涉及的toPrimitive
和toNumber
函數,參考:從[]==![]
爲true
來剖析JavaScript各類蛋疼的類型轉換 和 MDN
使用對象字面量替代new Array這種形式
不要使用全局函數
Switch語句必須帶有default分支
函數不該該有時候有返回值,有時候沒有返回值
For循環必須使用大括號
If語句必須使用大括號
for-in循環中的變量應該使用var關鍵字明確限定做用域,從而避免做用域污染。
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
是原型鏈的頂端。
這兩種其實本質是同樣的,只是看的方式不一樣。
就像俄羅斯套娃,最後一個的內部是空的,因此能夠判定它是「最後一個」。若是用這種眼光去看,那麼Object.prototype
就是頂端。
而若是把null也當成是一個節點,那麼「空」纔是原型鏈的頂端。
但也有人提出:
ECMA262這麼寫道:
The value of the [[Prototype]] internal slot of the Object prototype object is null.
我不是很懂,由於這個初始值是null,因此就天然成爲原型鏈的頂端了嗎
關係:
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
屬性
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]]
的兩種方法
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
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()}
複製代碼
兩種類型的區別:存儲位置不一樣。
區別 | 原始數據類型 | 引用數據類型 |
---|---|---|
存儲位置 | 棧(stack) | 堆(heap) |
佔據空間 | 小,大小固定 | 大,大小不固定 |
引用數據類型若是存儲在棧中,將會影響程序運行的性能;
引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體。
// radix = 0, 2, 3, ..., 36
// radix表示的是string的進制,而非轉換出來的結果的進制
parseInt(string, radix)
parseFloat(value)
複製代碼
都爲全局函數,不屬於任何對象。(但Number裏有同名函數)
能轉換的數據類型:
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
複製代碼
Number(value)
複製代碼
在非構造器上下文中 (如:沒有 new 操做符),Number 能被用來執行類型轉換。
'12.3b'.match(/(\d)+(\.)?(\d)+/g)[0] * 1
複製代碼
可是這個不太靠譜,提供一種思路而已。
參考:
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
這個參數調整,但老的瀏覽器不支持此參數。
參考: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,')
複製代碼
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==種。
本質:完美洗牌的算法問題。
這個問題須要剔除給定的array的初始順序的影響。
不管怎麼洗,出現各類結果的機率必須是同樣的。
這樣其實問題就簡單:只要考慮,每一個index的位置,有多少種可能性就好了。
這就像是:餐桌四個座位,有幾種坐法?
其實就是array的排列問題。
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));
複製代碼
Array.prototype.sort([compareFunction])
: 根據所提供的排序算法,進行排序。
compareFunction(a, b)
接受兩個參數,即兩個比較的item。
函數返回一個number:
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random() - 0.5;
})
console.log(arr);
複製代碼
缺陷:
這個方法產生的結果,並非均勻的。
測試以下:
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]
複製代碼
參考:高程三 6.2
能夠用來建立單個對象。
但要建立不少對象,就會產生大量的重複代碼。
// 建立 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 操做符調用構造函數的過程:
[[protorype]]
設置爲Person.prototype
;Person
被傳入參數並調用(執行構造函數中的代碼,爲這個新對象添加屬性),關鍵字this
被設定指向新對象;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
});
複製代碼
注:後二者寫法,都直接重寫了原型,若是有實例在原型重寫以前生成,這些實例與更改後的原型是沒有聯繫的。
優勢:
可讓全部對象實例共享原型對象所包含的屬性和方法(沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中)。
缺點:
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
複製代碼
優勢:
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 語言經驗的開發人員在看到獨立的構造函數和原型時,極可能會感到很是困惑。動態原 型模式正是致力於解決這個問題的一個方案,它把全部信息都封裝在了構造函數中,而經過在構造函數 中初始化原型(僅在必要的狀況下),又保持了同時使用構造函數和原型的優勢。換句話說,能夠經過 檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。
基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但從表面上看,這個函數又很像是典型的構造函數。
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 程序)改動時使用。
穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:
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 操做符對這種對象也沒有意義。
參考:高程三 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'
複製代碼
缺點:
在子類型構造函數的內部調用超類型構造函數。
函數只不過是在特定環境中執行代碼的對象,所以經過使用 apply()
和 call()
方法也能夠在(未來)新建立的對象上執行構造函數。
使用call或apply方法,將父對象的構造函數綁定在子對象上。
即在子對象構造函數中加一行:
function Cat () {
Animal.apply(this); // 「借調」超類型的構造函數
// or
// Animal.call(this);
this.family = 'cat';
}
複製代碼
優勢:
簡單。
能夠在子類型構造函數中向超類型構造函數傳遞參數,如:
function Animal (name) {
this.name = name;
}
function Cat (name) {
this.kind = 'cat';
Animal.call(this, name);
}
複製代碼
缺點:
Animal.prototype
中定義的方法(由於並無利用原型鏈),對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式。JavaScript 中最經常使用的繼承模式。
結合前兩種方法,發揮兩者之長的一種繼承模式。
思路:
這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。
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
複製代碼
instanceof
和 isPrototypeOf()
可以識別 基於組合繼承建立的對象。
缺點:
不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。
屬性也會建立兩遍:
Animal
構造函數時,Cat.prototype
會獲得name屬性;Animal
構造函數,在新對象上cat1
上建立了實例屬性name
。這種方法並無使用嚴格意義上的構造函數。
藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
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()
方法規範化了原型式繼承。
這個方法接收兩個參數:一 個用做新對象原型的對象,(可選的)一個爲新對象定義額外屬性的對象。
缺點:
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']
複製代碼
與原型式繼承(上一種) 緊密相關的一種思路。
與寄生構造函數和工廠模式相似。
建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。
// 原型式繼承的主要方法
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; //返回這個對象
}
複製代碼
在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。
缺點:
解決三、組合繼承
的缺點 —— 調用兩次超類型構造函數 ∴ 屬性也建立了兩遍。
借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
基本思路:
沒必要爲了 指定子類型的原型 而 調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。
本質:
使用 寄生式繼承 來 繼承超類型的原型,而後再將結果指定給子類型的原型。
/* * @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);
複製代碼
優勢:
高效率,體如今它只調用了一次超類構造函數,所以避免了在 Cat.prototype
上建立沒必要要的、多餘的屬性,而且原型鏈還能保持不變還可以正常使用 instanceof 和 isPrototypeOf()。
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。
全局函數沒法查看局部函數的內部細節,但局部函數能夠查看其上層的函數細節,直至全局細節。
當須要從局部函數查找某一屬性或方法時,若是當前做用域沒有找到,就會上溯到上層做用域查找,直至全局函數。
這種組織形式就是做用域鏈。
如下內容總結自You Don't Know JS:
其餘任何規則都沒法應用時,就會使用默認綁定。
默認綁定狀況下,this
在非嚴格模式中指向global
,嚴格模式中爲undefined
。
function foo () {
'use strict';
console.log(this.a);
}
foo(); //Uncaught TypeError: Cannot read property 'a' of undefined
複製代碼
考慮調用位置(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
複製代碼
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的引用而已。
它的調用位置實際上是全局環境(默認綁定規則被應用)。
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),本質同上一情形
複製代碼
使用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),本質同上一情形
```
複製代碼
上述的修正只是在調用的時候作的修正,沒有真正解決問題。
包裝成一個函數:
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
複製代碼
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
複製代碼
一些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"
複製代碼
將this指向新建的對象。
3顯式、4new > 2隱式 > 1基礎
this被忽略
傳遞null
或undefined
做爲apply
、call
或bind
的綁定參數,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
複製代碼
間接引用
退回默認綁定。
知識點:
變量聲明返回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);
//}
複製代碼
軟綁定(Softening Binding)
複雜,暫時不想管。
this是在定義函數時綁定的,不是在執行過程當中綁定的。
方法一:詞法捕獲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
複製代碼
方法二: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
複製代碼
[[protorype]]
設置爲Base.prototype
;Base
被傳入參數並調用(執行構造函數中的代碼,爲這個新對象添加屬性),關鍵字this
被設定指向新對象;Base
顯性返回一個對象,不然自動返回新對象。new Base();
// 即
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
複製代碼
參考: MDN,高程三
ajax的全稱:Asynchronous Javascript And XML(異步傳輸+js+xml)。
其自己不是一種新技術,而是一個在 2005年被Jesse James Garrett提出的新術語,用來描述一種使用現有技術集合的‘新’方法。包括:
AJAX可與服務器交流並交換數據,無需從新刷新頁面就能更新頁面。
所謂異步,在這裏簡單地解釋就是:向服務器發送請求的時候,咱們沒必要等待結果,而是能夠同時作其餘的事情,等到有告終果它本身會根據設定進行後續操做,與此同時,頁面是不會發生整頁刷新的,提升了用戶體驗。
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.')
}
}
複製代碼
建立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.')
}
}
複製代碼
建立一個新的HTTP請求,並指定該HTTP請求的方法、URL及驗證信息
// actually make the request
xhr.open(requestMethod, url, isAsync)
複製代碼
設置響應HTTP請求狀態變化的函數
XHR 對象的 readyState 屬性,該屬性表示請求/響應過程的當前活動階段。這個屬性可取的值以下:
必須在調用 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
}
}
複製代碼
發送HTTP請求
// actually make the request
if (requestMethod === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(data)
} else {
xhr.send()
}
複製代碼
獲取異步調用返回的數據
用回調的方式傳入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')
}
}
複製代碼
使用JavaScript和DOM實現局部刷新
調用 ajax 封裝函數。
btnAjax.onclick = function () {
ajax(callback, 'GET', '/ajax', true)
ajax(callback, 'POST', '/ajax', true, encodeURIComponent('name') + '=' + encodeURIComponent('hi'))
}
複製代碼
參考:
看到有兩種不一樣寫法:
// MDN
new ActiveXObject('Microsoft.XMLHTTP')
// 高程三
new ActiveXObject('MSXML2.XMLHttp.6.0')
new ActiveXObject('MSXML2.XMLHttp.3.0')
new ActiveXObject('MSXML2.XMLHttp')
複製代碼
[[0], 1].reduce((p, v) => p.push([v])).toString()
的值題目:
[[0], 1].reduce((p, v) => p.push([v])).toString() // "2"
複製代碼
Why?
基礎知識點:
arr.reduce(callback[, initialValue])
reduce返回值,是用的callback的返回值。
callback的四個參數:
push:
push的返回值,是數組的新長度。
函數返回值:
具體緣由不清楚。
函數返回的數值,是number的包裝對象,而非原始數據類型,因此能用toString()
而不會報錯。
function foo () { return 1; }
foo().__proto__; // Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
1.__proto__; // Uncaught SyntaxError: Invalid or unexpected token
複製代碼
題目分析:
p
的初始值爲[0]
,v
爲1
。p.push([v])
,即p.push([1])
,因而p的現值爲[0, [1]]
。p.puh([1])
的返回值,即數組的新長度,也就是2。