求職之手寫程式碼-手寫原始碼大雜燴
自定義事件
面試官:手寫一個自定義原生事件。
簡單三步曲:
- 建立自定義事件:
const myEvent = new Event('jsliangEvent')
- 監聽自定義事件:
document.addEventListener(jsliangEvent)
- 觸發自定義事件:
document.dispatchEvent(jsliangEvent)
簡單實現:
window.onload = function() {
const myEvent = new Event('jsliangEvent');
document.addEventListener('jsliangEvent', function(e) {
console.log(e);
})
setTimeout(() => {
document.dispatchEvent(myEvent);
}, 2000);
};
複製程式碼
頁面 2 秒後自動觸發 myEvent
事件。
建立自定義事件
建立自定義事件的 3 種方法:
- 使用
Event
let myEvent = new Event('event_name');
複製程式碼
- 使用
customEvent
(可以傳引數)
let myEvent = new CustomEvent('event_name', {
detail: {
// 將需要傳遞的引數放到這裡
// 可以在監聽的回撥函式中獲取到:event.detail
}
});
複製程式碼
- 使用
document.createEvent('CustomEvent')
和initEvent()
// createEvent:建立一個事件
let myEvent = document.createEvent('CustomEvent'); // 注意這裡是 CustomEvent
// initEvent:初始化一個事件
myEvent.initEvent(
// 1. event_name:事件名稱
// 2. canBubble:是否冒泡
// 3. cancelable:是否可以取消預設行為
)
複製程式碼
事件的監聽
自定義事件的監聽其實和普通事件一樣,通過 addEventListener
來監聽:
button.addEventListener('event_name', function(e) {})
複製程式碼
事件的觸發
觸發自定義事件使用 dispatchEvent(myEvent)
。
注意,這裡的引數是要自定義事件的物件(也就是 myEvent
),而不是自定義事件的名稱(myEvent
)
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定義事件</title>
</head>
<body>
<button class="btn">點我</button>
<script>
window.onload = function() {
// 方法 1
const myEvent = new Event('myEvent');
// 方法 2
// const myEvent = new CustomEvent('myEvent', {
// detail: {
// name: 'jsliang',
// },
// });
// 方法 3
// const myEvent = document.createEvent('CustomEvent');
// myEvent.initEvent('myEvent', true, true);
const btn = document.querySelector('.btn');
btn.addEventListener('myEvent', function(e) {
console.log(e);
})
setTimeout(() => {
btn.dispatchEvent(myEvent);
}, 2000);
};
</script>
</body>
</html>
複製程式碼
上面 console.log(e)
輸出:
/*
CustomEvent {
bubbles: true
cancelBubble: false
cancelable: true
composed: false
currentTarget: null
defaultPrevented: false
detail: null
eventPhase: 0
isTrusted: false
path: (5) [button.btn, body, html, document, Window]
returnValue: true
srcElement: button.btn
target: button.btn
timeStamp: 16.354999970644712
type: "myEvent"
}
*/
複製程式碼
Object.create()
Object.create()
方法建立一個新物件,使用現有的物件來提供新建立的物件的 __proto__
。
function create(proto) {
function F() {};
F.prototype = proto;
return new F();
}
複製程式碼
試驗一下:
function create(proto) {
function F() {};
F.prototype = proto;
return new F();
}
const Father = function() {
this.bigName = '爸爸';
};
Father.prototype.sayHello = function() {
console.log(`我是你${this.bigName}`);
}
const Child = function() {
Father.call(this);
this.smallName = '兒子';
}
Child.prototype = create(Father.prototype);
Child.prototype.constructor = Child;
const child = new Child();
child.sayHello(); // 我是你爸爸
複製程式碼
下面講寄生組合式繼承會用到 Object.create()
。
ES5 實現類繼承
使用 ES5 實現繼承,簡要在 3 行程式碼:
Father.call(this)
。在Child
中通過Father.call(this)
,將Father
的this
修改為Child
的this
Child.prototype = Object.create(Father.prototype)
。將Child
的原型鏈繫結到Father
的原型鏈上。Child.prototype.constructor = Child
。這個建構函式的例項的構造方法constructor
指向自身。
const Father = function (name, like) {
this.name = name;
this.like = like;
this.money = 10000000;
};
Father.prototype.company = function() {
console.log(`${this.name} 有 ${this.money} 元`);
}
const Children = function (name, like) {
Father.call(this);
this.name = name;
this.like = like;
}
Children.prototype = Object.create(Father.prototype);
Children.prototype.constructor = Children;
const jsliang = new Children('jsliang', '學習');
console.log(jsliang); // Children {name: "jsliang", like: "學習", money: 10000000}
jsliang.company(); // jsliang 有 10000000 元
複製程式碼
需要注意 Child.prototype = Object.create(Father.prototype)
這句話:
- 這一步不用
Child.prototype = Father.prototype
的原因是怕共享記憶體,修改父類原型物件就會影響子類 - 不用
Child.prototype = new Parent()
的原因是會呼叫 2 次父類的構造方法(另一次是call
),會存在一份多餘的父類例項屬性 Object.create
是建立了父類原型的副本,與父類原型完全隔離
最後,這種繼承方法,叫做 寄生組合式繼承。
instanceof
面試官:手寫一個 instanceof
,其實 instanceof
就是查詢原型鏈的過程.
那麼有下面程式碼:
const Father = function() {
this.bigName = '爸爸';
};
Father.prototype.sayHello = function() {
console.log(`我是你${this.bigName}`);
}
const Child = function() {
Father.call(this);
this.smallName = '兒子';
}
Child.prototype = Object.create(Father.prototype);
Child.prototype.constructor = Child;
const child = new Child();
child.sayHello(); // 我是你爸爸
console.log(child instanceof Child); // true
console.log(child instanceof Father); // true
console.log(child instanceof Object); // true
複製程式碼
如何改造當中的 instanceof
呢?
function instanceOf(a, b) {
let proto = a.__proto__;
const prototype = b.prototype;
// 從當前 __proto__ 開始查詢
while (proto) {
// 如果找到 null 還沒有找到,返回 false
if (proto === null) {
return false;
}
// 如果 a.__proto__.xxx === b.prototype,返回 true
if (proto === prototype) {
return true;
}
// 進一步迭代
proto = proto.__proto__;
}
}
console.log(instanceOf(child, Child)); // true
console.log(instanceOf(child, Father)); // true
console.log(instanceOf(child, Object)); // true
複製程式碼
輸出結果同 instanceof
一樣,完成目標!
才怪!!!
經過測試:
let num = 123;
console.log(num instanceof Object); // false
console.log(instancOf(123, Object)); // true
複製程式碼
為什麼呢?因為 instanceof
在原生程式碼上,實際是做了基本型別的檢測,基本型別應該返回 false
,所以可以進行改造:
function instanceOf(a, b) {
// 新增:通過 typeof 判斷基本型別
if (typeof a !== 'object' || b === null) {
return false;
}
// 新增:getPrototypeOf 是 Object 自帶的一個方法
// 可以拿到引數的原型物件
let proto = Object.getPrototypeOf(a);
const prototype = b.prototype;
while (proto) {
if (proto === null) {
return false;
}
if (proto === prototype) {
return true;
}
proto = proto.__proto__;
}
}
複製程式碼
柯里化
實現一個 add
方法,使計算結果能夠滿足以下預期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
複製程式碼
實現方法:
function add () {
const numberList = Array.from(arguments);
// 進一步收集剩餘引數
const calculate = function() {
numberList.push(...arguments);
return calculate;
}
// 利用 toString 隱式轉換,最後執行時進行轉換
calculate.toString = function() {
return numberList.reduce((a, b) => a + b, 0);
}
return calculate;
}
// 實現一個 add 方法,使計算結果能夠滿足以下預期
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2, 3)(4)); // 10;
console.log(add(1)(2)(3)(4)(5)); // 15;
複製程式碼
詳細看 JavaScript 系列的閉包篇章,裡面有講解到閉包和柯里化。
迭代器
迭代器的意思是:我的版本是可控的,你踢我一下,我動一下。
// 在資料獲取的時候沒有選擇深拷貝內容
// 對於引用型別進行處理會有問題
// 這裡只是演示簡化了一點
function Iterdtor(arr) {
let data = [];
if (!Array.isArray(arr)) {
data = [arr];
} else {
data = arr;
}
let length = data.length;
let index = 0;
// 迭代器的核心 next
// 當呼叫 next 的時候會開始輸出內部物件的下一項
this.next = function () {
let result = {};
result.value = data[index];
result.done = index === length - 1 ? true : false;
if (index !== length) {
index++;
return result;
}
// 當內容已經沒有了的時候返回一個字串提示
return 'data is all done';
};
}
const arr = [1, 2, 3];
// 生成一個迭代器物件
const iterdtor = new Iterdtor(arr);
console.log(iterdtor.next()); // { value: 1, done: false }
console.log(iterdtor.next()); // { value: 2, done: false }
console.log(iterdtor.next()); // { value: 2, done: true }
console.log(iterdtor.next()); // data is all done
複製程式碼
Ajax
通過 Promise
實現 ajax
:
index.json
{
"name": "jsliang",
"age": 25
}
複製程式碼
index.js
const getData = (url) => {
return new Promise((resolve, reject) => {
// 設定 XMLHttpRequest 請求
const xhr = new XMLHttpRequest();
// 設定請求方法和 url
xhr.open('GET', url);
// 設定請求頭
xhr.setRequestHeader('Accept', 'application/json');
// 設定請求的時候,readyState 屬性變化的一個監控
xhr.onreadystatechange = (res) => {
// 如果請求的 readyState 不為 4,說明還沒請求完畢
if (xhr.readyState !== 4) {
return;
}
// 如果請求成功(200),那麼 resolve 它,否則 reject 它
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
};
// 傳送請求
xhr.send();
})
};
getData('./index.json').then((res) => {
console.log(res); // { "name": "jsliang", "age": 25 }
})
複製程式碼
補充:Ajax 狀態
- 0 - 未初始化。尚未呼叫
open()
方法 - 1 - 啟動。已經呼叫
open()
方法,但尚未呼叫send()
方法。 - 2 - 傳送。已經呼叫
send()
方法,但尚未接收到響應。 - 3 - 接收。已經接收到部分響應資料。
- 4 - 完成。已經接收到全部響應資料,而且已經可以在客戶端使用了。
陣列扁平化
方法一:手撕遞迴
const jsliangFlat = (arr) => {
// 1. 設定空陣列
const result = [];
// 2. 設定遞迴
const recursion = (tempArr) => {
// 2.1 遍歷陣列
for (let i = 0; i < tempArr.length; i++) {
// 2.2 如果陣列裡面還是一個陣列,那麼遞迴它
if (Array.isArray(tempArr[i])) {
recursion(tempArr[i]);
} else { // 2.3 否則新增它
result.push(tempArr[i]);
}
}
};
recursion(arr);
// 3. 返回結果
return result;
};
console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]
複製程式碼
方法二:flat()
flat
方法可以扁平陣列,如果不傳引數,flat()
扁平一層,flat(2)
扁平 2 層,到 flat(Infinity)
扁平所有層。
const jsliangFlat = (arr) => {
return arr.flat(Infinity);
};
console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]
複製程式碼
注意這個方法在 Node
執行會報錯,這是一個 ES6 的方法。
方法三:reduce
不推薦 reduce
,我怕小夥伴看得頭暈。
const jsliangFlat = (arr = []) => {
return arr.reduce((prev, next) => {
if (Array.isArray(next)) {
return prev.concat(jsliangFlat(next));
} else {
return prev.concat(next);
}
}, [])
};
console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]
複製程式碼
物件扁平化
其實我也不知道這個有沒考,也不是很難:
const obj = {
a: {
b: {
c: 1,
d: 2,
},
e: 3,
},
f: {
g: 4,
h: {
i: 5,
},
},
};
// 1. 設定結果集
const result = [];
// 2. 遞迴
const recursion = (obj, path = []) => {
// 2.1 如果到底部,此時 obj 是對應的值
if (typeof obj !== 'object') {
// 2.1.1 結果集加上這個欄位
result.push({
[path.join('.')]: obj,
})
// 2.1.2 終止遞迴
return;
}
// 2.2 遍歷 obj 物件
for (let i in obj) {
// 2.2.1 判斷物件自身是否含有該欄位(排除原型鏈)
if (obj.hasOwnProperty(i)) {
// 2.2.2 回溯,新增路徑
path.push(i);
// 2.2.3 進一步遞迴
recursion(obj[i], path);
// 2.2.4 回溯,刪除路徑,方便下一次使用
path.pop();
}
}
};
recursion(obj);
// 3. 返回結果
console.log(result);
/*
[
{ 'a.b.c': 1 },
{ 'a.b.d': 2 },
{ 'a.e': 3 },
{ 'f.g': 4 },
{ 'f.h.i': 5 },
]
*/
複製程式碼
還有反向推題:
- 根據
obj
和路徑path
(a.b.c
),找到它的值
遞迴一下就行了,或者迭代也可以,不難。
寫不出來的小哥反省下,寫不出來的小姐姐找我,教你啊~ /手動狗頭防暴力
陣列去重
看著本文標題是 3 種,實際上有 5 種。
方法一:手撕去重
const jsliangSet = (arr) => {
// 設定結果
const result = [];
// 遍歷陣列
for (let i = 0; i < arr.length; i++) {
// 如果結果集不包含這個元素
// 這裡也可以用 result.indexOf(arr[i]) === -1
// 或者 arr.lastIndexOf(arr[i]) === i
if (!result.includes(arr[i])) {
result.push(arr[i]);
}
}
// 返回結果
return result;
};
console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]
複製程式碼
方法二:Set
const jsliangSet = (arr) => {
return [...new Set(arr)];
};
console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]
方法三:filter
同樣,通過 filter
也可以,其實核心也是 lastIndexOf
和當前索引值的一個比對。
const jsliangSet = (arr) => {
return arr.filter((item, index) => {
return arr.lastIndexOf(item) === index;
})
};
console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]
複製程式碼
其他
其他的還有:
- 釋出訂閱模式:Node 回撥函式、Vue event bus
- 非同步併發數限制
- 非同步序列|非同步並行
- 圖片懶載入
- 滾動載入
- 陣列 API 實現:
filter
、map
、forEach
、reduce
- 大資料渲染(渲染幾萬條資料不卡頁面)
- JSON:
JSON.parse()
、JSON.stringify()
相關文章
- 前端面試之手寫程式碼前端面試
- 前端筆試之手寫程式碼(一)前端筆試
- 手寫Vuex原始碼Vue原始碼
- 手寫@koa/router原始碼原始碼
- 手寫ArrayList核心原始碼原始碼
- 手寫 ArrayList 核心原始碼原始碼
- 手寫Koa.js原始碼JS原始碼
- 手寫Express.js原始碼ExpressJS原始碼
- 手寫Redux-Saga原始碼Redux原始碼
- 《四 spring原始碼》手寫springmvc原始碼SpringMVC
- 手寫 Java HashMap 核心原始碼JavaHashMap原始碼
- js手寫程式碼合集JS
- 大雜燴
- 《四 spring原始碼》手寫springioc框架Spring原始碼框架
- JS面試手寫程式碼JS面試
- JS 筆試手寫程式碼JS筆試
- mysql 大雜燴MySql
- redis 大雜燴Redis
- 手寫一個Promise,附原始碼分析Promise原始碼
- 🐒編寫高質量程式碼(手撕程式碼)
- zookeeper原始碼 — 五、處理寫請求過程原始碼
- 手寫Struts,帶你深入原始碼中心解析原始碼
- 手寫Spring ioc 框架,狠狠的“Spring 原始碼Spring框架原始碼
- 手寫Vue2.0原始碼(八)-元件原理Vue原始碼元件
- Spring原始碼系列:初探底層,手寫SpringSpring原始碼
- JavaScript手寫程式碼無敵祕籍JavaScript
- 手寫程式碼之 【釋出訂閱】
- 手寫雜湊表
- .Net Core——用程式碼寫程式碼?
- 日常筆記大雜燴筆記
- Binder + AMS + AIDL大雜燴AI
- 網路流大雜燴
- 吉司機大雜燴
- 運維-技能大雜燴運維
- 《四 spring原始碼》利用TransactionManager手寫spring的aopSpring原始碼
- Spring學習之——手寫Mini版Spring原始碼Spring原始碼
- Android多程式之手動編寫Binder類Android
- 手寫指令碼程式碼太累!搞一個生成工具吧指令碼