也到了本身快找工做的時候了,因此最近在複習前端的知識,這裏是本身對JavaScript的一些知識點的總結,之後會持續更新,分享本身的複習知識,有些是在不懂的時候參考大佬的講解。有些理解不到位的地方還請指正。祝本身找工做順利!!!javascript
這三個函數都會改變this的指向,call和apply更適用於在函數運行時改變this;而bind會返回一個新的函數,新函數的this由bind傳入的參數決定,因此bind更適用於返回一個新函數,這個函數在未來纔會執行,好比DOM添加事件。前端
// call
Function.prototype.myCall = function (ctx = window, ...arg) {
if (typeof this !== "function") return
ctx.fn = this
let res = ctx.fn(...arg)
delete ctx.fn
return res
}
// apply
Function.prototype.myApply = function (ctx = window, arg) {
if (typeof this !== "function") return
ctx.fn = this
if(!Array.isArray(arg)) {
throw new Error('須要數組')
}
let res = ctx.fn(...arg)
delete ctx.fn
return res
}
// bind
Function.prototype.newbBind = function(target){
target = target || window
var self = this;
// 這裏的arguments是在調用時傳入的參數
var args = [].slice.call(arguments, 1);
var temp = function () {}
function f(){
// 這裏的arguments是bind返回的新函數傳入的參
var _args = [].slice.call(arguments,0)//將一個類數組轉化爲數組
return self.apply(this instanceof temp? this : target, args.concat(_args))
}
temp.prototype = self.prototype
f.prototype = new temp()
return f
}
複製代碼
在Lambda演算(一套數理邏輯的形式系統,具體我也沒深刻研究過)中有個小技巧:假如一個函數只能收一個參數,那麼這個函數怎麼實現加法呢,由於高階函數是能夠當參數傳遞和返回值的,因此問題就簡化爲:寫一個只有一個參數的函數,而這個函數返回一個帶參數的函數,這樣就實現了能寫兩個參數的函數了——這就是所謂的柯里化(Currying,以邏輯學家Hsakell Curry命名),也能夠理解爲一種在處理函數過程當中的邏輯思惟方式。java
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。git
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
// 參考:https://juejin.im/post/5c9c3989e51d454e3a3902b6
複製代碼
原型是function的一個屬性,該屬性本質上是一個對象,它定義了構造函數構造出來的共有祖先,構造函數產生的實例對象能夠繼承該屬性的方法和屬性,當實例訪問某個屬性找不到就會順着原型鏈訪問該屬性。es6
有了原型,原型仍是一個對象,那麼這個名爲原型的對象天然還有本身的原型,這樣的原型上還有原型的結構就構成了原型鏈。github
原型鏈是是描述實例對象與構造函數的原型之間的關係,若是實例對象找不到某個屬性或者方法就會到構造函數的prototype上查找,若是仍是找不到就會訪問構造函數prototype屬性的__proto__
屬性,直到null。面試
在JavaScript中沒有類的概念,傳統語言中類經過拷貝實現繼承,JavaScript經過原型鏈、原型委託的方式實現繼承。正則表達式
組合繼承:編程
function Father() {
this.name = "father"
}
Father.prototype.say = function() {
console.log('say')
}
function Child() {
Father.call(this)
this.age = 12
}
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child
複製代碼
let inhert = (function() {
function F(){}
return function(father, child){
F.prototype = father.prototype
child.prototype = new F()
child.prototype.constructor = child
}
})
複製代碼
參考:juejin.im/post/5c96d0…json
this是JavaScript中的一個關鍵字,被自動定義在全部函數的做用域中。**this是在運行的時候進行綁定,並非在編寫的時候進行綁定,它的上下文取決於函數調用時的各類條件。**this的綁定和函數的聲明位置無關,只取決於函數的調用方式。
當一個函數被調用的時候,會建立一個活動記錄(也成爲執行上下文)。這個記錄會包含函數在哪裏調用(調用棧)、函數調用的方法、傳入的參數等信息。this就是記錄中的一個屬性,會在函數執行的過程當中用到。
調用位置指的是函數被調調用的位置而不是聲明的位置。
默認綁定的時候this指向window,默認綁定是指函數不帶任何修飾的函數引用進行調用。好比:
function foo() {
console.log(this)
}
foo() // window
複製代碼
可是須要注意的是在嚴格模式下,默認綁定並不會指向window。
隱式綁定一般以對象做爲執行上下文調用。可是咱們須要明白一個道理:無論是在對象中聲明一個函數,仍是先定義再添加函數的引用,嚴格來叔這個函數都不屬於該對象。
隱式綁定規則會把函數調用中的this綁定到這個上下文對象,由於調用foo的時候this被綁定到該對象,所以this.a等同於obj.a。
對象屬性引用鏈中只有最後一層會影響調用的位置。
let obj2 = {
a:2,
foo1:foo1
}
let obj1 = {
a:1,
obj2:obj2
}
function foo1() {
console.log(this.a)
}
obj1.obj2.foo1() // 2
複製代碼
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
let bar = obj.foo
bar()
複製代碼
由於bar是obj.foo的一個引用,可是實際上引用的是foo函數的自己,所以bar()是一個不帶任何修飾符的調用因此是默認綁定,this指向window。
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
function doFoo(fn) {
fn()
}
doFoo(obj.foo)
複製代碼
這裏調用doFoo的時候參入了obj.foo做爲實參,並將obj.foo賦值給fn,因此fn是foo函數的引用,在調用fn的時候也是不帶任何修飾的調用,因此是默認調用this指向window。
如下這種狀況this也是指向window。緣由和上面同樣。
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
setTimeout(obj.foo, 1000)
複製代碼
因此上面咱們能夠看出回調函數丟失this是很是常見的。
若是把null或者undefined做爲this綁定的對象傳入其中,這些值會被忽略,其實是默認綁定。
new > call、apply、bind > 隱式綁定 > 默認綁定
節流(throttle)是防止用戶頻繁操做,形成瀏覽器性能消耗過大
防抖(debounce),就是指觸發事件後在 n 秒內函數只能執行一次,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。
// 節流函數(throttle)
function throttle (fn, wait=500) {
let pre_time = 0
return function(...arg) {
let curr_time = Date.now()
if(curr_time - pre_time > wait) {
fn.apply(this, arg)
pre_time = curr_time
}
}
}
// 防抖函數(debounce)
function debounce(fn, wait = 500, immediately = true) {
let timer
return function(...arg) {
if(immediately) {
fn.apply(this, arg)
immediately = false
}
clearTimout(timer)
timer = setTimout(()=> {
fn.apply(this, arg)
}, wait)
}
}
複製代碼
一、瞭解 Promise 嗎?
二、Promise 解決的痛點是什麼?
三、Promise 解決的痛點還有其餘方法能夠解決嗎?若是有,請列舉。
四、Promise 如何使用?
五、Promise 經常使用的方法有哪些?它們的做用是什麼?如何使用?
六、Promise 在事件循環中的執行過程是怎樣的?
七、Promise 的業界實現都有哪些?
八、能不能手寫一個 Promise ?
function myPromise(constructor){
let self=this;
self.status="pending" //定義狀態改變前的初始狀態
self.value=undefined;//定義狀態爲resolved的時候的狀態
self.reason=undefined;//定義狀態爲rejected的時候的狀態
function resolve(value){
//兩個==="pending",保證了狀態的改變是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//兩個==="pending",保證了狀態的改變是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕獲構造異常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
// 來源:https://github.com/forthealllight/blog/issues/4
複製代碼
promise是異步編程的一種解決方案,解決了回調地獄的問題。Promise是一個容器保存着某個將來纔會結束的事件的結果,也能夠說是一個對象從它能夠獲取異步操做的消息。
特色:
pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)三種狀態。pending
(進行中)到fulfilled
(已成功)或pending
(進行中)到reject
(以失敗)。const promise = new Promise(function(resolve, reject) {
if(/*success*/) {
resolve(val)
} else {
reject(val)
}
})
複製代碼
Promise接受一個函數做爲參數,該函數接受兩個參數,它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。
resolve的做用是在異步操做成功的時候調用,並將異步操做的結果做爲參數傳遞出去;reject是在異步操做失敗的時候調用。
Promise實例生成以後能夠用then方法分別指定成功和失敗的回調函數。
promise.then(function() {
/*success*/
}, function() {
/*failure*/
})
複製代碼
第一個參數是成功時調用,第二個是失敗時調用,這兩個函數都接受Promise對象傳出的值做爲參數,第一個成功時的回調函數時必須的失敗時的回調函數不是必須的。
resolve
函數的參數除了正常的值之外,還多是另外一個 Promise 實例,好比像下面這樣。
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
// 這裏p2的狀態決定p1的狀態,p2後的then都是針對p1的
複製代碼
這裏p2的狀態決定p1的狀態,p2後的then都是針對p1的
Promise的具體例子:
function timeout(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms, 'done');
})
}
let p = timeout(100).then((val) => {
console.log(val)
})
複製代碼
Promise建立以後會當即執行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// promise
// Hi!
// resolved.
複製代碼
實現Ajax
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
複製代碼
then方法是定義在原型對象Promise.prototype
上的,它的做用是爲 Promise 實例添加狀態改變時的回調函數。前面說過,then
方法的第一個參數是resolved
狀態的回調函數,第二個參數(可選)是rejected
狀態的回調函數。
then方法也能夠返回一個新的Promise實例,所以能夠採用鏈式調用:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
複製代碼
用於錯誤的捕獲
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
複製代碼
上面代碼中,第二種寫法要好於第一種寫法,理由是**第二種(catch)寫法能夠捕獲前面then
方法執行中的錯誤,**也更接近同步的寫法(try/catch
)。所以,建議老是使用catch
方法,而不使用then
方法的第二個參數。
finally方法用於執行無論最後狀態如何,都會執行的操做。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製代碼
該方法用於將多個Promise實例包裝成一個新的Promise實例。
const p = Promise.all([p1, p2, p3])
複製代碼
Promise.all()接受一個數組,數組的值都是Promise對象,若是不是則會調用Promise.resolve()方法。(Promise.all
方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。)
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
注意,若是做爲參數的 Promise 實例,本身定義了catch
方法,那麼它一旦被rejected
,並不會觸發Promise.all()
的catch
方法。
Promise.race
方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3])
複製代碼
上面代碼中,只要p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。和Promise同樣,數組的值必須是promise對象,若是不是則會調用Promise.resolve()方法。
Promise.resolve()方法能夠將現有的對象轉換爲Promise對象。
Promise.resolve('foo')
// 等同於
new Promise(function(resolve) {
resolve('foo')
})
複製代碼
Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
。
let p = Promise.reject('foo')
// 等同於
new Promise((resolve, reject) => reject('foo'))
複製代碼
由於數組和對象都是引用值,因此當咱們直接使用=賦值,會是兩個對象的指針指向同一個空間,當咱們改變其中一個值的時候,另外一個對象也會受到影響。當咱們使用深拷貝從新開闢了一個內存空間,將該對象的指針指向新開闢的空間。
針對數組咱們可使用[...arr]
針對對象咱們可使用Object.assign({}, obj)、{...obj}
以上兩種都是淺拷貝
也可使用JSON.parse(JOSN.stringify(obj))
function deepClone(obj) {
let res
if(typeof obj === "object") {
res = obj.constructor = Array?[]:{}
for(let i in obj) {
res[i] = typeof obj[i] === "object"?deepClone(obj[i]):obj[i]
}
} else {
res = obj
}
return obj
}
複製代碼
JavaScript將任務分爲同步任務和異步任務,在第一次執行的時候會將整個script代碼看做宏任務,同步任務進入主線程,異步任務進入Event Table註冊,當知足條件異步任務的回調函數加入到Event Queue隊列中,當主線程空閒的時候,會從Event Queue取出對應的函數。宏任務(script、setTimeout)和微任務(Promise、process.nextTick)分別進入不一樣的Event Table,它們的執行順序不同,當主線程空閒的時候首先會清空微任務隊列,而後再拿出一個宏任務隊列的函數,而後再檢查微任務隊列,如此循環。
做用域是在運行時代碼中的特定變量的有效範圍。做用域決定了代碼區塊中變量和其餘資源的可見性。做用域內層能夠看見做用域外層,做用域外層不能看見做用域外層,因此做用域在不一樣做用域中聲明的變量不會形成污染和命名衝突。
定義在最外層的函數和變量,未經聲明就賦值的變量,window的屬性。這裏須要注意的是var聲明的全局變量以及未經聲明就賦值的變量會掛載到window屬性上,可是var聲明的變量不能刪除,未經聲明的變量能夠刪除。
當函數執行的時候就會在內部建立一個函數做用域,當函數執行完成就會銷燬該做用域。
在ES6以前是沒有塊級做用域的,ES6引入了let、const關鍵字就能夠建立塊級做用域。
當在一個函數內部搜索一個變量的時候,若是該函數沒有聲明該變量,那麼就會順着代碼執行環境建立的做用域逐層向外搜索,一直搜索到全局做用域。
解釋階段:
執行階段
JavaScript在解釋階段便會肯定做用域規則,可是執行上下文是在函數執行的前一刻。
執行上下文最明顯的就是this指向是在執行的時候肯定的。
區別:執行上下文在運行時肯定,隨時能夠改變;做用域在定義時就肯定,而且不會改變。同一做用域下,不一樣的調用會產生不一樣的執行上下文,從而產生不一樣的結果。
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域外執行。簡單講,閉包就是指有權訪問另外一個函數做用域中的變量的函數。
let a = 1
function foo() {
console.log(a)
}
function bar() {
let a = 2
foo()
}
bar() // 1
複製代碼
let a = 'window'
function foo() {
let a = 'foo'
return function() {
console.log(a)
}
}
let bar = foo()
bar() // foo
複製代碼
dom.onclick = function(){}
dom.addEventListenner('click',function(){})
dom.addEventListenner('keyup',fuction(){})
,增長了事件類型dom.addEventListenner('keyup',fuction(){}, false|true)
,第三個參數爲false表示在冒泡階段觸發,第三個參數爲true表示在捕獲階段觸發。先捕獲後冒泡。
dom.addEventListenner('keyup',fuction(){}, true)
dom.addEventListenner('keyup',fuction(){}, false)
事件委託就是基於事件冒泡的,當子元素觸發點擊事件會冒泡到父元素,而後經過e.target來判斷子元素。
經過冒泡或者捕獲怎麼到達目標對象的階段?
事件首先經過捕獲到達目標元素,再經過目標元素冒泡到window對象,即先捕獲後冒泡。
參考:www.jianshu.com/p/71bb3cf19…
// 1.第一種
// 定義
let eve = new Event('coustome')
// 綁定
dom.addEventListenner('coustome', function(){})
// 觸發
dom.dispatch(eve)
// 2.第二種,能夠添加數據
let eve1 = new CustomoeEvent('coustome', {data})
複製代碼
__proto__
屬性指向函數的prototype
屬性function New(fn, ...arg) {
let res = {}
if(fn.prototype !== null) {
res = Object.create(fn.prototype)
}
let ret = fn.apply(res, arg)
if(ret === "object" || ret === "function" && ret !== null) {
return ret
}
retrun res
}
複製代碼
語法:let str = new String('hello world')
當咱們聲明一個字符串變量的時候let str1 = 'hello'
,這是字面量的形式,而且是一個不可變的值。咱們訪問str1.length
屬性、或其餘屬性的時候,就會把該變量轉換成爲一個String對象(這裏一般叫作包裝類),由於聲明的字符串沒有該屬性,只有轉換爲包裝類纔有。在JavaScript中會把字符串字面量轉化成String對象。
null在數值轉換時被轉換爲0,undefined會被轉換爲NaN
undefined只有一個值,即undefined。如下狀況會出現undefined:
null也只有一個值,可是當咱們執行typeof null
的時候,會返回object。咱們能夠理解爲null是一個空指針對象,尚未保存對象。如下幾種狀況會使用出現null:
不能區別null、對象、數組、正則表達式等
是基於原型鏈操做的:A instanceof B,判斷A的原型鏈上有沒有B的原型
比較好的方法,可是IE6/7/8中 Object.prototype.toString.apply(null)返回「[object Object]」。
對象聲明可使用字面量形式和構造函數形式
let obj = {}
let obj1 = new Object()
複製代碼
這兩種方法生成的對象是同樣的,區別在於字面量形式能夠添加多個鍵值對、構造函數形式只能逐個添加。
JavaScript還有一些對象子類型,一般被稱爲內置對象。
對象中的值一般不會存儲在對象內部,一般狀況下,存儲在對象容器內部的是這些屬性的名稱,它們就像指針同樣,指向這些值的真正存儲位置。
let obj = {
a:1
}
obj.a
obj['a']
複製代碼
obj.a,的語法被稱爲屬性訪問,obj['a']的方法被稱爲鍵訪問,它們在大都數狀況下是能夠互換的,區別在於.a要符合命名的規範性,['a']能夠接受任意的UTF-8/Unicode字符做爲屬性名。好比"super-Fun!",這時候就不可使用屬性訪問了。
注意:在對象中屬性名永遠都是字符串,若是不是者會被轉換爲字符串。
let a = "foo"
let obj = {
[a + '1']: 'hello',
[a + '2']: 'hello2'
}
複製代碼
一、查看屬性描述符:Object.getOwnPropertyDescriptor(obj, props)
語法:
let myObj = {
a: 1
}
console.log(Object.getOwnPropertyDescriptor(myObj, 'a'))
複製代碼
二、設置屬性描述符:Object.defineProperty(obj, props)
語法:
Object.defineProperty(myObj, 'b', {
value:2,
writable: false,
configurable: true,
enumerable: true
})
複製代碼
因此咱們經過設置writable,configurable爲false來設置一個對象常量。
一、經過設置writable,configurable爲false來設置一個對象常量。
二、禁止拓展:Object.preventzectensions(obj)
語法:
let myObj1 = {
a: 1
}
Object.preventExtensions(myObj1)
myObj1.b = 2
myObj1.b // undefined
複製代碼
三、密封:Object.seal(obj)
實際上這個方法會調用Object.preventzectensions(obj)方法,並將現有屬性的configurable設爲false,因此密封以後既不能添加新的屬性,也不能刪除和配置現有屬性。
四、凍結:Object.freeze(obj)
這個方法會調用Object.seal()方法,並將現有屬性的writable設爲false,故既不能添加新的屬性,也不能刪除、配置、修改現有屬性。
get、set會劫持你對對象數據的操做。
let data = {}
Object.defineProperty(data, 'key', {
// value: 1,
enumerable: true,
configurable: false, // 不能再定義
get: function () {
// Dep.target && dep.addDep(Dep.target)
return this.value
},
set: function (newVal) {
if (newVal === this.value) {
return
}
console.log(`發生了變化${this.value}=>${newVal}`)
this.value = newVal
// dep.notify() // 通知全部訂閱者
}
})
複製代碼
一、in:檢查對象及原型鏈
二、hasOwnProperty()
具備length屬性,能夠經過數字下標訪問元素,如arguments、獲取的DOM節點。Array.from(arguments)能夠將一個類數組轉化爲數組
// es6最簡單的方式
[...new Set(arr)]
function unique(arr) {
let list = [...arr]
let res = []
list.forEach(item => {
if(!res.include(item)) {
res.push(item)
}
})
return res
}
複製代碼