20道JS原理題助你面試一臂之力!
本文針對目前常見的面試題,實現了相應方法的核心原理,部分邊界細節未處理。後續也會持續更新,希望對你有所幫助。
1、實現一個call函式
// 思路:將要改變this指向的方法掛到目標this上執行並返回
Function
。
prototype
。
mycall
=
function
(
context
)
{
if
(
typeof
this
!==
‘function’
)
{
throw
new
TypeError
(
‘not funciton’
)
}
context
=
context
||
window
context
。
fn
=
this
let
arg
=
[。。。
arguments
]。
slice
(
1
)
let
result
=
context
。
fn
(。。。
arg
)
delete
context
。
fn
return
result
}
2、實現一個apply函式
// 思路:將要改變this指向的方法掛到目標this上執行並返回
Function
。
prototype
。
myapply
=
function
(
context
)
{
if
(
typeof
this
!==
‘function’
)
{
throw
new
TypeError
(
‘not funciton’
)
}
context
=
context
||
window
context
。
fn
=
this
let
result
if
(
arguments
[
1
])
{
result
=
context
。
fn
(。。。
arguments
[
1
])
}
else
{
result
=
context
。
fn
()
}
delete
context
。
fn
return
result
}
3、實現一個bind函式
// 思路:類似call,但返回的是函式
Function
。
prototype
。
mybind
=
function
(
context
)
{
if
(
typeof
this
!==
‘function’
)
{
throw
new
TypeError
(
‘Error’
)
}
let
_this
=
this
let
arg
=
[。。。
arguments
]。
slice
(
1
)
return
function
F
()
{
// 處理函式使用new的情況
if
(
this
instanceof
F
)
{
return
new
_this
(。。。
arg
,
。。。
arguments
)
}
else
{
return
_this
。
apply
(
context
,
arg
。
concat
(。。。
arguments
))
}
}
}
4、instanceof的原理
// 思路:右邊變數的原型存在於左邊變數的原型鏈上
function
instanceOf
(
left
,
right
)
{
let
leftValue
=
left
。
__proto__
let
rightValue
=
right
。
prototype
while
(
true
)
{
if
(
leftValue
===
null
)
{
return
false
}
if
(
leftValue
===
rightValue
)
{
return
true
}
leftValue
=
leftValue
。
__proto__
}
}
5、Object.create的基本實現原理
// 思路:將傳入的物件作為原型
function
create
(
obj
)
{
function
F
()
{}
F
。
prototype
=
obj
return
new
F
()
}
6、new本質
function
myNew
(
fun
)
{
return
function
()
{
// 建立一個新物件且將其隱式原型指向建構函式原型
let
obj
=
{
__proto__
:
fun
。
prototype
}
// 執行建構函式
fun
。
call
(
obj
,
。。。
arguments
)
// 返回該物件
return
obj
}
}
function
person
(
name
,
age
)
{
this
。
name
=
name
this
。
age
=
age
}
let
obj
=
myNew
(
person
)(
‘chen’
,
18
)
// {name: “chen”, age: 18}
7、實現一個基本的Promise
// 未新增非同步處理等其他邊界情況
// ①自動執行函式,②三個狀態,③then
class
Promise
{
constructor
(
fn
)
{
// 三個狀態
this
。
state
=
‘pending’
this
。
value
=
undefined
this
。
reason
=
undefined
let
resolve
=
value
=>
{
if
(
this
。
state
===
‘pending’
)
{
this
。
state
=
‘fulfilled’
this
。
value
=
value
}
}
let
reject
=
value
=>
{
if
(
this
。
state
===
‘pending’
)
{
this
。
state
=
‘rejected’
this
。
reason
=
value
}
}
// 自動執行函式
try
{
fn
(
resolve
,
reject
)
}
catch
(
e
)
{
reject
(
e
)
}
}
// then
then
(
onFulfilled
,
onRejected
)
{
switch
(
this
。
state
)
{
case
‘fulfilled’
:
onFulfilled
()
break
case
‘rejected’
:
onRejected
()
break
default
:
}
}
}
8、實現淺複製
// 1。 。。。實現
let
copy1
=
{。。。{
x
:
1
}}
// 2。 Object。assign實現
let
copy2
=
Object
。
assign
({},
{
x
:
1
})
9、實現一個基本的深複製
// 1。 JOSN。stringify()/JSON。parse()
let
obj
=
{
a
:
1
,
b
:
{
x
:
3
}}
JSON
。
parse
(
JSON
。
stringify
(
obj
))
// 2。 遞迴複製
function
deepClone
(
obj
)
{
let
copy
=
obj
instanceof
Array
?
[]
:
{}
for
(
let
i
in
obj
)
{
if
(
obj
。
hasOwnProperty
(
i
))
{
copy
[
i
]
=
typeof
obj
[
i
]
===
‘object’
?
deepClone
(
obj
[
i
])
:
obj
[
i
]
}
}
return
copy
}
10、使用setTimeout模擬setInterval
// 可避免setInterval因執行時間導致的間隔執行時間不一致
setTimeout
(
function
()
{
// do something
setTimeout
(
arguments
。
callee
,
500
)
},
500
)
11、js實現一個繼承方法// 借用建構函式繼承例項屬性
// 借用建構函式繼承例項屬性
function
Child
()
{
Parent
。
call
(
this
)
}
// 寄生繼承原型屬性
(
function
()
{
let
Super
=
function
()
{}
Super
。
prototype
=
Parent
。
prototype
Child
。
prototype
=
new
Super
()
})()
12、實現一個基本的Event Bus
// 元件通訊,一個觸發與監聽的過程
class
EventEmitter
{
constructor
()
{
// 儲存事件
this
。
events
=
this
。
events
||
new
Map
()
}
// 監聽事件
addListener
(
type
,
fn
)
{
if
(
!
this
。
events
。
get
(
type
))
{
this
。
events
。
set
(
type
,
fn
)
}
}
// 觸發事件
emit
(
type
)
{
let
handle
=
this
。
events
。
get
(
type
)
handle
。
apply
(
this
,
[。。。
arguments
]。
slice
(
1
))
}
}
// 測試
let
emitter
=
new
EventEmitter
()
// 監聽事件
emitter
。
addListener
(
‘ages’
,
age
=>
{
console
。
log
(
age
)
})
// 觸發事件
emitter
。
emit
(
‘ages’
,
18
)
// 18
13、實現一個雙向資料繫結
let
obj
=
{}
let
input
=
document
。
getElementById
(
‘input’
)
let
span
=
document
。
getElementById
(
‘span’
)
// 資料劫持
Object
。
defineProperty
(
obj
,
‘text’
,
{
configurable
:
true
,
enumerable
:
true
,
get
()
{
console
。
log
(
‘獲取資料了’
)
},
set
(
newVal
)
{
console
。
log
(
‘資料更新了’
)
input
。
value
=
newVal
span
。
innerHTML
=
newVal
}
})
// 輸入監聽
input
。
addEventListener
(
‘keyup’
,
function
(
e
)
{
obj
。
text
=
e
。
target
。
value
})
完整實現可前往之前寫的:這應該是最詳細的響應式系統講解了
https://
juejin。im/post/5d26e368
e51d4577407b1dd7
14、實現一個簡單路由
// hash路由
class
Route
{
constructor
(){
// 路由儲存物件
this
。
routes
=
{}
// 當前hash
this
。
currentHash
=
‘’
// 繫結this,避免監聽時this指向改變
this
。
freshRoute
=
this
。
freshRoute
。
bind
(
this
)
// 監聽
window
。
addEventListener
(
‘load’
,
this
。
freshRoute
,
false
)
window
。
addEventListener
(
‘hashchange’
,
this
。
freshRoute
,
false
)
}
// 儲存
storeRoute
(
path
,
cb
)
{
this
。
routes
[
path
]
=
cb
||
function
()
{}
}
// 更新
freshRoute
()
{
this
。
currentHash
=
location
。
hash
。
slice
(
1
)
||
‘/’
this
。
routes
[
this
。
currentHash
]()
}
}
15、實現懶載入
<
ul
>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/1。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/2。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/3。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/4。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/5。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/6。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/7。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/8。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/9。png”
alt
=
“”
><
/li>
<
li
><
img
src
=
“。/imgs/default。png”
data
=
“。/imgs/10。png”
alt
=
“”
><
/li>
<
/ul>
let
imgs
=
document
。
querySelectorAll
(
‘img’
)
// 可視區高度
let
clientHeight
=
window
。
innerHeight
||
document
。
documentElement
。
clientHeight
||
document
。
body
。
clientHeight
function
lazyLoad
()
{
// 滾動捲去的高度
let
scrollTop
=
window
。
pageYOffset
||
document
。
documentElement
。
scrollTop
||
document
。
body
。
scrollTop
for
(
let
i
=
0
;
i
<
imgs
。
length
;
i
++
)
{
// 圖片在可視區冒出的高度
let
x
=
clientHeight
+
scrollTop
-
imgs
[
i
]。
offsetTop
// 圖片在可視區內
if
(
x
>
0
&&
x
<
clientHeight
+
imgs
[
i
]。
height
)
{
imgs
[
i
]。
src
=
imgs
[
i
]。
getAttribute
(
‘data’
)
}
}
}
// addEventListener(‘scroll’, lazyLoad) or setInterval(lazyLoad, 1000)
16、rem實現原理
// 原始配置
function
setRem
()
{
let
doc
=
document
。
documentElement
let
width
=
doc
。
getBoundingClientRect
()。
width
let
rem
=
width
/
75
doc
。
style
。
fontSize
=
rem
+
‘px’
}
// 監聽視窗變化
addEventListener
(
“resize”
,
setRem
)
17、手寫實現AJAX
// 1。 簡單流程
// 例項化
let
xhr
=
new
XMLHttpRequest
()
// 初始化
xhr
。
open
(
method
,
url
,
async
)
// 傳送請求
xhr
。
send
(
data
)
// 設定狀態變化回撥處理請求結果
xhr
。
onreadystatechange
=
()
=>
{
if
(
xhr
。
readyStatus
===
4
&&
xhr
。
status
===
200
)
{
console
。
log
(
xhr
。
responseText
)
}
}
// 2。 基於promise實現
function
ajax
(
options
)
{
// 請求地址
const
url
=
options
。
url
// 請求方法
const
method
=
options
。
method
。
toLocaleLowerCase
()
||
‘get’
// 預設為非同步true
const
async
=
options
。
async
// 請求引數
const
data
=
options
。
data
// 例項化
const
xhr
=
new
XMLHttpRequest
()
// 請求超時
if
(
options
。
timeout
&&
options
。
timeout
>
0
)
{
xhr
。
timeout
=
options
。
timeout
}
// 返回一個Promise例項
return
new
Promise
((
resolve
,
reject
)
=>
{
xhr
。
ontimeout
=
()
=>
reject
&&
reject
(
‘請求超時’
)
// 監聽狀態變化回撥
xhr
。
onreadystatechange
=
()
=>
{
if
(
xhr
。
readyState
==
4
)
{
// 200-300 之間表示請求成功,304資源未變,取快取
if
(
xhr
。
status
>=
200
&&
xhr
。
status
<
300
||
xhr
。
status
==
304
)
{
resolve
&&
resolve
(
xhr
。
responseText
)
}
else
{
reject
&&
reject
()
}
}
}
// 錯誤回撥
xhr
。
onerror
=
err
=>
reject
&&
reject
(
err
)
let
paramArr
=
[]
let
encodeData
// 處理請求引數
if
(
data
instanceof
Object
)
{
for
(
let
key
in
data
)
{
// 引數拼接需要透過 encodeURIComponent 進行編碼
paramArr
。
push
(
encodeURIComponent
(
key
)
+
‘=’
+
encodeURIComponent
(
data
[
key
]))
}
encodeData
=
paramArr
。
join
(
‘&’
)
}
// get請求拼接引數
if
(
method
===
‘get’
)
{
// 檢測url中是否已存在 ? 及其位置
const
index
=
url
。
indexOf
(
‘?’
)
if
(
index
===
-
1
)
url
+=
‘?’
else
if
(
index
!==
url
。
length
-
1
)
url
+=
‘&’
// 拼接url
url
+=
encodeData
}
// 初始化
xhr
。
open
(
method
,
url
,
async
)
// 傳送請求
if
(
method
===
‘get’
)
xhr
。
send
(
null
)
else
{
// post 方式需要設定請求頭
xhr
。
setRequestHeader
(
‘Content-Type’
,
‘application/x-www-form-urlencoded;charset=UTF-8’
)
xhr
。
send
(
encodeData
)
}
})
}
18、實現拖拽
window
。
onload
=
function
()
{
// drag處於絕對定位狀態
let
drag
=
document
。
getElementById
(
‘box’
)
drag
。
onmousedown
=
function
(
e
)
{
var
e
=
e
||
window
。
event
// 滑鼠與拖拽元素邊界的距離 = 滑鼠與可視區邊界的距離 - 拖拽元素與邊界的距離
let
diffX
=
e
。
clientX
-
drag
。
offsetLeft
let
diffY
=
e
。
clientY
-
drag
。
offsetTop
drag
。
onmousemove
=
function
(
e
)
{
// 拖拽元素移動的距離 = 滑鼠與可視區邊界的距離 - 滑鼠與拖拽元素邊界的距離
let
left
=
e
。
clientX
-
diffX
let
top
=
e
。
clientY
-
diffY
// 避免拖拽出可視區
if
(
left
<
0
)
{
left
=
0
}
else
if
(
left
>
window
。
innerWidth
-
drag
。
offsetWidth
)
{
left
=
window
。
innerWidth
-
drag
。
offsetWidth
}
if
(
top
<
0
)
{
top
=
0
}
else
if
(
top
>
window
。
innerHeight
-
drag
。
offsetHeight
)
{
top
=
window
。
innerHeight
-
drag
。
offsetHeight
}
drag
。
style
。
left
=
left
+
‘px’
drag
。
style
。
top
=
top
+
‘px’
}
drag
。
onmouseup
=
function
(
e
)
{
this
。
onmousemove
=
null
this
。
onmouseup
=
null
}
}
}
19、實現一個節流函式
// 思路:在規定時間內只觸發一次
function
throttle
(
fn
,
delay
)
{
// 利用閉包儲存時間
let
prev
=
Date
。
now
()
return
function
()
{
let
context
=
this
let
arg
=
arguments
let
now
=
Date
。
now
()
if
(
now
-
prev
>=
delay
)
{
fn
。
apply
(
context
,
arg
)
prev
=
Date
。
now
()
}
}
}
function
fn
()
{
console
。
log
(
‘節流’
)
}
addEventListener
(
‘scroll’
,
throttle
(
fn
,
1000
))
20、實現一個防抖函式
// 思路:在規定時間內未觸發第二次,則執行
function
debounce
(
fn
,
delay
)
{
// 利用閉包儲存定時器
let
timer
=
null
return
function
()
{
let
context
=
this
let
arg
=
arguments
// 在規定時間內再次觸發會先清除定時器後再重設定時器
clearTimeout
(
timer
)
timer
=
setTimeout
(
function
()
{
fn
。
apply
(
context
,
arg
)
},
delay
)
}
}
function
fn
()
{
console
。
log
(
‘防抖’
)
}
addEventListener
(
‘scroll’
,
debounce
(
fn
,
1000
))
原作者姓名: 陳煜侖
原出處:掘金
原文連結:20道JS原理題助你面試一臂之力!