前端開發技術面試題?1. 如果咱們想要确保對象被深凍結,就必須創建一個遞歸函數來凍結對象類型的每個屬性:,我來為大家科普一下關于前端開發技術面試題?下面希望有你要的答案,我們一起來看看吧!
1. 如果咱們想要确保對象被深凍結,就必須創建一個遞歸函數來凍結對象類型的每個屬性:
2. 沒有深凍結
let person = {
name: "Leonardo",
profession: {
name: "developer"
}
};
Object.freeze(person);
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }
3. 深凍結
function deepFreeze(object) {
let propNames = Object.getOwnPropertyNames(object);
for (let name of propNames) {
let value = object[name];
object[name] = value && typeof value === "object" ? deepFreeze(value) : value;
}
return Object.freeze(object);
}
let person = {
name: "Leonardo",
profession: {
name: "developer"
}
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property
/*
自定義函數對象的call方法
*/
export function call (fn, obj, ...args) {
// 如果傳入的是null/undefined, this指定為window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 給obj添加一個方法: 屬性名任意, 屬性值必須當前調用call的函數對象
obj.tempFn = fn
// 通過obj調用這個方法
const result = obj.tempFn(...args)
// 删除新添加的方法
delete obj.tempFn
// 返回函數調用的結果
return result
}
5.3. 手寫apply()
/*
自定義函數對象的apply方法
*/
export function apply (fn, obj, args) {
// 如果傳入的是null/undefined, this指定為window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 給obj添加一個方法: 屬性名任意, 屬性值必須當前調用call的函數對象
obj.tempFn = fn
// 通過obj調用這個方法
const result = obj.tempFn(...args)
// 删除新添加的方法
delete obj.tempFn
// 返回函數調用的結果
return result
}
import {call} from './call'
/*
自定義函數對象的bind方法
重要技術:
高階函數
閉包
call()
三點運算符
*/
export function bind (fn, obj, ...args) {
if (obj===null || obj===undefined) {
obj = obj || window
}
return function (...args2) {
call(fn, obj, ...args, ...args2)
}
}
5.5. 手寫一個防抖函數
/*
實現函數防抖的函數
*/
export function debounce(callback, delay) {
return function () {
// console.log('debounce 事件...')
// 保存this和arguments
const that = this
const args = arguments
// 清除待執行的定時器任務
if (callback.timeoutId) {
clearTimeout(callback.timeoutId)
}
// 每隔delay的時間, 啟動一個新的延遲定時器, 去準備調用callback
callback.timeoutId = setTimeout(function () {
callback.apply(that, args)
// 如果定時器回調執行了, 删除标記
delete callback.timeoutId
}, delay)
}
}
/*
實現函數節流的函數
*/
export function throttle(callback, delay) {
let start = 0 // 必須保存第一次點擊立即調用
return function () {
// 它的this是誰就得讓callback()中的this是誰, 它接收的所有實參都直接交給callback()
console.log('throttle 事件')
const current = Date.now()
if (current - start > delay) { // 從第2次點擊開始, 需要間隔時間超過delay
callback.apply(this, arguments)
start = current
}
}
}
/*
1). 大衆乞丐版
問題1: 函數屬性會丢失
問題2: 循環引用會出錯
*/
export function deepClone1(target) {
return JSON.parse(JSON.stringify(target))
}
/*
獲取數據的類型字符串名
*/
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1)
}
/*
2). 面試基礎版本
解決問題1: 函數屬性還沒丢失
*/
export function deepClone2(target) {
const type = getType(target)
if (type==='Object' || type==='Array') {
const cloneTarget = type === 'Array' ? [] : {}
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone2(target[key])
}
}
return cloneTarget
} else {
return target
}
}
/*
3). 面試加強版本
解決問題2: 循環引用正常
*/
export function deepClone3(target, map = new Map()) {
const type = getType(target)
if (type==='Object' || type==='Array') {
let cloneTarget = map.get(target)
if (cloneTarget) {
return cloneTarget
}
cloneTarget = type==='Array' ? [] : {}
map.set(target, cloneTarget)
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone3(target[key], map)
}
}
return cloneTarget
} else {
return target
}
}
/*
4). 面試加強版本2(優化遍曆性能)
數組: while | for | forEach() 優于 for-in | keys()&forEach()
對象: for-in 與 keys()&forEach() 差不多
*/
export function deepClone4(target, map = new Map()) {
const type = getType(target)
if (type==='Object' || type==='Array') {
let cloneTarget = map.get(target)
if (cloneTarget) {
return cloneTarget
}
if (type==='Array') {
cloneTarget = []
map.set(target, cloneTarget)
target.forEach((item, index) => {
cloneTarget[index] = deepClone4(item, map)
})
} else {
cloneTarget = {}
map.set(target, cloneTarget)
Object.keys(target).forEach(key => {
cloneTarget[key] = deepClone4(target[key], map)
})
}
return cloneTarget
} else {
return target
}
}
/*
自定義instanceof工具函數:
語法: myInstanceOf(obj, Type)
功能: 判斷obj是否是Type類型的實例
實現: Type的原型對象是否是obj的原型鍊上的某個對象, 如果是返回true, 否則返回false
*/
export function myInstanceOf(obj, Type) {
// 得到原型對象
let protoObj = obj.__proto__
// 隻要原型對象存在
while(protoObj) {
// 如果原型對象是Type的原型對象, 返回true
if (protoObj === Type.prototype) {
return true
}
// 指定原型對象的原型對象
protoObj = protoObj.__proto__
}
return false
}
/*
自定義new工具函數
語法: newInstance(Fn, ...args)
功能: 創建Fn構造函數的實例對象
實現: 創建空對象obj, 調用Fn指定this為obj, 返回obj
*/
export function newInstance(Fn, ...args) {
// 創建一個新的對象
const obj = {}
// 執行構造函數
const result = Fn.apply(obj, args) // 相當于: obj.Fn()
// 如果構造函數執行的結果是對象, 返回這個對象
if (result instanceof Object) {
return result
}
// 如果不是, 返回新創建的對象
obj.__proto__.constructor = Fn // 讓原型對象的構造器屬性指向Fn
return obj
}
/*
1. 函數的返回值為promise, 成功的結果為response, 失敗的結果為error
2. 能處理多種類型的請求: GET/POST/PUT/DELETE
3. 函數的參數為一個配置對象
{
url: '', // 請求地址
method: '', // 請求方式GET/POST/PUT/DELETE
params: {}, // GET/DELETE請求的query參數
data: {}, // POST或DELETE請求的請求體參數
}
4. 響應json數據自動解析為js的對象/數組
*/
/* 發送任意類型請求的函數 */
function axios({
url,
method='GET',
params={},
data={}
}) {
// 返回一個Promise對象
return new Promise((resolve, reject) => {
// 處理method(轉大寫)
method = method.toUpperCase()
// 處理query參數(拼接到url上) id=1&xxx=abc
/*
{
id: 1,
xxx: 'abc'
}
*/
let queryString = ''
Object.keys(params).forEach(key => {
queryString = `${key}=${params[key]}&`
})
if (queryString) { // id=1&xxx=abc&
// 去除最後的&
queryString = queryString.substring(0, queryString.length-1)
// 接到url
url = '?' queryString
}
// 1. 執行異步ajax請求
// 創建xhr對象
const request = new XMLHttpRequest()
// 打開連接(初始化請求, 沒有請求)
request.open(method, url, true)
// 發送請求
if (method==='GET') {
request.send()
} else if (method==='POST' || method==='PUT' || method==='DELETE'){
request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 告訴服務器請求體的格式是json
request.send(JSON.stringify(data)) // 發送json格式請求體參數
}
// 綁定狀态改變的監聽
request.onreadystatechange = function () {
// 如果請求沒有完成, 直接結束
if (request.readyState!==4) {
return
}
// 如果響應狀态碼在[200, 300)之間代表成功, 否則失敗
const {status, statusText} = request
// 2.1. 如果請求成功了, 調用resolve()
if (status>=200 && status<=299) {
// 準備結果數據對象response
const response = {
data: JSON.parse(request.response),
status,
statusText
}
resolve(response)
} else { // 2.2. 如果請求失敗了, 調用reject()
reject(new Error('request error status is ' status))
}
}
})
}
/* 發送特定請求的靜态方法 */
axios.get = function (url, options) {
return axios(Object.assign(options, {url, method: 'GET'}))
}
axios.delete = function (url, options) {
return axios(Object.assign(options, {url, method: 'DELETE'}))
}
axios.post = function (url, data, options) {
return axios(Object.assign(options, {url, data, method: 'POST'}))
}
axios.put = function (url, data, options) {
return axios(Object.assign(options, {url, data, method: 'PUT'}))
}
export default axios
/*
* 自定義事件總線
*/
const eventBus = {}
/*
{
add: [callback1, callback2]
delete: [callback3]
}
*/
let callbacksObj = {}
/*
綁定事件監聽
*/
eventBus.on = function (eventName, callback) {
const callbacks = callbacksObj[eventName]
if (callbacks) {
callbacks.push(callback)
} else {
callbacksObj[eventName] = [callback]
}
}
/*
分發事件
*/
eventBus.emit = function (eventName, data) {
const callbacks = callbacksObj[eventName]
if (callbacks && callbacks.length > 0) {
callbacks.forEach(callback => {
callback(data)
})
}
}
/*
移除事件監聽
*/
eventBus.off = function (eventName) {
if (eventName) {
delete callbacksObj[eventName]
} else {
callbacksObj = {}
}
}
export default eventBus
/*
自定義消息訂閱與發布
*/
const PubSub = {}
/*
{
add: {
token1: callback1,
token2: callback2
},
update: {
token3: callback3
}
}
*/
let callbacksObj = {} // 保存所有回調的容器
let id = 0 // 用于生成token的标記
// 1. 訂閱消息
PubSub.subscribe = function (msgName, callback) {
// 确定token
const token = 'token_' id
// 取出當前消息對應的callbacks
const callbacks = callbacksObj[msgName]
if (!callbacks) {
callbacksObj[msgName] = {
[token]: callback
}
} else {
callbacks[token] = callback
}
// 返回token
return token
}
// 2. 發布異步的消息
PubSub.publish = function (msgName, data) {
// 取出當前消息對應的callbacks
let callbacks = callbacksObj[msgName]
// 如果有值
if (callbacks) {
// callbacks = Object.assign({}, callbacks)
// 啟動定時器, 異步執行所有的回調函數
setTimeout(() => {
Object.values(callbacks).forEach(callback => {
callback(data)
})
}, 0)
}
}
// 3. 發布同步的消息
PubSub.publishSync = function (msgName, data) {
// 取出當前消息對應的callbacks
const callbacks = callbacksObj[msgName]
// 如果有值
if (callbacks) {
// 立即同步執行所有的回調函數
Object.values(callbacks).forEach(callback => {
callback(data)
})
}
}
/*
4. 取消消息訂閱
1). 沒有傳值, flag為undefined
2). 傳入token字符串
3). msgName字符串
*/
PubSub.unsubscribe = function (flag) {
// 如果flag沒有指定或者為null, 取消所有
if (flag === undefined) {
callbacksObj = {}
} else if (typeof flag === 'string') {
if (flag.indexOf('token_') === 0) { // flag是token
// 找到flag對應的callbacks
const callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag))
// 如果存在, 删除對應的屬性
if (callbacks) {
delete callbacks[flag]
}
} else { // flag是msgName
delete callbacksObj[flag]
}
} else {
throw new Error('如果傳入參數, 必須是字符串類型')
}
}
export default PubSub
/*
實現數組聲明式處理系列工具函數
*/
/*
實現map()
*/
export function map (array, callback) {
const arr = []
for (let index = 0; index < array.length; index ) {
arr.push(callback(array[index], index))
}
return arr
}
/*
實現reduce()
*/
export function reduce (array, callback, initValue) {
let result = initValue
for (let index = 0; index < array.length; index ) {
// 調用回調函數将返回的結果賦值給result
result = callback(result, array[index], index)
}
return result
}
/*
實現filter()
*/
export function filter(array, callback) {
const arr = []
for (let index = 0; index < array.length; index ) {
if (callback(array[index], index)) {
arr.push(array[index])
}
}
return arr
}
/*
實現find()
*/
export function find (array, callback) {
for (let index = 0; index < array.length; index ) {
if (callback(array[index], index)) {
return array[index]
}
}
return undefined
}
/*
實現findIndex()
*/
export function findIndex (array, callback) {
for (let index = 0; index < array.length; index ) {
if (callback(array[index], index)) {
return index
}
}
return -1
}
/*
實現every()
*/
export function every (array, callback) {
for (let index = 0; index < array.length; index ) {
if (!callback(array[index], index)) { // 隻有一個結果為false, 直接返回false
return false
}
}
return true
}
/*
實現some()
*/
export function some (array, callback) {
for (let index = 0; index < array.length; index ) {
if (callback(array[index], index)) { // 隻有一個結果為true, 直接返回true
return true
}
}
return false
}
export function test() {
console.log('test()222')
}
const PENDING = 'pending' // 初始未确定的狀态
const RESOLVED = 'resolved' // 成功的狀态
const REJECTED = 'rejected' // 失敗的狀态
/*
Promise構造函數
*/
function Promise(excutor) {
const self = this // Promise的實例對象
self.status = PENDING // 狀态屬性, 初始值為pending, 代表初始未确定的狀态
self.data = undefined // 用來存儲結果數據的屬性, 初始值為undefined
self.callbacks = [] // {onResolved(){}, onRejected(){}}
/*
将promise的狀态改為成功, 指定成功的value
*/
function resolve(value) {
// 如果當前不是pending, 直接結束
if (self.status !== PENDING) return
self.status = RESOLVED // 将狀态改為成功
self.data = value // 保存成功的value
// 異步調用所有緩存的待執行成功的回調函數
if (self.callbacks.length > 0) {
// 啟動一個延遲時間為0的定時器, 在定時器的回調中執行所有成功的回調
setTimeout(() => {
self.callbacks.forEach(cbsObj => {
cbsObj.onResolved(value)
})
})
}
}
/*
将promise的狀态改為失敗, 指定失敗的reason
*/
function reject(reason) {
// 如果當前不是pending, 直接結束
if (self.status !== PENDING) return
self.status = REJECTED // 将狀态改為失敗
self.data = reason // 保存reason數據
// 異步調用所有緩存的待執行失敗的回調函數
if (self.callbacks.length > 0) {
// 啟動一個延遲時間為0的定時器, 在定時器的回調中執行所有失敗的回調
setTimeout(() => {
self.callbacks.forEach(cbsObj => {
cbsObj.onRejected(reason)
})
})
}
}
// 調用excutor來啟動異步任務
try {
excutor(resolve, reject)
} catch (error) { // 執行器執行出錯, 當前promise變為失敗
console.log('-----')
reject(error)
}
}
/*
用來指定成功/失敗回調函數的方法
1). 如果當前promise是resolved, 異步執行成功的回調函數onResolved
2). 如果當前promise是rejected, 異步執行成功的回調函數onRejected
3). 如果當前promise是pending, 保存回調函數
返回一個新的promise對象
它的結果狀态由onResolved或者onRejected執行的結果決定
2.1). 抛出error ==> 變為rejected, 結果值為error
2.2). 返回值不是promise ==> 變為resolved, 結果值為返回值
2.3). 返回值是promise ===> 由這個promise的決定新的promise的結果(成功/失敗)
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
onResolved = typeof onResolved === 'function' ? onResolved : value => value // 将value向下傳遞
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason
} // 将reason向下傳遞
return new Promise((resolve, reject) => { // 什麼時候改變它的狀态
/*
1. 調用指定的回調函數
2. 根據回調執行結果來更新返回promise的狀态
*/
function handle(callback) {
try {
const result = callback(self.data)
if (!(result instanceof Promise)) { // 2.2). 返回值不是promise ==> 變為resolved, 結果值為返回值
resolve(result)
} else { // 2.3). 返回值是promise ===> 由這個promise的決定新的promise的結果(成功/失敗)
result.then(
value => resolve(value),
reason => reject(reason)
)
// result.then(resolve, reject)
}
} catch (error) { // 2.1). 抛出error ==> 變為rejected, 結果值為error
reject(error)
}
}
if (self.status === RESOLVED) {
setTimeout(() => {
handle(onResolved)
})
} else if (self.status === REJECTED) {
setTimeout(() => {
handle(onRejected)
})
} else { // PENDING
self.callbacks.push({
onResolved(value) {
handle(onResolved)
},
onRejected(reason) {
handle(onRejected)
}
})
}
})
}
/*
用來指定失敗回調函數的方法
catch是then的語法糖
*/
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
/*
用來返回一個指定vlaue的成功的promise
value可能是一個一般的值, 也可能是promise對象
*/
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
// 如果value是一個promise, 最終返回的promise的結果由value決定
if (value instanceof Promise) {
value.then(resolve, reject)
} else { // value不是promise, 返回的是成功的promise, 成功的值就是value
resolve(value)
}
})
}
/*
用來返回一個指定reason的失敗的promise
*/
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
/*
返回一個promise, 隻有當數組中所有promise都成功才成功, 否則失敗
*/
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let resolvedCount = 0 // 已經成功的數量
const values = new Array(promises.length) // 用來保存成功promise的value值
// 遍曆所有promise, 取其對應的結果
promises.forEach((p, index) => {
p.then(
value => {
resolvedCount
values[index] = value
if (resolvedCount === promises.length) { // 都成功了
resolve(values)
}
},
reason => reject(reason)
)
})
})
}
/*
返回一個promise, 由第一個完成promise決定
*/
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
// 遍曆所有promise, 取其對應的結果
promises.forEach(p => {
// 返回的promise由第一個完成p來決定其結果
p.then(resolve, reject)
})
})
}
/*
返回一個延遲指定時間才成功(也可能失敗)的promise
*/
Promise.resolveDelay = function (value, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 如果value是一個promise, 最終返回的promise的結果由value決定
if (value instanceof Promise) {
value.then(resolve, reject)
} else { // value不是promise, 返回的是成功的promise, 成功的值就是value
resolve(value)
}
}, time)
})
}
/*
返回一個延遲指定時間才失敗的promise
*/
Promise.rejectDelay = function (reason, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(reason)
}, time)
})
}
export default Promise
/*
數組扁平化: 取出嵌套數組(多維)中的所有元素放到一個新數組(一維)中
如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
*/
/*
方法一: 遞歸 reduce() concat()
*/
export function flatten1 (array) {
return array.reduce((pre, item) => {
if (Array.isArray(item)) {
return pre.concat(flatten1(item))
} else {
return pre.concat(item)
}
}, [])
}
/*
方法二: ... some() concat()
*/
export function flatten2 (array) {
let arr = [].concat(...array)
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!