最後億遍js總結
阿新 • • 發佈:2021-07-14
原型原型鏈
- 參照 js原型與原型鏈解析
繼承
- 參照 js的幾種繼承方式
call,apply,bind用法及實現
- 作用:簡單來說就是改變
this
的指向,在一個物件中呼叫另一個物件的方法
用法
apply
與call
的用法相似,會直接執行函式
A.sayName.call(B,'tom')
A.sayName.apply(B,['tom'])
bind
用法與call
相似,不同的是,她不會立即執行,他會返回原函式的拷貝
let bSay = A.sayName.bind(B,'tom') //拷貝
bSay()//呼叫
實現
//A.myCall(B,arg1,arg2) //掛載到Function 的原型物件上,以便繼承給函式物件呼叫 Function.prototype.myCall = function (originFun, ...args) { //A點出的myCall,這裡this 指向A函式 console.log(this) //在B裡面定義一個私有屬性指向A函式 originFun.__thisFn__ = this //B呼叫A函式,this指向B let result = originFun.__thisFn__(...args) //刪除無用屬性 delete originFun.__thisFn__ return result }
判斷型別的方法
-
typeof
與instanceof
-
Object.prototype.toString.call()
;每個物件都存在toString()
方法,預設情況下,被每個Object
物件繼承,如果未被覆蓋,會返回[object type]
,type
是物件的型別。比如:let obj= new Object() obj.toString() //輸出 "[object Object]"
但是對於其他情況
var arr =new Array(1,2) arr.toString() // 輸出 "1,2"
這是因為所謂的
Array
、String
等型別在繼承於基類Object
時,重寫了toString
Array
上面的toString
就停止查詢,直接呼叫了,所以輸出與預想的有偏差,那如何讓arr
去呼叫Object
上面的該方法呢?顯而易見借用call()
Object.prototype.toString.call(arr) // 輸出 "[object Array]"
然後在利用
slice
方法截取出來型別// 是否字串 export const isString = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'String' // 是否數字 export const isNumber = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Number' // 是否boolean export const isBoolean = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Boolean' // 是否函式 export const isFunction = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Function' // 是否為null export const isNull = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Null' // 是否undefined export const isUndefined = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Undefined' // 是否物件 export const isObj = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Object' // 是否陣列 export const isArray = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Array' // 是否時間 export const isDate = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Date' // 是否正則 export const isRegExp = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'RegExp' // 是否錯誤物件 export const isError = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Error' // 是否Symbol函式 export const isSymbol = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Symbol' // 是否Promise物件 export const isPromise = (o) => Object.prototype.toString.call(o).slice(8, -1) === 'Promise'
捕獲與冒泡
- 事件捕獲與冒泡過程
捕獲
-
事件捕獲階段,會由外到內,一層一層的檢查是否註冊了事件
-
如何在捕獲階段註冊事件?
- 使用
addEventListener
註冊事件,第三個引數代表是否在捕獲階段處理事件,設定為true
<div class="box"> <button class="btn">按鈕</button> </div> <script> //先顯示div 後顯示btn const box = document.querySelector('.box') box.addEventListener( 'click', () => { alert('div被點選了') }, true ) const btn = document.querySelector('.btn') btn.addEventListener( 'click', () => { alert('button被點選了') }, true ) </script>
- 使用
冒泡
-
與捕獲相反,在事件冒泡階段,會從裡到外來檢查是否註冊事件
-
預設情況下,所有的事件都是在冒泡階段進行註冊,如何阻止事件冒泡?
-
w3標準
event.stopPropagation()
-
IE瀏覽器
event.cancelBubble=true
-
事件委託
- 多個元素註冊了相同的事件,可以把這些事件委託到它的父元素上,比較常見的是
ul
裡面的li
標籤點選事件
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const ul = document.querySelector('ul')
ul.addEventListener('click', (e) => {
if (e.target.nodeName == 'LI') {
alert(e.target.innerText)
}
})
</script>
閉包問題
- 在一個內層函式中訪問到外層函式的作用域
隔離作用域
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
// Counter1與Counter2互不影響,在閉包內修改變數,不會影響到另一個閉包中的變數
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
經典for迴圈閉包問題
for (var i = 1, arr = []; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 500)
}// 5,5,5,5
//使用閉包
for (var i = 1, arr = []; i < 5; i++) {
;(function (i) {
setTimeout(() => {
console.log(i)
}, 1000 * i)
})(i)
}//1,2,3,4
new物件時發生了什麼,實現一個new
const myNew = (constructorFn, ...args) => {
// 建立一個新的物件
let obj = {}
// 建立一個私有屬性,指向建構函式的原型物件
obj.__proto__ = constructorFn.prototype
// 執行建構函式
constructorFn.call(obj, ...args)
//返回這個物件
return obj
}
function Person(name, age) {
this.name = name
this.age = age
}
const Per1 = new Person('Tom', 12)
console.log('Per1', Per1)
const Per2 = myNew(Person, 'Tom', 12)
console.log('Per2',Per2)
防抖與節流
閉包
深淺拷貝
- 基本型別
String
、Number
等都是按值訪問的,它的值儲存在棧中 - 引用型別都是 按引用訪問的,它將引用地址儲存在棧上,然後再去堆上開闢空間存放他們的值
淺拷貝
- 如果是基本型別,就直接拷貝基本型別的值,如果是引用型別,就拷貝引用型別的引用地址
- 淺拷貝對於基本型別來說,他們之間改變值不會相互影響,但是對於引用型別來說,由於拷貝的是地址,這些地址最終都指向同一個堆裡面,所以會互相影響
實現方式
- ES6展開運算子
let obj1 ={name:'tom',age:12,addr:{lng:26.0,lag:45.0},friends:['john','jerry']}
let obj2={...obj1}
obj2.name='jerry'
obj2.frinds[0]='tom'
ES6展開運算子對於不同結構的資料,存在不同的表現.對於一維的物件或者陣列,進行的深拷貝,對於多維的物件或者陣列進行的是淺拷貝
- Object.assign()
let obj1 ={name:'tom',age:12,addr:{lng:26.0,lag:45.0},friends:['john','jerry']}
let obj2= Object.assign({}, obj1);
與解構賦值的作用一樣
- lodash.clone()
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
深拷貝
- 主要針對於引用型別,他會在對上面重新開闢一空間存放物件,這樣兩個深拷貝的物件就不會互相影響了
實現方式
- JSON.parse(JSON.stringfy() )
let obj1 ={name:'tom',friends:['jerry','john']}
let obj2 =JSON.parse(JSON.stringify(obj1))
obj2.name='jerry'
obj2.friends[0]='tom'
- lodash.cloneDeep()
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
- 遞迴遍歷實現
function clone(targetObj) {
//判斷是否未物件,是物件去遍歷拷貝
if (typeof targetObj === 'object') {
//判斷源資料是物件還是陣列
let cloneTarget = Array.isArray(targetObj) ? [] : {}
for (const key in targetObj) {
//遞迴拷貝
cloneTarget[key] = clone(targetObj[key])
}
return cloneTarget
} else {
//不是物件直接返回
return targetObj
}
}