原型:每一個對象都會在其內部初始化一個屬性,就是prototype(原型)屬性,相似一個指針。javascript
原型鏈:當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼就會去prototype裏找這個屬性,如此遞推下去,一直檢索到 Object 內建對象。java
《js高級程序設計》:原型鏈就是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。node
原型鏈例子:git
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//繼承 Father
Son.prototype = new Father();
//Son.prototype被重寫,致使Son.prototype.constructor也一同被重寫
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
//instance實例經過原型鏈找到了Father原型中的getFatherValue方法.
複製代碼
原型鏈問題:es6
在子類構造函數內部使用apply
或者call
來調用父類的函數便可在實現屬性繼承的同時,又能傳遞參數,又能讓實例不互相影響。github
function Super(){
this.flag = true;
}
Super.prototype.getFlag = function(){
return this.flag; //繼承方法
}
function Sub(){
this.subFlag = flase
Super.call(this) //繼承屬性
}
Sub.prototype = new Supe();
Sub.prototype.constructor = Sub;
var obj = new Sub();
Super.prototype.getSubFlag = function(){
return this.flag;
}
複製代碼
小問題: Sub.prototype = new Super;
會致使Sub.prototype
的constructor
指向Super
; 然而constructor
的定義是要指向原型屬性對應的構造函數的,Sub.prototype
是Sub
構造函數的原型,因此應該添加一句糾正:Sub.prototype.constructor = Sub;
面試
組合繼承是 JavaScript 最經常使用的繼承模式,不過它也有不足的地方: 就是不管什麼狀況下,都會調用兩次父類構造函數: 一次是在建立子類型原型的時候, 另外一次是在子類型構造函數內部。編程
爲了下降調用父類構造函數的開銷而出現, 基本思路是沒必要爲了指定子類型的原型而調用超類型的構造函數。json
function extend(subClass,superClass){
//建立對象
var prototype = object(superClass.prototype);
prototype.constructor = subClass;//加強對象
subClass.prototype = prototype;//指定對象
}
複製代碼
extend
的高效率體如今它沒有調用superClass
構造函數,所以避免了在subClass.prototype
上面建立沒必要要,多餘的屬性,同時原型鏈還能保持不變,所以還能正常使用 instanceof
和 isPrototypeOf()
方法.segmentfault
其內部其實也是ES5組合繼承的方式,經過call借用構造函數,在A類構造函數中調用相關屬性,再用原型鏈的鏈接實現方法的繼承。
class B extends A {
constructor() {
return A.call(this); //繼承屬性
}
}
A.prototype = new B; //繼承方法
複製代碼
ES6封裝了class,extends關鍵字來實現繼承,內部的實現原理其實依然是基於上面所講的原型鏈,不過進過一層封裝後,Javascript的繼承得以更加簡潔優雅地實現。
class ColorPoint extends Point {
//經過constructor來定義構造函數,用super調用父類的屬性方法
constructor(x, y, color) {
super(x, y); // 等同於parent.constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 等同於parent.toString()
}
}
複製代碼
可參考:
call
和 apply
都是爲了解決改變 this 的指向。
call 方法第一個參數是要綁定給this的值,後面傳入的是一個參數列表。當第一個參數爲null、undefined的時候,默認指向window。
apply 接受兩個參數,第一個參數是要綁定給this的值,第二個參數是一個參數數組。當第一個參數爲null、undefined的時候,默認指向window。
var arr1 = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr1[0], arr1[1], arr1[2], arr1[3], arr1[4])//89
var arr2 = [1,2,3,89,46]
var max = Math.max.apply(null,arr2)//89
//至關於
obj1.fn() === obj1.fn.call/apply(obj1);
fn1() === fn1.call/apply(null)
f1(f2) === f1.call/apply(null,f2)
複製代碼
//cat give dog
cat.eatFish.call(dog, '汪汪汪', 'call')
//getValue.call(a, 'yck', '24') => a.fn = getValue
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])
複製代碼
call 的實現:
Function.prototype.myCall = function (context) {
var context = context || window
// 給 context 添加一個屬性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 將 context 後面的參數取出來
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 刪除 fn
delete context.fn
return result
}
複製代碼
bind
(ES5新增) 和call
很類似,第一個參數是this的指向,從第二個參數開始是接收的參數列表。
區別:bind 方法不會當即執行,而是返回一個改變了上下文 this 後的函數。
能夠經過 bind 實現 柯里化。
柯里化又稱部分求值(Partial Evaluation),簡單來講就是隻傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
柯里化有3個常見做用:1. 參數複用;2. 提早返回;3. 延遲計算/運行。
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2); // 3
addTen(2); // 12
複製代碼
低版本中實現 bind:
if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 保存原函數
context = [].shift.call(arguments), // 保存須要綁定的this上下文
args = [].slice.call(arguments); // 剩餘的參數轉爲數組
return function () { // 返回一個新函數
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
}
}
複製代碼
應用場景:
//1.將類數組轉化爲數組
var trueArr = Array.prototype.slice.call(arrayLike)
//2.數組追加
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]
//3.判斷變量類型
function isArray(obj){
return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('dot') // false
//4.利用call和apply作繼承
function Person(name,age){
// 這裏的this都指向實例
this.name = name
this.age = age
this.sayAge = function(){
console.log(this.age)
}
}
function Female(){
Person.apply(this,arguments)//將父元素全部方法在這裏執行一遍就繼承了
}
var dot = new Female('Dot',2)
複製代碼
參考: Promise 是 ES6 新增的語法,解決了回調地獄的問題。`
回調地獄:多個回調函數嵌套
Promise 不是新的語法功能,而是一種新的寫法,容許將回調函數的嵌套,改爲鏈式調用。
readFile(fileA)
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileB);
})
.then
......
複製代碼
Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。
優勢:
缺點:
創造 Promise 實例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
//resolve 指 fullfilled 狀態
resolve(value);
} else {
reject(error);
}
});
複製代碼
Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
複製代碼
一個簡單例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
複製代碼
timeout方法返回一個Promise實例,表示一段時間之後纔會發生的結果。過了指定的時間(ms參數)之後,Promise實例的狀態變爲resolved,就會觸發then方法綁定的回調函數。
形式上,Generator 函數是一個普通函數,可是有兩個特徵。
星號
;yield
表達式,定義不一樣的內部狀態調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象(Iterator Object)。
調用遍歷器對象的next方法
,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。
// 使用 * 表示這是一個 Generator 函數
// 內部能夠經過 yield 暫停代碼
// 經過調用 next 恢復執行
function* test() {
let a = 1 + 2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next()); // > { value: 2, done: false }
console.log(b.next()); // > { value: 3, done: false }
console.log(b.next()); // > { value: undefined, done: true }
複製代碼
ES6 誕生之前,異步編程的方法,大概有下面四種。
所以,Generator
函數是更好的寫法。Generator 函數還能夠部署錯誤處理代碼,捕獲函數體外拋出的錯誤。
使用 Generator 函數,執行一個真實的異步任務。
var fetch = require('node-fetch');
function* gen(){
//先讀取一個遠程接口,而後從 JSON 格式的數據解析信息。
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
//執行
//執行 Generator 函數,獲取遍歷器對象
var g = gen();
//使用next方法執行異步任務的第一階段
var result = g.next();
//因爲Fetch模塊返回的是一個 Promise 對象,所以要用then方法調用下一個next方法。
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
複製代碼
ES2017 標準引入了 async 函數,使得異步操做變得更加方便。
async 函數就是 Generator 函數的語法糖。
async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await。而且返回一個
Promise
。
async函數對 Generator 函數的改進:
async 、 await 相比直接使用 Promise :
例子:getJSON函數返回一個promise,這個promise成功resolve時會返回一個json對象。咱們只是調用這個函數,打印返回的JSON對象,而後返回」done」。
// promise
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
複製代碼
//使用Async/Await
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()
//async函數會隱式地返回一個promise,該promise的reosolve值就是函數return的值。(示例中reosolve值就是字符串」done」)
複製代碼
優點: 處理 then 的調用鏈可以更清晰準確的寫出代碼。
缺點: 濫用 await 可能會致使性能問題,由於 await 會阻塞代碼,也許以後的異步代碼並不依賴於前者,但仍然須要等待前者完成,致使代碼失去了併發性。