原型原型鏈
- 參照 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
}
}