前端工程師進階之旅-手撕程式碼
主要包括一些工作中常用的方法,面試常問到的方法。還有一些不太瞭解,趁機深入瞭解的知識點。
廢話少說,直接幹程式碼就完事了。
資料型別判斷
使用 Object.prototype.toString 實現。
function myTypeof(data) {
return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
}
陣列去重
//new Set() 集合
//ES6提供了新的資料結構Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
function unique(arr) {
return [...new Set(arr)];
}
//陣列去重 filter
function unique(arr) {
return arr.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
// 陣列去重 forEach
function unique(arr) {
let res = [];
arr.forEach((item) => {
res.includes(item) ? "" : res.push(item);
});
return res;
}
陣列扁平化
//使用 Infinity,可展開任意深度的巢狀陣列
arr.flat(Infinity);
//適用JSON轉換
JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g, "") + "]");
//遞迴
function myFlat(arr) {
let res = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
res = res.concat(myFlat(item));
} else {
res.push(item);
}
});
return res;
}
//some
function myFlat(arr) {
while (arr.some((res) => Array.isArray(res))) {
arr = [].concat(...arr);
}
return arr;
}
深拷貝深克隆
// 簡單克隆 無法複製函式
var newObj = JSON.parse(JSON.stringify(obj));
// 深克隆 無法克隆特殊例項 Date等
function deepClone(target) {
if (typeof target !== "object") {
return target;
}
var result;
if (Object.prototype.toString.call(target) == "[object Array]") {
// 陣列
result = [];
} else {
// 物件
result = {};
}
for (var prop in target) {
if (target.hasOwnProperty(prop)) {
result[prop] = deepClone(target[prop]);
}
}
return result;
}
//複雜版深克隆
function deepClone(target) {
if (typeof target !== "object") return target;
// 檢測RegDate型別建立特殊例項
let constructor = target.constructor;
if (/^(RegExp|Date)$/i.test(constructor.name)) {
return new constructor(target);
}
// 判斷型別
var result =
Object.prototype.toString.call(target) == "[object Array]" ? [] : {};
// 迭代迴圈
for (var prop in target) {
if (target.hasOwnProperty(prop)) {
// 遞迴
result[prop] = deepClone(target[prop]);
}
}
return result;
}
繼承方法
原型鏈繼承:
// 原型鏈繼承
// 問題:原型中的引用物件會被所有的例項共享,子類在例項化的時候不能給父類建構函式傳參
function Father() {
this.hobby = ["coding", "eat"];
}
Father.prototype.skill = function () {
console.log("i will javascript");
};
function Son() {}
Son.prototype = new Father();
var father = new Father();
var son = new Son();
var son1 = new Son();
console.log(father.hobby); //[ 'coding', 'eat' ]
father.hobby.push("play");
console.log(father.hobby, son.hobby, son1.hobby);
//[ 'coding', 'eat', 'play' ] [ 'coding', 'eat' ] [ 'coding', 'eat' ]
son.hobby.push("hei");
console.log(father.hobby, son.hobby, son1.hobby);
//[ 'coding', 'eat', 'play' ] [ 'coding', 'eat', 'hei' ] [ 'coding', 'eat', 'hei' ]
son.skill(); //i will javascript
借用建構函式實現繼承
// 原型鏈繼承
// 問題:方法需要定義在建構函式內,因此每次建立子類例項都會建立一邊方法
function Father(name) {
this.name = name;
this.sayNmae = function () {
return this.name;
};
}
function Son(name) {
Father.call(this, name);
}
Son.prototype = new Father();
var father = new Father("wenbo");
var son = new Son("zhijian");
console.log(father.name, son.name); //wenbo zhijian
console.log(father.sayNmae(), son.sayNmae()); //wenbo zhijian
組合繼承
//組合繼承,結合原型鏈繼承和借用建構函式,使用原型鏈繼承原型上的屬性和方法,借用建構函式繼承例項屬性。
//即可以把方法定義在原型上實現重用,又可以讓每個例項都有自己的屬性
// 組合繼承
function Father(name) {
this.name = name;
}
Father.prototype.sayName = function () {
return this.name;
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
var son = new Son("yewen", 18);
console.log(son); //Son { name: 'yewen', age: 18 }
console.log(son.sayName()); //yewen
寄生式組合繼承
//寄生組合繼承
// 組合繼承會導致呼叫兩次父類建構函式
function Father(name) {
this.name = name;
}
Father.prototype.sayName = function () {
return this.name;
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
class 實現繼承
// calss繼承
class Father {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Son extends Father {
constructor(name, age) {
super(name);
this.age = age;
}
getAge() {
return this.age;
}
}
var son = new Son("heihei", 18);
console.log(son); //Son { name: 'heihei', age: 18 }
console.log(son.getName(), son.getAge()); //heihei 18
事件匯流排(釋出訂閱模式)
class EventEmitter {
constructor() {
this.cache = {};
}
//新增訂閱
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn);
} else {
this.cache[name] = [fn];
}
}
//刪除訂閱
off(name, fn) {
let tasks = this.cache[name];
if (tasks) {
const index = tasks.findIndex((f) => f === fn || f.callback === fn);
index >= 0 ? tasks.splice(index, 1) : "";
}
}
//釋出事件
emit(name, once, ...args) {
if (this.cache[name]) {
// 建立副本
let tasks = this.cache[name].slice();
for (const fn of tasks) {
fn(...args);
}
once ? delete this.cache[name] : "";
}
}
}
let demo = new EventEmitter();
demo.on("wenbo", function (data) {
console.log("wenbo", data);
});
let fn1 = function (data) {
console.log("hello:", data);
};
demo.on("wenbo", fn1);
demo.emit("wenbo", false, "world");
demo.off("wenbo", fn1);
demo.emit("wenbo", false, "world");
//wenbo world
//hello: world
//wenbo world
防抖函式
function debounce(fun, wait) {
var timeId = null;
return function () {
var _this = this;
var _arg = arguments;
clearTimeout(timeId);
timeId = setTimeout(function () {
fun.apply(_this, _arg);
}, wait);
};
}
節流函式
function throttle(fun, wait) {
var lastTime = 0;
return function () {
var _this = this;
var _arg = arguments;
var nowTime = new Date().getTime();
if (nowTime - lastTime > wait) {
fun.apply(_this, _arg);
lastTime = nowTime;
}
};
}
圖片載入優化懶載入
// 獲取全部img元素 並且將類陣列轉化成陣列
let imgList = [...document.querySelectorAll("img")];
let len = imgList.length;
// 圖片懶載入
function imgLazyLoad() {
let count = 0;
return (function () {
let isLoadList = [];
imgList.forEach((item, index) => {
let h = item.getBoundingClientRect();
// 判斷圖片是否快要滾動道可視區域
if (h.top < window.innerHeight + 200) {
item.src = item.dataset.src;
console.log(item.dataset.src);
isLoadList.push(index);
count++;
// 全部載入 移出scroll事件
if (len == count) {
document.removeEventListener("scroll", imgLazyLoad);
}
}
});
// 移出已經載入完成的圖片
imgList = imgList.filter((img, index) => !isLoadList.includes(index));
})();
}
// 節流函式
function throttle(fun, wait) {
var lastTime = 0;
return function () {
var _this = this;
var _arg = arguments;
var nowTime = new Date().getTime();
if (nowTime - lastTime > wait) {
fun.apply(_this, _arg);
lastTime = nowTime;
}
};
}
// 預設執行一次載入首屏圖片
imgLazyLoad();
// 節流執行
document.addEventListener("scroll", throttle(imgLazyLoad, 200));
管理操作Cookie
var cookie = {
//設定cookie
set: function (name, value, time) {
document.cookie = `${name}=${value};expires=${time};path=/`;
return this;
},
//獲取cookie
get: function (name) {
var arr;
var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
if ((arr = document.cookie.match(reg))) {
return unescape(arr[2]);
} else {
return null;
}
},
//移出token
remove: function (name) {
return this.setCookie(name, "", -1);
},
};
封裝 myForEach 方法
// thisValue 可選引數。當執行回撥函式 callback 時,用作 this 的值。
Array.prototype.myForEach = function (callback, thisValue) {
var _this
// 當this為空丟擲異常
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// var len = this.length
// this.length >>> 0 相當於 所有非數值轉換成0 ,所有大於等於 0 等數取整數部分
var len = this.length >>> 0
// callback不是函式時 丟擲異常
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 判斷是夠有傳參 thisValue
if (arguments.length > 1) {
_this = thisValue
}
// 迴圈遍歷
for (var i = 0; i < len; i++) {
// 回撥函式
callback.call(_this, this[i], i, this)
}
}
封裝 myFilter 方法
Array.prototype.myFilter = function (callback, thisValue) {
var _this
var arr = []
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var len = this.length >>> 0
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
_this = thisValue
}
for (var i = 0; i < len; i++) {
callback.call(_this, this[i], i, this) && arr.push(this[i])
}
return arr
}
封裝 myMap 方法
Array.prototype.myMAp = function (callback, thisValue) {
var _this
var arr = []
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var len = this.length >>> 0
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
_this = thisValue
}
for (var i = 0; i < len; i++) {
arr.push(callback.call(_this, this[i], i, this))
}
return arr
}
封裝 myEvery 方法
Array.prototype.myEvery = function (callback, thisValue) {
var _this
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var len = this.length >>> 0
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
_this = thisValue
}
for (var i = 0; i < len; i++) {
if (!callback.call(_this, this[i], i, this)) {
return false
}
}
return true
}
封裝 myReduce 方法
Array.prototype.myEvery = function (callback, initialValue) {
var value = 0
console.log(value)
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var len = this.length >>> 0
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
value = initialValue
}
for (var i = 0; i < len; i++) {
value = callback(value, this[i], i, this)
}
return value
}
獲取 URL 引數返回物件
function getURLParam(url) {
let obj = {};
url.replace(/(?<=[?|&])(\w+)=(\w+)/g, function (data, key, value) {
if (obj[key]) {
obj[key] = [].concat(obj[key], value);
} else {
obj[key] = value;
}
});
return obj;
}
HTML 字串模板
function render(template, data) {
return template.replace(/\{(\w+)}/g, function ($1, key) {
return data[key] ? data[key] : "";
});
}
let html = "我叫{name},今年{id}歲。";
let data = {
name: "Yevin",
age: 18,
};
render(html, data); //我叫Yevin,今年18歲
利用 JSONP 實現跨域請求
function jsonp(url, callbackName) {
return new Promise((resolve, reject) => {
var script = document.createElement("script");
script.src = "demo.js";
document.body.appendChild(script);
window[callbackName] = function (res) {
//移除remove
script.remove();
//返回資料
resolve(res);
};
});
}
原生 JS 封裝 AJAX
function Ajax(method, url, callback, data, async = true) {
var xhr;
//同一轉換method方法為大寫
method = method.toUpperCase();
// 開啟XMLHTTPRequest
xhr = new XMLHttpRequest();
// 監控狀態變化 執行回撥函式
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.readyState == 200) {
// 回撥函式返回引數
callback(xhr.responseText);
}
};
// 判斷請求方式 get post或者其他
if (method == "GET") {
xhr.open("GET", url, async);
} else if (method == "POST") {
xhr.open("POST", url, async);
// 設定請求頭
xhr.setRequestHeader("Content-Type", "application/json");
// 傳送引數
xhr.send(data);
}
}
函式柯里化
//把多個引數的函式變換成接受一個單一引數的函式,並且返回接受餘下的引數且返回結果的新函式的技術
function curry(fun) {
let fn = function (...arg) {
if (arg.length == fun.length) return fun(...arg);
return (...arg2) => fn(...arg, ...arg2);
};
return fn;
}
function demo(a, b) {
return a * b;
}
console.log(demo(1, 2)); //2
console.log(curry(demo)(1)(2)); //2
偏函式
// 偏函式,就是固定一個函式的一個或者多個引數,返回一個新的函式,這個函式用於接受剩餘的引數。
function partial(fn, ...arg) {
return function (...args) {
return fn(...arg, ...args);
};
}
function demo(a, b, c) {
console.log(a, b, c); // 1, 2,3
}
var a = partial(demo, 1);
a(2, 3);