一:自動綁定實例方法
題目:
在 JavaScript 的類當中,類實例如果不通過實例進行調用,方法中的 this 就不會指向實例,例如:
class Person {
constructor (name) {
this.name = name
}
sayHi () {
console.log(`I am ${this.name}.`)
}
}
const jerry = new Person('Jerry')
const sayHi = jerry.sayHi
sayHi() // => 報錯
所以在類似于 React.js 的組件的事件監聽當中我們總是需要手動地進行 bind(this) 操作。為了簡化這樣的操作,請你完成一個方法 autoBind,它可以接受一個類作為參數,并且返回一個類。返回的類的實例和原來的類的實例功能上并無差別,只是新的類的實例所有方法都會自動 bind 到實例上。例如:
const BoundPerson = autoBind(Person)
const jerry = new BoundPerson('Jerry')
const sayHi = jerry.sayHi
sayHi() // => I am Jerry.
const lucy = new BoundPerson('Lucy')
const sayHi = lucy.sayHi
sayHi() // => I am Lucy.
注意,如果 autoBind 以后給原來的類新增方法,也會自動反映在實例上,例如:
Person.prototype.sayGood = function () {
console.log(`I am ${this.name}. I am good!`)
}
const sayGood = lucy.sayGood
sayGood() // => I am Lucy. I am good!
請你完成 autoBind 的編寫。
答案:
~~~
/* Proxy 和反射的使用 */
const autoBind = (fn) => new Proxy(fn, {
construct (_, args) {
const obj = new fn(...args)
const prop = new Proxy(Reflect.getPrototypeOf(obj), {
get (target, key) {
const fn = Reflect.get(target, key)
return typeof fn === 'function' ? fn.bind(obj) : fn
}
})
Reflect.setPrototypeOf(obj, prop)
return obj
}
})
~~~
二: filter map
請你給原生的 Map 添加方法 filterKeys 和 filterValues,可以類似于于數組方法的 filter。它們分別可以對 Map 的鍵和值進行篩選,它們會返回一個新的 Map, 是對原有的 Map 的篩選結果,例如:
const m = new Map([['Jerry', 12], ['Jimmy', 13], ['Tomy', 14]])
m.filterKeys((key) => key.startsWith('J')) // => Map { Jerry => 12, Jimmy => 13 }
m.filterValues((val) => val >= 13) // => Map { Jimmy => 13, Tomy => 14 }
// 原有的 map 保持不變
console.log(m) // => Map { Jerry => 12 , Jimmy => 13, Tomy => 14 }
答案:
function filterWithIndex (i) {
return function (filter) {
return new Map([...this.entries()].filter((kv) => filter(kv[i])))
}
}
Map.prototype.filterKeys = filterWithIndex(0)
Map.prototype.filterValues = filterWithIndex(1)
三:單例模式
單例模式(Singleton)是一種常用的軟件設計模式,它保證我們系統中的某一個類在任何情況實例化的時候都獲得同一個實例。例如:
const root1 = new Root()
const root2 = new Root()
const root3 = new Root()
root1 === root2 // true
root2 === root3 // true
我們構造一個名為 singletonify 方法,可以傳入一個用戶自定義的類,可以返回一個新的單例模式的類。例如:
class A () {}
const SingleA = singletonify(A)
const a1 = new SingleA()
const a2 = new SingleA()
const a3 = new SingleA()
a1 === a2 // => true
a2 === a3 // => true
注意,你要保證 singletonify 返回的類的實例也是原來的類的實例:
a1 instanceof A // => true
a1 instanceof SingleA // => true
自定義的類屬性也要保持一致,例如:
class A () {}
A.staticMethod = () => {}
const SingleA = singletonify(A)
SingleA.staticMethod === A.staticMethod // => true
請你完成 singletonify 的編寫。
答案:
~~~
/* 不僅考察 Singleton 模式,還考察了 Proxy 的用法 */
const singletonify = (OriginalClass) => {
let i
return new Proxy(OriginalClass, {
construct () {
if (!i) i = new OriginalClass()
return i
}
})
}
~~~
四: 靈魂交換
有兩個不同的人,他們有不同的靈魂(prototype)。
class A {
constructor (name) {
this.name = name
}
sayHi () {
return `I am ${this.name}.`
}
}
class B {
constructor (name) {
this.name = name
}
sayHi () {
return `This is ${this.name}.`
}
}
const a = new A('Jerry')
const b = new B('Lucy')
a.sayHi() // => 'I am Jerry.'
b.sayHi() // => 'This is Lucy.'
a instanceof B // => false
b instanceof A // => false
請你完成 exchange,傳入兩個對象,可以交換他們的靈魂:
exchange(a, b)
a.sayHi() // => 'This is Jerry.'
b.sayHi() // => 'I am Lucy.'
a instanceof B // => true
b instanceof A // => true
注意不要觸碰到這兩個對象原來的類,例如:
exchange(a, b)
a.sayHi() // => 'This is Jerry.'
b.sayHi() // => 'I am Lucy.'
const c = new A('Tomy')
c.sayHi() // => 應該返回 'I am Tomy.'
你也不能使用 __proto__ 屬性。
答案:
~~~
/* 考察的是 Object.getPrototypeOf 和 Object.setPrototypeOf 這兩個 API 的使用 */
const exchange = (a, b) => {
const getProto = (o) => Object.getPrototypeOf(o)
const setProto = (o, p) => Object.setPrototypeOf(o, p)
const ap = getProto(a)
setProto(a, getProto(b))
setProto(b, ap)
}
/* or */
/*
const exchange = (a, b) => {
const protos = [a, b].map(o => Object.getPrototypeOf(o)); //這里的分號是必要的
[b, a].forEach((o, i) => Object.setPrototypeOf(o, protos[i]))
}
*/
~~~
六:屬性閃爍
完成一個 flikerProps 方法,接受一個對象作為參數。可以把該對象的不可遍歷屬性變成可遍歷屬性;把可遍歷屬性變成不可遍歷屬性。例如:
const obj = {}
const config1 = { enumerable: false, configurable: true }
const config2 = { enumerable: true, configurable: true }
Object.defineProperties(obj, {
green: config1,
red: config2,
blue: config1,
yellow: config2
})
console.log(Object.keys(obj)) // => ["red", "yellow"]
flikerProps(obj) // 閃爍
console.log(Object.keys(obj)) // => ["green", "blue"]
flikerProps(obj) // 閃爍
console.log(Object.keys(obj)) // => ["red", "yellow"]
flikerProps(obj) // 閃爍
console.log(Object.keys(obj)) // => ["green", "blue"]
注意不要觸碰到傳入對象的 prototype。
答案:
~~~
const flikerProps = (obj) => {
/* 本題主要考察的是 getOwnPropertyDescriptors 和 defineProperty 方法的使用 */
const descs = Object.getOwnPropertyDescriptors(obj)
for (let [key, desc] of Object.entries(descs)) {
Reflect.defineProperty(obj, key, { enumerable: !desc.enumerable })
}
}
~~~
七: 數組的空位填充
JavaScript 數組有空位的概念,也就數組的一個位置上沒有任何的值。例如:
[ , , 'Hello'] // => 0, 1 都是空位, 3 不是空位
空位并不等于 undefined 或者 null。一個位置上如果是 undefined 那么它依然有值,例如 [, , undefined],0 和 1 都是空位,而 2 不是空位。
請你完成一個函數 fillEmpty,它接受一個數組作為參數,可以把數組里面的所有空位都設置為 'Hello',例如:
const a = [, , null, undefined, 'OK', ,]
fillEmpty(a)
// a 變成 ['Hello', 'Hello', null, undefined, 'OK', 'Hello']
注意,你要原地修改原先的數組,而不是返回一個新的數組。
答案:
~~~
const fillEmpty = (arr) => {
for (let i = 0; i < arr.length; i++) {
if (i in arr) continue
arr[i] = 'Hello'
}
}
~~~
八:使用 generator 模擬 async/await
在遠古時代,我們使用 callback 進行異步流程控制,但是會有 callback hell 的問題。經過歷史的發展,逐漸地使用了不少的工具進行異步流程的改進,例如 Async.js、Promise 等,到后來的 generator + promise,還有最終的方案 async/await。了解以前是用什么方案處理異步流程控制,對我們理解現在的 asyn/await 也是很有好處。
請你實現一個簡單的函數 wrapAsync,使用 generator + promise 來模擬 async/await 進行異步流程的控制。wrapAsync 接受一個 generator 函數作為參數,并且返回一個函數。generator 函數內部可以使用關鍵字 yield 一個 Promise 對象,并且可以類似 async/await 那樣獲取到 Promise 的返回結果,例如:
const getData = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('My name is ' + name)
}, 100) // 模擬異步獲取數據
})
}
const run = wrapAsync(function * (lastName) {
const data1 = yield getData('Jerry ' + lastName)
const data2 = yield getData('Lucy ' + lastName)
return [data1, data2]
})
run('Green').then((val) => {
console.log(val) // => [ 'My name is Jerry Green', 'My name is Lucy Green' ]
})
getData 是一個異步函數并且返回 Promise,我們通過 yield 關鍵字獲取到這個異步函數的 Promise 返回的結果,在代碼編寫上起來像是同步的,執行上實際是異步的。
請你完成 wrapAsync 的編寫,wrapAsync 返回的函數接受的參數和傳入的 generator 接受的函數保持一致,并且在調用的時候會傳給 generator 函數(正如上面的例子);另外,wrapAsync 返回的函數執行結果是一個 Promise,我們可以通過這個 Promise 獲取到 generator 函數執行的結果(正如上面的例子)。
(此簡單實現你暫時不需要考慮異常的控制。)
答案:
~~~
const wrapAsync = (fn) => (...args) => new Promise((resolve, reject) => {
const gen = fn(...args)
let ret = gen.next()
const run = () => {
const promise = isPromise(ret.value) ? ret.value : Promise.resolve(ret.value)
if (ret.done) return promise.then(resolve)
promise.then((val) => {
ret = gen.next(val)
run()
}).catch((e) => {
try {
gen.throw(e)
} catch (e) {
reject(e)
}
})
}
/* Run generator */
run()
})
const isPromise = (obj) => {
return obj && typeof obj.then === 'function'
}
~~~
九:不重復數字
編寫一個 JavaScript 函數 uniqueNums,該函數有一個參數 n(一個不大 31 的整數),其返回值是一個數組,該數組內是 n 個隨機且不重復的整數,且整數取值范圍是 [2, 32]。
請你完成 uniqueNums 的編寫。
答案:
~~~
// const uniqueNums = (n) => {
// const arr = [...Array(31).keys()].map((n) => n + 2)
// const newArr = []
// while (newArr.length < n && arr.length) {
// const index = Math.floor(arr.length * Math.random())
// newArr.push(arr[index])
// arr.splice(index, 1)
// }
// return newArr
// }
const uniqueNums = (n) =>
[...(new Array(31)).keys()]
.map((i) => i + 2)
.sort(() => Math.random() - Math.random())
.slice(0, n)
~~~
十: Math.clz32 的 Polyfill
ES6 新增了 Math.clz32 方法,可以讓我們獲取到一個整數的無符號 32 位的二進制形式有多少位前置的 0。例如:
// 1 的 32 位二進制表示:0b00000000000000000000000000000001
// 有 31 位前置的 0
Math.clz32(1) // => 31
請你完成 clz32 來達到和 Math.clz32 的同樣的功能。如果輸入的是能夠轉換成數字的,先轉換成數字再進行計算:
Math.clz32('2') // => 30
如果不能轉換成數字的,返回 32:
Math.clz32('good') // => 32
總而言之,你的函數的返回結果要和 Math.clz32 保持一致。
答案:
~~~
const clz32 = (x) => {
x = (x >>> 0).toString(2)
return x * 1 === 0 ? 32 : 32 - x.length
}
~~~
- 前端入門
- 前端入職須知
- 正確看待前端
- 前端自我定位
- pc與手機頁面差別
- 前端書單
- 前端技術棧
- 前端資源導航
- 前端切圖
- 插件
- 組件、控件和插件的區別
- 技術文檔
- layui
- layer彈框在實際項目中的一些應用
- 前端面試題
- bat面試題庫
- 中小公司的leader
- 項目相關
- 職業規劃如何
- 前端經典筆試題
- javascript基礎(一)
- JavaScript基礎二
- JavaScript基礎面試題(三)
- JavaScript基礎面試題(四)
- JavaScript基礎面試題(五)
- JavaScript基礎面試題(六)
- JavaScript基礎面試題(七)
- JavaScript基礎面試題(八)
- JavaScript基礎面試題(九)
- JavaScript基礎面試題(十)
- dom經典面試題
- 正則表達式
- 史上最難面試題
- 簡單算法
- 前端idea
- vsc快速上手指南
- 微信開發者工具
- sublime的使用
- hbuilder入門
- 前端那些事
- 前端的注釋該怎么寫
- 前端架構師是怎么煉成的
- 細數前端的那些技術大牛
- 前端leader的那些事
- ps
- 圖片類型及其區別
- 基本概念及其常用工具
- ps操作技巧
- ps站點資源導航
- ui站點導航
- html
- css
- js
- 插件庫
- git教程
- web
- web兼容思想
- ui框架
- 小程序
- 微信專題
- 支付寶專題