JS 經典問題
必問
(零) AMD、CMD、CommonJs、ES6的對比
對模塊定義的規範化
解決:模塊化主要解決兩個問題,命名衝突
、文件依賴
AMD | CMD | CommonJs | ES6 |
---|---|---|---|
requireJS | SeaJS | Nodejs | |
define | define | module.exports | export/import |
AMD是預加載 | CMD是懶加載 | ||
優:加載快速,並行加載 並且執行 | 同AMD ,不執行 | ||
缺:執行順序不可控,容易埋坑 | 順序執行,等待時間較長 |
注意:Commonjs 中 module.export
跟exports
的區別?
返回對象不同 module.export 可以返回單獨返回一個數據類型,而 export只能返回 Object
(一)閉包
簡單來說閉包就是函數套函數。
一個函數在執行開始 會給其內部的變量分配一部分內存空間,被後面語句使用,當函數執行完畢,這些變量 爲被引用後,則會釋放這些內存空間,但是,若在函數內存在一個子函數 在調用主函數的變量的時候,這些變量如果被引用 則這個子函數和這些變量 會被解析器 保存起來 形成一個閉包
優勢: 變量私有化
劣勢:容易內存泄漏, 解決方法,退出函數之前,變量刪除
(二)原型鏈
參考-面試題:https://www.cnblogs.com/wjyz/p/10219106.html
基礎:https://www.jianshu.com/p/08c07a953fa0
什麼是原型鏈?
每個對象都有原型 _proto_,而 原型 還可以由原型 ,以此類推 ,就形成了原型鏈
prototype 原型指針:是函數獨有 的,是一個函數指向一個對象
(三)手寫繼承
參考:https://www.jianshu.com/p/6925ed009f1e
參考:https://blog.csdn.net/p312011150/article/details/83579313
1. 組合繼承
//一般情況 都是使用 call this 變量來進行變量參數傳遞
function animal(name) {
this.name = name || "動物";
this.run = () => {
console.log(this.name + "==>跑啊");
};
}
// 原型方法
animal.prototype.eat = function(food) {
console.log(this.name + "正在喫:" + food);
};
function Dog() {
animal.call(this);
this.name = "狗狗";
}
Dog.prototype = new animal();
let dog = new Dog();
console.log(dog);
console.log(dog.name);
console.log(dog.run());
console.log(dog.eat("6666"));
2. Object.create()
var Parent = {
getName: function() {
return this.name;
}
}
var child = Object.create(Parent, {
name: { value: "Benjamin"},
url : { value: "http://www.zuojj.com"}
});
console.log(child);
console.log(child.getName());
3. ES6 Class Extend
class Parent {
constructor(name){
this.name = name;
static sayHello(){
console.log(this.name)
}
}
}
class Child extends Parent {
constructor(name, age){
super(name);
this.age = age;
}
sayAge(){
console.log(this.age)
return this.age;
}
}
let parent = new Parent("Parent");
let child = new Child("Child", 18);
(四)深copy 淺 Copy
參考:https://www.cnblogs.com/ljx20180807/p/9790239.html
參考:https://mp.weixin.qq.com/s/vXbFsG59L1Ba0DMcZeU2Bg
Object.assign({},srcObj);
如果對象的屬性值爲簡單類型 得到的新對象爲深拷貝;
如果屬性值爲對象或其它引用類型,那對於這個對象而言其實是淺拷貝的
- 深copy
簡單版本
const deepClone = obj => {
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
};
//對於數組而言 還可以使用
slice(0),concat
(五) Event Loop
參考:https://segmentfault.com/a/1190000016278115
參考:https://segmentfault.com/a/1190000018675871
Event Loop 是一個執行模型
注意:可以將 EventLoop 看成一個單線程結構 ,他將 IO 操作拋出給其他線程,繼續執行 他的 方法,在IO 先出現結束後 EventLoop 再把結果拋出主線程
Promise所有的then的回調函數是在一個microtask函數中執行的,但是每一個回調函數的執行,又按照情況分爲立即執行,微任務(microtask)和宏任務(macrotask)。
我太難了:這個解釋太不靠譜了,需要深入瞭解一下
- 宏隊列: setTimeOut setInterval I/O 等
- 微隊列:Promise Object.observe 等
調用棧Stack
瀏覽器執行代碼的過程
:1.執行 全部 JS 代碼, 遇到 微任務 ,宏任務,全部拋出到其各自的棧中,繼續執行
:2.JS Stack 爲空後,再次執行 微任務 ,執行完後 (一個一個的放入Stack 執行)
:3:在執行 爲首的 一個宏任務,放入Stack中執行
:4:Stack 爲空後繼續 2-4
(五)常見算法
- 數組去重
let unique = (array)=>{
return Array.from(new Set(array))
}
unique = (array)=>{
const res = new Map();//WeakSet
return array.filter(curr=>{
let isIn = res.has(curr);
if(!isIn) res.set(curr,0)//0 不重要
return !isIn
})
}
- 數組排序
//冒泡排序
const sortWay = (array)=>{
for(let i=0,len =array.length;i<(len-1);i++ ){
let curr = array[i],currNext = array[i+1];
if(curr < currNext){//降序
array[i]= currNext;
array[i+1]= curr;
}
}
}
//數組自帶的sort
array = array.sort((a,b)=>{return a-b})
//數組打亂的算法
//隨機取出 20個 數
let a = [1,2,3,4,5....,50];
a = a.map((curr,index)=>{
return {value:curr,radom:Math.radom()}
})).sort((a,b)=>{
return a.radom -b-radom;
}).map(curr=>{
return curr.value;
}).slice(0,20);
談談你對js堆和棧的理解
基本問題
(一) typeof 和 instanceof 區別
- typeof在判斷 null、array、object以及函數實例(new + 函數)時,得到的都是object
- instanceof 判斷一個實例是否屬於某種類型 string arrray
(二)es6 常見問題
- 基本數據類型
Number,String, Null, Undefined, Symbol, Boolean
(三)new 做了什麼
- 創建一個新的對象
- 新對象的
_proto_
指向 構造對象的prototype
- 構造函數的作用域 賦值給新對象
- 執行構造函數的代碼
- 返回這新的對象
(四) arguments與arguments轉化成數組的方法
第一:這個問題很奇葩, 但是問了,不會 就尷尬了
function a(){
console.log(arguments)
}
a([1,2,3,4,5,6,7])
轉換爲數組
function a(){
let newResult = [];
for(let i =0,len = arguments.length;i<len;i++){
newResult.push(arguments[i])
}
return newRresult;
}
a(1,2,3,4,5,6,7)
(五)數據結構
(六)跨域 和cros
參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
跨域
跨域:不同源 就跨域,同源是指 同 協議,同 域名,同端口,
Cros
Cros :跨域資源共享(Cross-origin resource sharing)
是一種機制,出於安全原因,瀏覽器限制從JS內發起的跨源HTTP請求,並不一定是瀏覽器限制了發起跨站請求,也可能是跨站請求可以正常發起,但是返回結果被瀏覽器攔截了
衍生問題
- 爲什麼要跨域 優缺點是?
- 怎麼解決跨域的問題
- 什麼情況需要Cros
跨域 xhr 請求,下載圖片 ,css 中的字體引用
- 什麼是
簡單請求
什麼預檢請求
簡單請求 :不會 發送預檢請求 的請求 (GET、HEAD、POST)
預檢請求:會發出( OPTIONS 請求進行預先判斷允許跨域)(DELETE、PUT,OPTIONS)
- http 常見的請求方式
HTTP請求方法並不是只有GET和POST,只是最常用的。據RFC2616標準(現行的HTTP/1.1)得知,通常有以下8種方法:OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE和CONNECT。
官方定義
HEAD方法跟GET方法相同,只不過服務器響應時不會返回消息體。一個HEAD請求的響應中,HTTP頭中包含的元信息應該和一個GET請求的響應消息相同。這種方法可以用來獲取請求中隱含的元信息,而不用傳輸實體本身。也經常用來測試超鏈接的有效性、可用性和最近的修改。
一個HEAD請求的響應可被緩存,也就是說,響應中的信息可能用來更新之前緩存的實體。如果當前實體跟緩存實體的閾值不同(可通過Content-Length、Content-MD5、ETag或Last-Modified的變化來表明),那麼這個緩存就被視爲過期了。
簡而言之
HEAD請求常常被忽略,但是能提供很多有用的信息,特別是在有限的速度和帶寬下。主要有以下特點:
1、只請求資源的首部;
2、檢查超鏈接的有效性;
3、檢查網頁是否被修改;
4、多用於自動搜索機器人獲取網頁的標誌信息,獲取rss種子信息,或者傳遞安全認證信息等
- 這麼配置 允許Cros
Access-Control-Allow-Origin: http://foo.example – 限制允許的域名
Access-Control-Allow-Methods: POST, GET, OPTIONS – 限制允許的請求類型
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400 – 請求結果緩存時間
(七)web安全
參考:https://developer.mozilla.org/zh-CN/docs/Web/Security
例子寫得好:https://blog.csdn.net/freeking101/article/details/86537087
- xss 攻擊
跨站腳本攻擊
是一種代碼注入攻擊
存儲型 XSS :惡意代碼存儲到 數據庫中 服務端執行
反射型 XSS :惡意代碼存在 URL 裏。服務端執行
DOM型XSS :惡意代碼僞造在URL裏, 在瀏覽器端執行
- CSRF攻擊
跨站請求僞造
由字面上的意思 就能理解了吧應該
注意:CSRF攻擊是源於Web的隱式身份驗證機制 ,,大部分是走的cookie 做驗證機制
而Cookie 的本身會隨着域名下的所有請求達到服務端,這樣 就僞造了請求
解決方法
CSRF | XSS |
---|---|
reffer驗證 | 輸入轉義 |
身份代碼放在 localstorage、html 或者其他位置(不放在cookie)自定義xhr header | 輸入內容長度控制 |
動態簽名 | 避免拼接 HTML |
- | 對 各種插入數據進行編碼 |
(八)ES6 的箭頭函數 this 指向
=> | function |
---|---|
window | 父級 |