1、解釋一下什么是閉包 ?
閉包:就是能夠讀取外層函數(shù)內(nèi)部變量的函數(shù)。
閉包需要滿足三個(gè)條件:
訪問(wèn)所在作用域;
函數(shù)嵌套;
在所在作用域外被調(diào)用 。
優(yōu)點(diǎn): 可以重復(fù)使用變量,并且不會(huì)造成變量污染 。
缺點(diǎn): 會(huì)引起內(nèi)存泄漏
使用閉包的注意點(diǎn):
由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對(duì)象
(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時(shí)一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
var Utils
var utils = Utils = (function () {
class Utils {
noConflict(bool = false) {
if (typeof bool != 'boolean') {
throw new Error('參數(shù)必須是布爾類型')
}
// 主動(dòng)放棄全局變量utils
window.utils = undefined
// 如果傳遞的參數(shù)是true
if (bool) {
// 主動(dòng)放棄全局變量Utils
window.Utils = undefined
// this代表new出來(lái)的對(duì)象
return this
}
}
fn1() { }
fn2() { }
fn3() { }
var utils
return (function () {
if (!utils) {
utils = new Utils
}
return utils
})()
})()
2、解釋一下原型和原型鏈 ?
原型
原型就是一個(gè)為對(duì)象實(shí)例定義了一些公共屬性和公共方法的對(duì)象模板。
原型鏈
對(duì)象之間的繼承關(guān)系通過(guò)構(gòu)造函數(shù)的prototype指向父類對(duì)象,直到指向Object對(duì)象為止形成的指向鏈條。
通俗講: 原型鏈?zhǔn)窃蛯?duì)象創(chuàng)建過(guò)程的歷史記錄。
注:在javascript中,所有的對(duì)象都擁有一個(gè)__proto__屬性指向該對(duì)象的原型(prototype) 。
3、說(shuō)一下 ES6 中你熟悉的一些內(nèi)容 ?
1 class 類的繼承ES6中不再像ES5一樣使用原型鏈實(shí)現(xiàn)繼承,而是引入Class這個(gè)概念
2 async、await使用 async/await, 搭配promise,可以通過(guò)編寫形似同步的代碼來(lái)處理異步流程, 提高代碼的簡(jiǎn)潔性和可讀性async 用于申明一個(gè) function 是異步的,而 await 用于等待一個(gè)異步方法執(zhí)行完成
3 Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案(回調(diào)函數(shù)和事件)更合理、強(qiáng)大
4 Symbol是一種基本類型。Symbol 通過(guò)調(diào)用symbol函數(shù)產(chǎn)生,它接收一個(gè)可選的名字參數(shù),該函數(shù)返回的symbol是唯一的
5 Proxy代理使用代理(Proxy)監(jiān)聽(tīng)對(duì)象的操作,然后可以做一些相應(yīng)事情
6 Set是類似于數(shù)組的數(shù)據(jù)集合,無(wú)序,插入刪除速度快,元素不重復(fù),查找速度快。
7 Map是一個(gè)類似對(duì)象的數(shù)據(jù)結(jié)構(gòu),和對(duì)象不同的在于它的key可以是任意類型,但是對(duì)象只能使用 string和symbol類型,Map的存儲(chǔ)關(guān)聯(lián)性更強(qiáng)
8 生成器函數(shù)可以進(jìn)行阻斷函數(shù)執(zhí)行的過(guò)程,通過(guò)傳參可以傳入新的值進(jìn)入函數(shù)繼續(xù)執(zhí)行,可以用于將異步變?yōu)樽枞酵?/p>
4、數(shù)組排序的方式 ?
冒泡排序
var arr = [4, 23, 14, 52, 66, 1134, 567, 23];
for (var j = 0; j < arr.length - 1; j++) {
for (var i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
var tmp = arr[i]
arr[i] = arr[i + 1]
arr[i + 1] = tmp
}
}
}
console.log(arr);//[4, 14, 23, 23, 52, 66, 567, 1134]
選擇排序
var arr = [4, 23, 14, 52, 66, 1134, 567, 23];
for (var j = 0; j < arr.length - 1; j++) {
for (var i = j + 1; i < arr.length; i++) {
if (arr[j] > arr[i]) {
var tmp = arr[j]
arr[j] = arr[i]
arr[i] = tmp
}
}
}
console.log(arr);//[4, 14, 23, 23, 52, 66, 567, 1134]
5、什么是事件輪詢(EventLoop) ?
1、所有任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧。
2、主線程發(fā)現(xiàn)有異步任務(wù),如果是微任務(wù)就把他放到微任務(wù)的消息隊(duì)列里,如果是宏任務(wù)就把他
放到宏任務(wù)的消息隊(duì)列里。
3、執(zhí)行棧所有同步任務(wù)執(zhí)行完畢。
4、執(zhí)行至清空微任務(wù)隊(duì)列,之后再執(zhí)行至清空宏任務(wù)隊(duì)列。
5、輪詢第4步。
async function async1() {
console.log("async1 start")//2
await async2()
console.log("async1 end")//6
}
async function async2() {
console.log(async2)//3
}
console.log("script start")//1
setTimeout(function () {
console.log("setTimeout")//8
}, 0)
async1()
new Promise(function (resolve) {
console.log("promise1")//4
resolve()
}).then(function () {
console.log("promise2")//7
})
console.log("script end")//5
6、數(shù)組的一些API, 哪些能夠改變?cè)瓟?shù)組, 那些不能 ?
改變?cè)瓟?shù)組的方法:
unshift,push,shift,pop,splice,sort,reverse
不改變?cè)瓟?shù)組的方法:
concat,every,filter,forEach,indexOf,join,lastIndexOf,mapsome,every,slice,reduce,reduceRight,flat,flatMap,find
7、for 循環(huán)與 forEach 的區(qū)別 ?
-
1.for循環(huán)可以使用break跳出循環(huán),但forEach不能。
-
2.for循環(huán)可以控制循環(huán)起點(diǎn)(i初始化的數(shù)字決定循環(huán)的起點(diǎn)),forEach只能默認(rèn)從索引0開(kāi)始。
-
3.for循環(huán)過(guò)程中支持修改索引(修改 i),但forEach做不到(底層控制index自增,無(wú)法左右它)。
8、深淺拷貝 ?
淺拷貝 - 數(shù)組
var arr = [1, 2, 3, [4, 5, 6]]
var brr = []
// 1.淺拷貝 - Object.assign(新, 舊)
Object.assign(brr, arr);
console.log(arr, brr);
console.log(arr === brr); // false
console.log(arr[3] === brr[3]) // true
// 2.淺拷貝 - 利用數(shù)組方法slice
brr = arr.slice();
console.log(arr, brr);
console.log(arr === brr); // false
console.log(arr[3] === brr[3]); // true
// 3.淺拷貝 - 利用數(shù)組方法concat
brr = arr.concat();
console.log(arr, brr);
console.log(arr === brr); // false
console.log(arr[3] === brr[3]); // true
淺拷貝 - 對(duì)象
var obj = {
name: '張三',
age: 12,
wife: {
name: '翠花',
age: 13
}
}
var pbj = {}
// 1.淺拷貝 - Object.assign(新, 舊)
Object.assign(pbj, obj)
console.log(pbj, obj);
console.log(pbj === obj); // false
console.log(pbj.wife === obj.wife); // true
// 2.遍歷對(duì)象,將每個(gè)鍵值對(duì)放在新的對(duì)象中
for (var key in obj) {
pbj[key] = obj[key]
}
console.log(pbj, obj);
console.log(pbj === obj); // false
console.log(pbj.wife === obj.wife); // true
深拷貝 - 數(shù)組
// 1.JSON轉(zhuǎn)換的語(yǔ)法
// 將object類型轉(zhuǎn)成json格式的字符串 - JSON.stringify(object類型的數(shù)據(jù))
// 將json格式的字符串轉(zhuǎn)成object類型 - JSON.parse(json格式的字符串)
var arr = [1, 2, 3]
brr = JSON.parse(JSON.stringify(arr))
console.log(arr, brr);
console.log(arr === brr); // false
console.log(arr[3] === brr[3]); // false
var arr = [1, 2, 3, [4, 5, 6], function () { }]
// JSON轉(zhuǎn)換會(huì)丟失函數(shù)
// 2.split方法
var str = arr.toString()
console.log(str);
var brr = str.split(',')
console.log(brr);
深拷貝 - 對(duì)象
var obj = {
name: '張三',
age: 12,
wife: {
name: '翠花',
age: 13
}
}
// 1.JSON轉(zhuǎn)換的語(yǔ)法
var pbj = JSON.parse(JSON.stringify(obj))
console.log(obj, pbj);
console.log(obj === pbj); // false
console.log(obj.wife === pbj.wife); // false
// JSON轉(zhuǎn)換的缺陷
var obj = {
name: '張三',
age: 12,
wife: {
name: '翠花',
age: 13
},
eat: function () {
console.log('能吃');
}
}
// 如果原數(shù)據(jù)中有方法,在轉(zhuǎn)換過(guò)程中會(huì)丟失方法
萬(wàn)能深拷貝函數(shù)
const cloneDeep = (data) => {
if (data instanceof Date) return new Date(data)
if (data instanceof Function) return new Function('return ' + data.toString()).call(undefined)
if (data instanceof RegExp) return new RegExp(data)
let target
if (Object.prototype.toString.call(data) === '[object Object]') target = {}
else if (Object.prototype.toString.call(data) === '[object Array]') target = []
else return data
for (const key in data) {
if (data.hasOwnProperty(key)) target[key] = cloneDeep(data[key])
}
return target
}
9、url 的組成 ?
http:/https: 協(xié)議
www.baidu.com 域名
:8080 端口
/sf/vsearch 路徑
?wd=百度熱搜 查詢(可有可無(wú))
#a=1&b=2 哈希值(可有可無(wú))
10、常見(jiàn)的跨域方式 ?
JSONP:
JSONP是利用外鏈腳本,沒(méi)有跨源限制的特點(diǎn),來(lái)實(shí)現(xiàn)跨源請(qǐng)求的一種技術(shù)。
CORS:
cors:跨域資源共享,是一種實(shí)現(xiàn)跨源請(qǐng)求數(shù)據(jù)的技術(shù)。這就是跨源問(wèn)題的解決方案之一。也是廣泛的解決方案。
正向代理
先搭建一個(gè)屬于自己的代理服務(wù)器
1、用戶發(fā)送請(qǐng)求到自己的代理服務(wù)器
2、自己的代理服務(wù)器發(fā)送請(qǐng)求到服務(wù)器
3、服務(wù)器將數(shù)據(jù)返回到自己的代理服務(wù)器
4、自己的代理服務(wù)器再將數(shù)據(jù)返回給用戶
反向代理
1、用戶發(fā)送請(qǐng)求到服務(wù)器(訪問(wèn)的其實(shí)是反向代理服務(wù)器,但用戶不知道)
2、反向代理服務(wù)器發(fā)送請(qǐng)求到真正的服務(wù)器
3、真正的服務(wù)器將數(shù)據(jù)返回給反向代理服務(wù)器
4、反向代理服務(wù)器再將數(shù)據(jù)返回給用戶
11、Promise 的使用場(chǎng)景 ?
場(chǎng)景1:獲取文件信息。
場(chǎng)景2:配合AJAX獲取信息
場(chǎng)景3:解決回調(diào)地獄,實(shí)現(xiàn)串行任務(wù)隊(duì)列。
場(chǎng)景4: node中進(jìn)行本地操作的異步過(guò)程
12、let, const, var 的區(qū)別 ?
聲明方式 變量提升 暫時(shí)性死區(qū) 重復(fù)聲明 初始值 作用域
var 允許 不存在 允許 不需要 非塊級(jí)
let 不允許 存在 不允許 不需要 塊級(jí)
const 不允許 存在 不允許 需要 塊級(jí)
13、對(duì) this 的理解, 三種改變 this 的方式 ?
1.任何情況下直接在script中寫入的this都是window。
2.函數(shù)中的this 非嚴(yán)格模式:this指向window, 嚴(yán)格模式時(shí):this指向undefined。
3.箭頭函數(shù)的this
this都指向箭頭函數(shù)外上下文環(huán)境的this指向
4.對(duì)象中this
對(duì)象屬性的this 指向?qū)ο笸馍舷挛沫h(huán)境的this
對(duì)象方法(普通函數(shù))中的this,指向當(dāng)前對(duì)象(誰(shuí)執(zhí)行該方法,this就指向誰(shuí))
5.回調(diào)函數(shù)的this指向
1)、 setTimeout,setInterval回調(diào)函數(shù)不管是否是嚴(yán)格模式都會(huì)指向window。
2)、通過(guò)在函數(shù)內(nèi)執(zhí)行當(dāng)前回調(diào)函數(shù) 非嚴(yán)格模式:this指向window, 嚴(yán)格模式時(shí):this指向undefined。
3)遞歸函數(shù)中的this 非嚴(yán)格模式:this指向window, 嚴(yán)格模式時(shí):this指向undefined。
使用arguments0執(zhí)行函數(shù)時(shí) this指向arguments。
5)事件中的回調(diào)函數(shù),this指向事件偵聽(tīng)的對(duì)象(e.currentTarget);
6、call,apply,bind方法執(zhí)行時(shí)this的指向
如果call,apply,bind傳參時(shí),第一個(gè)參數(shù)傳入的不是null或者undefined,傳入什么this指向什么
如果第一個(gè)參數(shù)傳入的是null或者undefined ,非嚴(yán)格模式下指向window
7、在ES6的類中this的指向
構(gòu)造函數(shù)中的this指向?qū)嵗?dāng)前類所產(chǎn)生的新的實(shí)例對(duì)象
類中實(shí)例化方法中this指向誰(shuí)執(zhí)行該方法,this指向誰(shuí)
類中靜態(tài)方法中this執(zhí)行該類或者該類的構(gòu)造函數(shù)
類中實(shí)例化箭頭方法,this仍然指向當(dāng)前類實(shí)例化的實(shí)例對(duì)象
8、ES5的原型對(duì)象中this的指向
在原型的方法中,this指向?qū)嵗?dāng)前構(gòu)造函數(shù)的實(shí)例化對(duì)象(誰(shuí)執(zhí)行該方法,this指向誰(shuí));
三種改變this指向的方式
函數(shù)名.call(this,…)this寫誰(shuí)就指誰(shuí)。
函數(shù)名.apply(this,[參數(shù)1,參數(shù)2,…]) this寫誰(shuí)就指誰(shuí)。
函數(shù)名. bind (this,1,2,3) this寫誰(shuí)就指誰(shuí)。
14、cookie, localStorage,sessionStorage 的區(qū)別 ?
存儲(chǔ)方式 作用與特性 存儲(chǔ)數(shù)量及大小
cookie
存儲(chǔ)方式:
存儲(chǔ)用戶信息,獲取數(shù)據(jù)需要與服務(wù)器建立連接。
以路徑存儲(chǔ),上層路徑不能訪問(wèn)下層的路徑cookie,下層的路徑cookie可以訪問(wèn)上層的路徑cookie
作用與特性:
可存儲(chǔ)的數(shù)據(jù)有限,且依賴于服務(wù)器,無(wú)需請(qǐng)求服務(wù)器的數(shù)據(jù)盡量不要存放在cookie 中,以免影響頁(yè)面性能。
可設(shè)置過(guò)期時(shí)間。
存儲(chǔ)數(shù)量及大小:
將cookie控制在4095B以內(nèi),超出的數(shù)據(jù)會(huì)被忽略。
IE6或更低版本 最多存20個(gè)cookie;
IE7及以上
版本 多可以有50個(gè);
Firefox多 50個(gè);
chrome和Safari沒(méi)有做硬性限制。
cookie最大特征就是可以在頁(yè)面與服務(wù)器間互相傳遞,當(dāng)發(fā)送或者接受數(shù)據(jù)時(shí)自動(dòng)傳遞
localStorage
存儲(chǔ)客戶端信息,無(wú)需請(qǐng)求服務(wù)器。
數(shù)據(jù)永久保存,除非用戶手動(dòng)清理客戶端緩存。
開(kāi)發(fā)者可自行封裝一個(gè)方法,設(shè)置失效時(shí)間。 5M左右,各瀏覽器的存儲(chǔ)空間有差異。
任何地方都可以存都可以取
操作簡(jiǎn)單
sessionStorage
存儲(chǔ)客戶端信息,無(wú)需請(qǐng)求服務(wù)器。
數(shù)據(jù)保存在當(dāng)前會(huì)話,刷新頁(yè)面數(shù)據(jù)不會(huì)被清除,結(jié)束會(huì)話(關(guān)閉瀏覽器、關(guān)閉頁(yè)面、跳轉(zhuǎn)頁(yè)面)數(shù)據(jù)失效。
5M左右,各瀏覽器的存儲(chǔ)空間有差異。
同頁(yè)面不同窗口中數(shù)據(jù)不會(huì)共享
15、輸入 url 到打開(kāi)頁(yè)面 都做了什么事情 ?
輸入U(xiǎn)RL
訪問(wèn)hosts解析,如果沒(méi)有解析訪問(wèn)DNS解析
TCP握手
HTTP請(qǐng)求
HTTP響應(yīng)返回?cái)?shù)據(jù)
瀏覽器解析并渲染頁(yè)面
16、原生 ajax 的流程 ?
// 創(chuàng)建xhr
var xhr = new XMLHTTPRequest()
// 偵聽(tīng)通信狀態(tài)改變的事件
xhr.addEventListener("readystatechange", readyStateChangeHandler);
// Method 分為 get post put delete等等
// Async 異步同步
// name和password是用戶名和密碼
xhr.open(Method, URL, Async, name, password)
// 發(fā)送內(nèi)容給服務(wù)器
xhr.send('內(nèi)容')
function readyStateChangeHandler(e) {
// 當(dāng)狀態(tài)是4時(shí),并且響應(yīng)頭成功200時(shí),
if (xhr.readyState === 4 && xhr.status === 200) {
// 打印返回的消息
console.log(xhr.response)
}
}
17、如何實(shí)現(xiàn)繼承 ?
對(duì)于 JavaScript 來(lái)說(shuō),繼承有兩個(gè)要點(diǎn):
1 復(fù)用父構(gòu)造函數(shù)中的代碼
2 復(fù)用父原型中的代碼第一種實(shí)現(xiàn)復(fù)用父構(gòu)造函數(shù)中的代碼,我們可以考慮調(diào)用父構(gòu)造函數(shù)并將 this 綁定到子構(gòu)造函數(shù)。
第一種方法:復(fù)用父原型中的代碼,我們只需改變?cè)玩溂纯?。將子?gòu)造函數(shù)的原型對(duì)象的 proto 屬性指向父構(gòu)造函數(shù)的原型對(duì)象。
第二種實(shí)現(xiàn)
使用 new 操作符來(lái)替代直接使用 proto 屬性來(lái)改變?cè)玩湣?/p>
第三種實(shí)現(xiàn)
使用一個(gè)空構(gòu)造函數(shù)來(lái)作為中介函數(shù),這樣就不會(huì)將構(gòu)造函數(shù)中的屬性混到 prototype 中
function A(x, y) {
this.x = x
this.y = y
}
A.prototype.run = function () { }
// 寄生繼承 二者一起使用
function B(x, y) {
A.call(this, x, y) // 借用繼承
}
B.prototype = new A() // 原型繼承
// 組合繼承
Function.prototype.extends = function (superClass) {
function F() { }
F.prototype = superClass.prototype
if (superClass.prototype.constructor !== superClass) {
Object.defineProperty(superClass.prototype, 'constructor', { value: superClass })
}
let proto = this.prototype
this.prototype = new F()
let names = Reflect.ownKeys(proto)
for (let i = 0; i < names.length; i++) {
let desc = Object.getOwnPropertyDescriptor(proto, names[i])
Object.defineProperty(this.prototypr, name[i], desc)
}
this.prototype.super = function (arg) {
superClass.apply(this, arg)
}
this.prototype.supers = superClass.prototype
}
第四種實(shí)現(xiàn)
es6類的繼承extends。
18、null 和 undefined 的區(qū)別 ?
null是一個(gè)表示"無(wú)"的對(duì)象(空對(duì)象指針),轉(zhuǎn)為數(shù)值時(shí)為0;
undefined是一個(gè)表示"無(wú)"的原始值,轉(zhuǎn)為數(shù)值時(shí)為NaN。
拓展:
null表示"沒(méi)有對(duì)象",即該處不應(yīng)該有值。典型用法是:
1 作為函數(shù)的參數(shù),表示該函數(shù)的參數(shù)不是對(duì)象。
2 作為對(duì)象原型鏈的終點(diǎn)。
undefined表示"缺少值",就是此處應(yīng)該有一個(gè)值,但是還沒(méi)有定義。典型用法是:
1 變量被聲明了,但沒(méi)有賦值時(shí),就等于undefined。
2 調(diào)用函數(shù)時(shí),應(yīng)該提供的參數(shù)沒(méi)有提供,該參數(shù)等于undefined。
3 對(duì)象沒(méi)有賦值的屬性,該屬性的值為undefined。
4 函數(shù)沒(méi)有返回值時(shí),默認(rèn)返回undefined。
19、函數(shù)的節(jié)流和防抖 ?
節(jié)流
節(jié)流是指當(dāng)一個(gè)事件觸發(fā)的時(shí)候,為防止事件的連續(xù)頻繁觸發(fā),設(shè)置定時(shí)器,達(dá)到一種一段事件內(nèi)只觸發(fā)一次的效果,在當(dāng)前事件內(nèi)不會(huì)再次觸發(fā),當(dāng)前事件結(jié)束以后,再次觸發(fā)才有效.
function thro(cb,wait){
let timeOut
return function(){
if(timeOut) return
timeOut = setTimeout(function(){
cb()
clearTimeout(timeOut)
timeOut = null
},wait)
}
}
防抖
防抖是指當(dāng)一個(gè)事件觸發(fā)的時(shí)候, 為防止頻繁觸發(fā)事件, 設(shè)置定時(shí)器,以達(dá)到一種 頻繁觸發(fā)期間不處理, 只有當(dāng)最后一次連續(xù)觸發(fā)結(jié)束以后才處理
function debounce(cb,wait){
let timer
return function(){
clearTimeout(timer)
timer = setTimeout(()=>cb(),wait)
}
}
20、什么是 Promise ?
Promise 是異步編程的一種解決方案:從語(yǔ)法上講,promise是一個(gè)對(duì)象,從它可以獲取異步操作的消息;
從本意上講,它是承諾,承諾它過(guò)一段時(shí)間會(huì)給你一個(gè)結(jié)果。
promise有三種狀態(tài): pending(等待態(tài)),fulfilled(成功態(tài)),rejected(失敗態(tài));狀態(tài)一旦改變,就不會(huì)再變。創(chuàng)造promise實(shí)例后,它會(huì)立即執(zhí)行
promise是用來(lái)解決兩個(gè)問(wèn)題的:
回調(diào)地獄,代碼難以維護(hù), 常常第一個(gè)的函數(shù)的輸出是第二個(gè)函數(shù)的輸入這種現(xiàn)象
promise可以支持多個(gè)并發(fā)的請(qǐng)求,獲取并發(fā)請(qǐng)求中的數(shù)據(jù)
這個(gè)promise可以解決異步的問(wèn)題,本身不能說(shuō)promise是異步的
let myPromise = new Promise(function(myResolve, myReject) {
// "Producing Code"(可能需要一些時(shí)間)
myResolve(1); // 成功時(shí)
myReject(); // 出錯(cuò)時(shí)
}).then(res => {
console.log(res);//1
}).catch(res => {
console.log(res);
}) ;
21、普通函數(shù)與箭頭函數(shù)的區(qū)別 ?
1.箭頭函數(shù)沒(méi)有prototype(原型),箭頭函數(shù)沒(méi)有自己的this,繼承的是外層代碼塊的this。
2.不可以當(dāng)做構(gòu)造函數(shù),也就是說(shuō)不可以使用new命令,否則會(huì)報(bào)錯(cuò)的。
3.不可以使用arguments對(duì)象,該對(duì)象在函數(shù)體內(nèi)不存在。如果要用,可以用 rest 參數(shù)代替。
4.不可以使用yield命令,因此箭頭函數(shù)不能用作 Generator(生成器) 函數(shù)。
5.因?yàn)闆](méi)有this,所以不能使用call、bind、apply來(lái)改變this的指向。
22、設(shè)計(jì)模式有哪些, 分別說(shuō)一說(shuō) ?
共23種設(shè)計(jì)模式,介紹其中6種應(yīng)用較為廣泛的模式。
發(fā)布訂閱模式:
這種設(shè)計(jì)模式可以大大降低程序模塊之間的耦合度,便于更加靈活的擴(kuò)展和維護(hù)。
中介者模式 :
觀察者模式通過(guò)維護(hù)一堆列表來(lái)管理對(duì)象間的多對(duì)多關(guān)系,中介者模式通過(guò)統(tǒng)一接口來(lái)維護(hù)一對(duì)多關(guān)系,且通信者之間不需要知道彼此之間的關(guān)系,只需要約定好API即可。
代理模式 :
為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
代理模式使得代理對(duì)象控制具體對(duì)象的引用。代理幾乎可以是任何對(duì)象:文件,資源,內(nèi)存中的對(duì)象,或者是一些難以復(fù)制的東西。
單例模式 :
保證一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)(調(diào)用一個(gè)類,任何時(shí)候返回的都是同一個(gè)實(shí)例)。
工廠模式 :
工廠模式定義一個(gè)用于創(chuàng)建對(duì)象的接口,這個(gè)接口由子類決定實(shí)例化哪一個(gè)類。該模式使一
個(gè)類的實(shí)例化延遲到了子類。而子類可以重寫接口方法以便創(chuàng)建的時(shí)候指定自己的對(duì)象類型
裝飾者模式 : 裝飾者(decorator)模式能夠在不改變對(duì)象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)像動(dòng)態(tài)的添加職責(zé)(方法或?qū)傩裕?。與繼承相比,裝飾者是一種更輕便靈活的做法。
23、Promsie 和 async/await 的區(qū)別和使用 ?
區(qū)別:
1)函數(shù)前面多了一個(gè)async關(guān)鍵字。await關(guān)鍵字只能用在async定義的函數(shù)內(nèi)。async函數(shù)會(huì)隱式地返回一個(gè)promise,該promise的reosolve值就是函數(shù)return的值。
2)第1點(diǎn)暗示我們不能在 外層代碼中使用await,因?yàn)椴辉赼sync函數(shù)內(nèi)。
使用:
1.async和await是配對(duì)使用的,await存在于async的內(nèi)部。否則會(huì)報(bào)錯(cuò) 。
2.await表示在這里等待一個(gè)promise返回,再接下來(lái)執(zhí)行。
3.await后面跟著的應(yīng)該是一個(gè)promise對(duì)象,(也可以不是,如果不是接下來(lái)也沒(méi)什么意義了…)
24、談一談垃圾回收機(jī)制 ?
垃圾回收是動(dòng)態(tài)存儲(chǔ)管理技術(shù),會(huì)自動(dòng)地釋放“垃圾‘’(不再被程序引用的對(duì)象),按照特定的垃圾收集算法來(lái)實(shí)現(xiàn)資源自動(dòng)回收的功能。
回收的兩種機(jī)制:
1.標(biāo)記清除(make-and-sweep)
2.引用計(jì)數(shù) 垃圾回收器會(huì)按照固定的時(shí)間間隔周期性的執(zhí)行。
25、數(shù)組去重 ?
第一種,splice
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length;) {
if (arr[i] === arr[j]) arr.splice(j, 1);
else j++; // 核心
}
}
第二種,continue
var arr1 = [];
xt: for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr1.length; j++) {
if (arr1[j] === arr[i]) continue xt;
}
arr1.push(arr[i]);
}
第三種,indexOf
var arr1 = [];
for (var i = 0; i < arr.length; i++) {
if (arr1.indexOf(arr[i]) < 0) arr1.push(arr[i])
}
第四種,indexOf+取反符號(hào)
var arr1 = [];
for (var i = 0; i < arr.length; i++) {
if (!(~arr1.indexOf(arr[i]))) arr1.push(arr[i])
}
第五種,includes+取反符號(hào)
var arr1 = [];
for (var i = 0; i < arr.length; i++) {
if (!arr1.includes(arr[i])) arr1.push(arr[i])
}
第六種,Set()
arr=[1,2,3,1,2,3,1,2,3]
new Set(arr);
26、判斷對(duì)象為空 ?
第一種
使用JSON.stringify()將對(duì)象轉(zhuǎn)換為json字符串;
JSON.stringify(obj) === '{}'
第二種
使用for...in循環(huán)遍歷對(duì)象除Symbol以外的所有可枚舉屬性,當(dāng)對(duì)象有屬性存在返回false, 否則返回 true。
const obj = {}
function isObjectEmpty(obj) {
for (var key in obj) {
return false
}
return true
}
console.log(isObjectEmpty(obj))
第三種
Object.getOwnPropertyNames() 方法會(huì)返回該對(duì)象所有可枚舉和不可枚舉屬性的屬性名(不含Symbol 屬性)組成的數(shù)組。然后再通過(guò)判斷返回的數(shù)組長(zhǎng)度是否為零,如果為零的話就是空對(duì)象。
Object.getOwnPropertyNames(obj).length === 0
第四種
Object.keys() 是 ES5 新增的一個(gè)對(duì)象方法,該方法返回一個(gè)數(shù)組,包含指定對(duì)象自有的可枚舉屬性(不
含繼承的和Symbol屬性)。用此方法只需要判斷返回的數(shù)組長(zhǎng)度是否為零,如果為零的話就是空對(duì)象。
27、如何用一次循環(huán)找到數(shù)組中兩個(gè)最大的值 ?
var arr=[1,4,10,11,11,2,5,7,2,3,4];
var [max,second]=arr[0]>arr[1] ? [arr[0],arr[1]] : [arr[1],arr[0]];
for(var i=2;i<arr.length;i++){
if(arr[i]>max){
second=max;
max=arr[i];
}else if(arr[i]<=max && arr[i]>second){
second=arr[i];
}
}
28、new 一個(gè)對(duì)象的過(guò)程 ?
new的實(shí)現(xiàn)過(guò)程(實(shí)際上就是調(diào)用這個(gè)構(gòu)造函數(shù),同時(shí)將構(gòu)造函數(shù)的prototype上的屬性方法掛上去。)
1. 新建一個(gè)對(duì)象
2. 對(duì)象 繼承 構(gòu)造函數(shù)的 原型鏈
3. 將構(gòu)造函數(shù)的this指向這個(gè)對(duì)象
4. 構(gòu)造函數(shù)返回這個(gè)對(duì)象
29、箭頭函數(shù)為什么不能用 new ?
因?yàn)榧^函數(shù)沒(méi)有prototype也沒(méi)有自己的this指向并且不可以使用arguments。
30、如何實(shí)現(xiàn)數(shù)組的復(fù)制 ?
for循環(huán)逐一復(fù)制
var arr1=[];
for(var i=0;i<arr.length;i++){
if(i in arr) arr1[i]=arr[i]
}
...方式
var arr1=[...arr];
slice方法
var arr1=arr.slice();
concat方法
var arr1=arr.concat();
map方法
var arr1=arr.map(item=>item);
reduce
var arr1=arr.reduce((v,t)=>v.push(t),[])
31、http 的理解 ?
HTTP 協(xié)議是超文本傳輸協(xié)議,是客戶端瀏覽器或其他程序“請(qǐng)求”與 Web 服務(wù)器響應(yīng)之間的應(yīng)用層通信協(xié)議。
HTTPS主要是由HTTP+SSL構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的一種安全通信通道。
32、http 和 https 的區(qū)別 ?
1、https協(xié)議需要到ca申請(qǐng)證書(shū),一般免費(fèi)證書(shū)較少,因而需要一定費(fèi)用。
2、http是超文本傳輸協(xié)議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協(xié)議。
3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
4、http的連接很簡(jiǎn)單,是無(wú)狀態(tài)的;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份
認(rèn)證的網(wǎng)絡(luò)協(xié)議,比http協(xié)議安全。
33、git 的常用指令有哪些 ?
git branch 分支查看
git branch branch_1 增加分支
git checkout branch 分支切換
git merge branch_1 合并分支(合并前要切換當(dāng)前分支至master)
git branch -d branch_1 刪除分支
git remote 查看當(dāng)前倉(cāng)庫(kù)管理的遠(yuǎn)程倉(cāng)庫(kù)信息
git remote show origin 查看指定的遠(yuǎn)程倉(cāng)庫(kù)的詳細(xì)信息
git push --set-upstream origin branch_1 第一次將本地分支推到遠(yuǎn)程倉(cāng)庫(kù)
git push <遠(yuǎn)程主機(jī)名> <本地分支名>:<遠(yuǎn)程分支名> 將本地分支推到遠(yuǎn)程分支
git pull <遠(yuǎn)程主機(jī)名> <遠(yuǎn)程分支>:<本地分支> 將遠(yuǎn)程分支拉到本地分支
git branch -d branch_0 刪除本地合并后分支
git brench -D branch_0 刪除本地未合并分支
it push origin --delete branch_0 刪除遠(yuǎn)程分支
git restore [filename] 進(jìn)行清除工作區(qū)的改變
git tag 查看標(biāo)簽
git tag v1.0.0 打標(biāo)簽
git push origin v1.0.0 將tag同步到遠(yuǎn)程服務(wù)器
34、平時(shí)是使用 git 指令還是圖形化工具 ?
repository:git庫(kù)相關(guān)操作,基本意思就是字面意思。
1)資源管理器中瀏覽該Git庫(kù)工作空間文件,省去查找路徑不斷點(diǎn)擊鼠標(biāo)的操作。
2)啟動(dòng)Git bash工具(命令行工具)。
3)查看當(dāng)前分支文件狀態(tài),不包括未提交的信息。
4)查看某個(gè)分支的文件(彈出框中可選擇需要查看的版本、分支或標(biāo)簽),跟上一條差不多,用
的比較少,可能是沒(méi)有這方面的額需求。
5)可視化當(dāng)前分支歷史、可視化所有分支歷史:彈出分支操作歷史,也就是gitk工具,放到gitk工具中介紹。
edit:用于操作commit時(shí)操作信息輸入,只能操作文字輸入部分,你沒(méi)有看錯(cuò)。常用的快捷鍵大家
都知道,何必要單獨(dú)做成基本沒(méi)啥用的。本來(lái)以為對(duì)變更的文件進(jìn)行批量操作、本來(lái)以為可以對(duì)未
版本跟蹤的文件批量刪除、本來(lái)、、、,都說(shuō)了是本來(lái)。
Branch:新建分支(需要選擇其實(shí)版本,可以根據(jù)版本號(hào)、其他分支或標(biāo)簽來(lái)選擇)、檢出分支
(覺(jué)得切換分支更合適)、重命名分支、刪除分支、當(dāng)前分支Reset操作(會(huì)丟棄所有未提交的變
更,包括工作區(qū)和索引區(qū),當(dāng)然了,有彈出框提示危險(xiǎn)操作)。
36、Promsie.all() 使用過(guò)嗎, 它是怎么使用的 ?
promise.all()用于一個(gè)異步操作需要在幾個(gè)異步操作完成后再進(jìn)行時(shí)使用。
promise.all()接受一個(gè)promise對(duì)象組成的數(shù)組參數(shù),返回promise對(duì)象。
當(dāng)數(shù)組中所有promise都完成了,就執(zhí)行當(dāng)前promise對(duì)象的then方法,如果數(shù)組中有一個(gè)promise執(zhí)行失敗了,就執(zhí)行當(dāng)前promise對(duì)象的catch方法。
36、什么是三次握手和四次揮手 ?
三次握手是網(wǎng)絡(luò)客戶端跟網(wǎng)絡(luò)服務(wù)器之間建立連接,并進(jìn)行通信的過(guò)程。相當(dāng)于客戶端和服務(wù)器之間
你來(lái)我往的3個(gè)步驟。
第一次握手是建立連接,客戶端發(fā)送連接請(qǐng)求報(bào)文,并傳送規(guī)定的數(shù)據(jù)包;
第二次握手是服務(wù)器端表示接收到連接請(qǐng)求報(bào)文,并回傳規(guī)定的數(shù)據(jù)包;
第三次握手是客戶端接收到服務(wù)器回傳的數(shù)據(jù)包后,給服務(wù)器端再次發(fā)送數(shù)據(jù)包。這樣就完成了客
戶端跟服務(wù)器的連接和數(shù)據(jù)傳送。
四次揮手表示當(dāng)前這次連接請(qǐng)求已經(jīng)結(jié)束,要斷開(kāi)這次連接。
第一次揮手是客戶端對(duì)服務(wù)器發(fā)起斷開(kāi)請(qǐng)求,
第二次握手是服務(wù)器表示收到這次斷開(kāi)請(qǐng)求,
第三次握手是服務(wù)器表示已經(jīng)斷開(kāi)連接
第四次握手是客戶端斷開(kāi)連接。
37、for in 和 for of 循環(huán)的區(qū)別 ?
`for in` 用于遍歷對(duì)象的鍵(`key`),`for in`會(huì)遍歷所有自身的和原型鏈上的可枚舉屬性。如果是數(shù)組,
for in會(huì)將數(shù)組的索引(index)當(dāng)做對(duì)象的key來(lái)遍歷,其他的object也是一樣的。
`for of`是`es6`引入的語(yǔ)法,用于遍歷 所有迭代器iterator,其中包括`HTMLCollection`,`NodeList`,`Array`,`Map`,`Set`,`String`,`TypedArray`,`arguments`等
對(duì)象的值(`item`)。
38、async/await 怎么拋出錯(cuò)誤異常 ?
如果可能出錯(cuò)的代碼比較少的時(shí)候可以使用try/catch結(jié)構(gòu)來(lái)了處理,如果可能出錯(cuò)的代碼比較多的時(shí)候,可以利用async函數(shù)返回一個(gè)promise對(duì)象的原理來(lái)處理,給async修飾的函數(shù)調(diào)用后返回的promise對(duì)象,調(diào)用catch方法來(lái)處理異常。
39、 函數(shù)式編程和命令式編程的區(qū)別 ?
命令式編程(過(guò)程式編程) :
專注于”如何去做”,這樣不管”做什么”,都會(huì)按照你的命令去做。解決某一問(wèn)題的具體算法實(shí)現(xiàn)。
函數(shù)式編程:把運(yùn)算過(guò)程盡量寫成一系列嵌套的函數(shù)調(diào)用。
函數(shù)式編程強(qiáng)調(diào)沒(méi)有”副作用”,意味著函數(shù)要保持獨(dú)立,所有功能就是返回一個(gè)新的值,沒(méi)有其他行為,尤其是不得修改外部變量的值。
所謂”副作用”,指的是函數(shù)內(nèi)部與外部交互(最典型的情況,就是修改全局變量的值),產(chǎn)生運(yùn)算以外的其他結(jié)果。
40、http 常見(jiàn)的響應(yīng)狀態(tài)碼 ?
100——客戶必須繼續(xù)發(fā)出請(qǐng)求
101——客戶要求服務(wù)器根據(jù)請(qǐng)求轉(zhuǎn)換HTTP協(xié)議版本
200——交易成功
201——提示知道新文件的URL
202——接受和處理、但處理未完成
203——返回信息不確定或不完整
204——請(qǐng)求收到,但返回信息為空
205——服務(wù)器完成了請(qǐng)求,用戶代理必須復(fù)位當(dāng)前已經(jīng)瀏覽過(guò)的文件
206——服務(wù)器已經(jīng)完成了部分用戶的GET請(qǐng)求
300——請(qǐng)求的資源可在多處得到
301——?jiǎng)h除請(qǐng)求數(shù)據(jù)
302——在其他地址發(fā)現(xiàn)了請(qǐng)求數(shù)據(jù)
303——建議客戶訪問(wèn)其他URL或訪問(wèn)方式
304——客戶端已經(jīng)執(zhí)行了GET,但文件未變化
305——請(qǐng)求的資源必須從服務(wù)器指定的地址得到
306——前一版本HTTP中使用的代碼,現(xiàn)行版本中不再使用
307——申明請(qǐng)求的資源臨時(shí)性刪除
400——錯(cuò)誤請(qǐng)求,如語(yǔ)法錯(cuò)誤
401——請(qǐng)求授權(quán)失敗
402——保留有效ChargeTo頭響應(yīng)
403——請(qǐng)求不允許
404——沒(méi)有發(fā)現(xiàn)文件、查詢或URl
405——用戶在Request-Line字段定義的方法不允許
406——根據(jù)用戶發(fā)送的Accept拖,請(qǐng)求資源不可訪問(wèn)
407——類似401,用戶必須首先在代理服務(wù)器上得到授權(quán)
41、 什么是事件流以及事件流的傳播機(jī)制 ?
事件觸發(fā)后,從開(kāi)始找目標(biāo)元素,然后執(zhí)行目標(biāo)元素的事件,再到離開(kāi)目標(biāo)元素的整個(gè)過(guò)程稱之為事件流。
W3C標(biāo)準(zhǔn)瀏覽器事件流的傳播分為3個(gè)階段:捕獲階段、目標(biāo)階段、冒泡階段
捕獲階段指找目標(biāo)元素的過(guò)程,這個(gè)找的過(guò)程,是從最大的document對(duì)象到html,再到
body,。。。直到目標(biāo)元素。
找到目標(biāo)元素后,調(diào)用執(zhí)行他綁定事件時(shí)對(duì)應(yīng)的處理函數(shù),這個(gè)過(guò)程被稱之為目標(biāo)階段。
當(dāng)目標(biāo)元素的事件執(zhí)行結(jié)束后,再?gòu)哪繕?biāo)元素,到他的父元素。。。body、html再到document的過(guò)程,是冒泡階段。
42、模塊化語(yǔ)法 ? commonJS AMD CMD ES6 Module
commonJS是nodejs自帶的一種模塊化語(yǔ)法,將一個(gè)文件看做是一個(gè)模塊,可以將文件中導(dǎo)出的時(shí)
候,被另一個(gè)文件導(dǎo)入使用。導(dǎo)出使用: module.exports 導(dǎo)出。導(dǎo)入使用: require 函數(shù)導(dǎo)
入。
AMD是社區(qū)開(kāi)發(fā)的模塊化語(yǔ)法,需要依賴 require.js 實(shí)現(xiàn),分為定義模塊,導(dǎo)出數(shù)據(jù)和導(dǎo)入模
塊,使用數(shù)據(jù)。AMD語(yǔ)法的導(dǎo)入是依賴前置的,也就是說(shuō),需要用到的文件需要在第一次打開(kāi)頁(yè)面
全部加載完成,造成的后果就是首屏加載很慢,后續(xù)操作會(huì)很流暢。
CMD是玉伯開(kāi)發(fā)的模塊化語(yǔ)法,需要依賴 sea.js 實(shí)現(xiàn),也分為模塊定義導(dǎo)出,和模塊導(dǎo)入使用數(shù)
據(jù)。CMD語(yǔ)法可以依賴前置,也可以按需導(dǎo)入,緩解了AMD語(yǔ)法的依賴前置。
ES6的模塊化語(yǔ)法,類似于commonJS的語(yǔ)法,分為數(shù)據(jù)導(dǎo)出和數(shù)據(jù)導(dǎo)入,導(dǎo)入導(dǎo)出更加靈活。
43、 什么是懶加載和預(yù)加載 ?
懶加載
懶加載也叫延遲加載,延遲加載網(wǎng)絡(luò)資源或符合某些條件時(shí)才加載資源。常見(jiàn)的就是圖片延時(shí)加載。
懶加載的意義:懶加載的主要目的是作為服務(wù)器前端的優(yōu)化,減少請(qǐng)求數(shù)或延遲請(qǐng)求數(shù)。
懶惰實(shí)現(xiàn)方式:
1.第一種是純粹的延遲加載,使用setTimeOut或setInterval進(jìn)行加載延遲.
2.第二種是條件加載,符合某些條件,或觸發(fā)了某些事件才開(kāi)始異步下載。
3.第三種是可視區(qū)加載,即僅加載用戶可以看到的區(qū)域,這個(gè)主要由監(jiān)控滾動(dòng)條來(lái)實(shí)現(xiàn),一般會(huì)在距用戶看到某圖片前一定距離遍開(kāi)始加載,這樣能保證用戶拉下時(shí)正好能看到圖片。
預(yù)加載
提前加載圖片,當(dāng)用戶需要查看時(shí)可直接從本地緩存中渲染。
兩者的行為是相反的,一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對(duì)服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。預(yù)加載應(yīng)用如廣告彈窗等。
44、token 一般存放在哪里 ? 為什么不存放在 cookie 內(nèi) ?
token一般放在本地存儲(chǔ)中。token的存在本身只關(guān)心請(qǐng)求的安全性,而不關(guān)心token本身的安全,因?yàn)閠oken是服務(wù)器端生成的,可以理解為一種加密技術(shù)。但如果存在cookie內(nèi)的話,瀏覽器的請(qǐng)求默認(rèn)會(huì)自動(dòng)在請(qǐng)求頭中攜帶cookie,所以容易受到csrf攻擊。
45、 less 和 sass 的區(qū)別 ?
編譯環(huán)境不一樣,sass是服務(wù)器端處理的,可以用Ruby、node-sass來(lái)編譯;less需要引入less.js來(lái)處理輸出,也可以使用工具在服務(wù)器端處理成css,也有在線編譯的。
變量定義符不一樣,less用的是@,而sass用$。
sass支持分支語(yǔ)句,less不支持
-
瀏覽器的同源策略機(jī)制 ?
同源策略
同源策略,又稱SOP,全稱Same Origin Policy,是瀏覽器最基本的安全功能。站在瀏覽器的較短看網(wǎng)頁(yè),如果網(wǎng)絡(luò)上的接口可以不受限制、無(wú)需授權(quán)隨意被人調(diào)用,那將是一個(gè)非常嚴(yán)重的混亂場(chǎng)景。瀏覽器為了安全有序,內(nèi)部實(shí)現(xiàn)了同源策略。
同源策略,指的是瀏覽器限制當(dāng)前網(wǎng)頁(yè)只能訪問(wèn)同源的接口資源。
所謂同源,指當(dāng)前頁(yè)面和請(qǐng)求的接口,兩方必須是同協(xié)議、且同域名、且同端口。只要有一個(gè)不相同,則會(huì)受到瀏覽器額約束,不允許請(qǐng)求。
但當(dāng)一個(gè)項(xiàng)目變的很大的時(shí)候,將所有內(nèi)容放在一個(gè)網(wǎng)站或一個(gè)服務(wù)器中會(huì)讓網(wǎng)站變的臃腫且性能低下,所以,在一些場(chǎng)景中,我們需要跨過(guò)同源策略,請(qǐng)求到不同源的接口資源,這種場(chǎng)景叫跨域。
跨域大致有3種方案:
jsonp
這種方式是利用瀏覽器不限制某些標(biāo)簽發(fā)送跨域請(qǐng)求,例如link、img、iframe、script。通常請(qǐng)求請(qǐng)求回來(lái)的資源要在js中進(jìn)行處理,所以jsonp跨域是利用script標(biāo)簽進(jìn)行發(fā)送,且這種請(qǐng)求方式只能是get請(qǐng)求。
cors
這種方式是讓接口資源方面進(jìn)行授權(quán),授權(quán)允許訪問(wèn)。在接口資源處添加響應(yīng)頭即可通過(guò)瀏覽器的同源策略,響應(yīng)頭具體的鍵值對(duì)如下:
{Access-Control-Allow-Origin: '*'}
proxy
這種方式屬于找外援的一種方式,瀏覽器只能限制當(dāng)前正在打開(kāi)的web頁(yè)面發(fā)送請(qǐng)求,但無(wú)法限制服務(wù)器端請(qǐng)求接口資源。所以我們可以將請(qǐng)求發(fā)送到自己服務(wù)器,然后自己服務(wù)器去請(qǐng)求目標(biāo)接口資源,最后自己服務(wù)器將接口資源返回給當(dāng)前頁(yè)面,類似于找外援代替自己請(qǐng)求目標(biāo)接口資源。
這種方式通常要對(duì)服務(wù)器進(jìn)行代理配置,需要對(duì)apache服務(wù)器、nginx服務(wù)器、nodejs服務(wù)器進(jìn)行配置。
45、 瀏覽器的緩存有哪些 ? 什么時(shí)候使用強(qiáng)制緩存 ? 什么時(shí)候使用協(xié)商緩存 ?
當(dāng)我們?cè)L問(wèn)同一個(gè)頁(yè)面時(shí),請(qǐng)求資源、數(shù)據(jù)都是需要一定的耗時(shí),如果可以將一些資源緩存下來(lái),那么從第二次訪問(wèn)開(kāi)始,就可以減少加載時(shí)間,提高用戶體驗(yàn),也能減輕服務(wù)器的壓力。
瀏覽器緩存分為強(qiáng)緩存和協(xié)商緩存,當(dāng)存在緩存時(shí),客戶端第一次向服務(wù)器請(qǐng)求數(shù)據(jù)時(shí),客戶端會(huì)緩存到內(nèi)存或者硬盤當(dāng)中,當(dāng)?shù)诙潍@取相同的資源,強(qiáng)緩存和協(xié)商緩存的應(yīng)對(duì)方式有所不同。
強(qiáng)緩存:當(dāng)客戶端第二次向服務(wù)器請(qǐng)求相同的資源時(shí),不會(huì)向服務(wù)器發(fā)送請(qǐng)求,而是直接從內(nèi)存/硬盤中間讀取。緩存由服務(wù)器的響應(yīng)頭里 cache-control 和 expires 兩個(gè)字段決定。
協(xié)商緩存:當(dāng)客戶端第二次向服務(wù)器請(qǐng)求相同的資源時(shí),先向服務(wù)器發(fā)送請(qǐng)求"詢問(wèn)"該請(qǐng)求的文件緩存在ben'd與服務(wù)器相比是否更改,如果更改,則更新文件,如果沒(méi)有就從內(nèi)存/硬盤中讀取。協(xié)商緩存由 last-modified 和 etag兩個(gè)字段決定。
46、 數(shù)組方法 forEach 和 map 的區(qū)別 ?
forEach和map都是循環(huán)遍歷數(shù)組中的每一項(xiàng)。forEach() 和 map() 里面每一次執(zhí)行匿名函數(shù)都支持3個(gè)參數(shù):數(shù)組中的當(dāng)前項(xiàng)item,當(dāng)前項(xiàng)的索引index,原始數(shù)組input。只能遍歷數(shù)組。
他們的區(qū)別是:forEach沒(méi)有返回值,但map中要有返回值,返回處理后的所有新元素組成的數(shù)組。
47、 什么是函數(shù)作用域 ? 什么是作用域鏈 ?
作用域就是在代碼執(zhí)行過(guò)程中,形成一個(gè)獨(dú)立的空間,讓空間內(nèi)的變量不會(huì)邪泄露在空間外,也讓獨(dú)立空間內(nèi)的變量函數(shù)在獨(dú)立空間內(nèi)運(yùn)行,而不會(huì)影響到外部的環(huán)境。
作用域分為全局作用域和局部作用域,也就是本來(lái)有一個(gè)巨大的空間,空間內(nèi)定義的函數(shù)內(nèi)部,就形成了一個(gè)獨(dú)立的小空間,全局作用域是最大的作用域。
但是當(dāng)獨(dú)立空間內(nèi)的數(shù)據(jù)不能滿足需求時(shí),是可以從外部獲取數(shù)據(jù)的,也就是說(shuō)這樣的獨(dú)立空間之間是可以有層級(jí)關(guān)系的,外部的空間不可以從內(nèi)部的空間獲取數(shù)據(jù),但內(nèi)部的空間可以。當(dāng)子級(jí)空間在父級(jí)空間中獲取數(shù)據(jù)的時(shí),父級(jí)空間沒(méi)有的話,父級(jí)空間也會(huì)到他的父級(jí)空間中查找數(shù)據(jù),這樣形成的鏈?zhǔn)浇Y(jié)構(gòu)叫作用域鏈。
當(dāng)將一個(gè)變量當(dāng)做值使用時(shí),會(huì)先在當(dāng)前作用域中查找這個(gè)變量的定義和數(shù)據(jù),如果沒(méi)有定義的話,就會(huì)去父級(jí)作用域中查找,如果父級(jí)作用域中有的話就使用這個(gè)值,如果父級(jí)作用域中也沒(méi)有的話,就通過(guò)父級(jí)作用域查找他的父級(jí)作用域,直到找到最大的作用域-全局,如果全局也沒(méi)有就報(bào)錯(cuò)。
當(dāng)將一個(gè)變量當(dāng)做數(shù)據(jù)容器存儲(chǔ),也就是給變量賦值的時(shí)候,也要先在自己作用域中查找變量的定義,如果沒(méi)有就在上一級(jí)作用域中查找,直到全局,如果全局作用域中也沒(méi)有這個(gè)變量的定義,就在全局定義這個(gè)變量并賦值。
48、 ES6 中 Set 和 Map 的原理 ?
Set 是無(wú)重復(fù)值的有序列表。根據(jù) `Object.is()`方法來(lái)判斷其中的值不相等,以保證無(wú)重復(fù)。 Set 會(huì)自動(dòng)移除重復(fù)的值,因此你可以使用它來(lái)過(guò)濾數(shù)組中的重復(fù)值并返回結(jié)果。 Set并不是數(shù)組的子類型,所以你無(wú)法隨機(jī)訪問(wèn)其中的值。但你可以使用`has()` 方法來(lái)判斷某個(gè)值是否存在于 Set 中,或通過(guò) `size` 屬性來(lái)查看其中有多少個(gè)值。 Set 類型還擁有`forEach()`方法,用于處理每個(gè)值。
Map 是有序的鍵值對(duì),其中的鍵允許是任何類型。與 Set 相似,通過(guò)調(diào)用 `Object.is()`方法來(lái)判斷重復(fù)的鍵,這意味著能將數(shù)值 5 與字符串 "5" 作為兩個(gè)相對(duì)獨(dú)立的鍵。使用`set()` 方法能將任何類型的值關(guān)聯(lián)到某個(gè)鍵上,并且該值此后能用 `get()` 方法提取出來(lái)。Map 也擁有一個(gè) `size` 屬性與一個(gè) `forEach()` 方法,讓項(xiàng)目訪問(wèn)更容易。
49、 0.1 + 0.2 為什么不等于 0.3, 在項(xiàng)目中遇到要怎么處理 ?
計(jì)算機(jī)內(nèi)部存儲(chǔ)數(shù)據(jù)使用2進(jìn)制存儲(chǔ),兩個(gè)數(shù)字進(jìn)行的數(shù)學(xué)運(yùn)算,首先是將這兩個(gè)數(shù)字以2進(jìn)制形式,存儲(chǔ)在計(jì)算機(jī)內(nèi)部,然后在計(jì)算機(jī)內(nèi)部使用兩個(gè)2進(jìn)制數(shù)字進(jìn)行計(jì)算,最后將計(jì)算結(jié)果的2進(jìn)制數(shù)字轉(zhuǎn)為10進(jìn)制展示出來(lái)。
由于10進(jìn)制的小數(shù)在轉(zhuǎn)2進(jìn)制的時(shí)候,規(guī)則是小數(shù)部分乘以2,判斷是否得到一個(gè)整數(shù),如果得到整數(shù),轉(zhuǎn)換完成;如果沒(méi)有得到整數(shù),則繼續(xù)乘以2判斷。所以,0.1和0.2在轉(zhuǎn)換2進(jìn)制的時(shí)候,其實(shí)是一個(gè)無(wú)限死循環(huán),也就是一直乘以2沒(méi)有得到整數(shù)的時(shí)候,但計(jì)算機(jī)內(nèi)部對(duì)于無(wú)線死循環(huán)的數(shù)據(jù),會(huì)根據(jù)一個(gè)標(biāo)準(zhǔn)保留52位。也就是說(shuō),計(jì)算機(jī)內(nèi)部在存儲(chǔ)0.1和0.2的時(shí)候,本來(lái)就不精準(zhǔn),兩個(gè)不精準(zhǔn)的小數(shù)在計(jì)算后,距離精準(zhǔn)的結(jié)果是有一定誤差的。
項(xiàng)目中碰到這種情況,有3種處理方法:
-
將小數(shù)乘以10的倍數(shù),轉(zhuǎn)為整數(shù),然后計(jì)算,計(jì)算完成后,再縮小10的倍數(shù),例如:
var result = ((0.1 * 10) + (0.2 * 10)) / 10
// result === 0.3
-
使用數(shù)字的toFixed方法,強(qiáng)制保留小數(shù)點(diǎn)后多少位,例:
var result = (0.1 + 0.2).toFixed(2)
// result === 0.30
-
自定義數(shù)字運(yùn)算方法,當(dāng)需要進(jìn)行數(shù)學(xué)運(yùn)算的時(shí)候,不直接進(jìn)行,調(diào)用自定義的方法進(jìn)行,例:(加法封裝)
function add(...args){
var num = args.find(item => {
if(item != 0 && !item){
throw new Error("數(shù)學(xué)運(yùn)算要使用數(shù)字")
}
})
var arr = args.map(item => {
var index = (item+'').indexOf('.')
if(index >= 0){
return (item+'').split('.')[1].length
}
})
arr = arr.filter(item => item)
if(arr.length){
var max = Math.max(...arr)
var data = args.map(item => item * Math.pow(10, max))
var data.reduce((a, b) => a + b) / Math.pow(10, max)
}else{
var data = args
return data.reduce((a, b) => a + b)
}
}
// 調(diào)用使用:
var num1 = add(0.1, 0.2)
console.log(num1); // 0.3
var num2 = add(1, 2)
console.log(num2); // 3
var num3 = add(1, 2.1)
console.log(num3); // 3.1
50、 什么是模塊化思想 ?
就是JS中將不同功能的代碼封裝在不同的文件中, 再互相引用時(shí)不會(huì)發(fā)生命名沖突的一種思想, 大多數(shù)情況下, 一個(gè)文件就是一個(gè)模塊。
模塊化的實(shí)現(xiàn),有多種方案:
CommonJS
CommonJS是nodejs中使用的模塊化規(guī)范
在 nodejs 應(yīng)用中每個(gè)文件就是一個(gè)模塊,擁有自己的作用域,文件中的變量、函數(shù)都是私有的,與其他文件相隔離。模塊導(dǎo)出:module.exports=數(shù)據(jù),模塊導(dǎo)入:require('模塊文件路徑')
ES6的模塊化
模塊功能主要由兩個(gè)命令構(gòu)成:export和import。export命令用于規(guī)定模塊的對(duì)外接口,import命令用于輸入其他模塊提供的功能。
一個(gè)模塊就是一個(gè)獨(dú)立的文件。該文件內(nèi)部的所有變量,外部無(wú)法獲取。如果你希望外部能夠讀取模塊內(nèi)部的某個(gè)變量,就必須使用export關(guān)鍵字輸出該變量。下面是一個(gè) JS 文件,里面使用export命令輸出變量。
AMD (Asynchronous Module Definition)
特點(diǎn): 提倡依賴前置,在定義模塊的時(shí)候就要聲明其依賴的模塊:導(dǎo)入模塊require([module],callback);定義模塊:define('模塊名稱', 函數(shù))。
CMD (Common Module Definition)
CMD規(guī)范是國(guó)內(nèi)SeaJS的推廣過(guò)程中產(chǎn)生的。提倡就近依賴(按需加載),在用到某個(gè)模塊的時(shí)候再去require。定義模塊:define(function (require, exports, module) {}),使用模塊:seajs.use()
51、 說(shuō)說(shuō)怎么用js 寫無(wú)縫輪播圖
將所有需要輪播的內(nèi)容動(dòng)態(tài)復(fù)制一份,放在原本的容器中,加定時(shí)器讓整個(gè)容器中的內(nèi)容滾動(dòng)輪播,當(dāng)內(nèi)容輪播到left值為-原本的內(nèi)容寬度時(shí),快速將內(nèi)容切換到left值為0的狀態(tài)。
52、 JS 如何實(shí)現(xiàn)多線程 ?
我們都知道JS是一種單線程語(yǔ)言,即使是一些異步的事件也是在JS的主線程上運(yùn)行的(具體是怎么運(yùn)行的,可以看我另一篇博客JS代碼運(yùn)行機(jī)制)。像setTimeout、ajax的異步請(qǐng)求,或者是dom元素的一些事件,都是在JS主線程執(zhí)行的,這些操作并沒(méi)有在瀏覽器中開(kāi)辟新的線程去執(zhí)行,而是當(dāng)這些異步操作被操作時(shí)或者是被觸發(fā)時(shí)才進(jìn)入事件隊(duì)列,然后在JS主線程中開(kāi)始運(yùn)行。
首先說(shuō)一下瀏覽器的線程,瀏覽器中主要的線程包括,UI渲染線程,JS主線程,GUI事件觸發(fā)線程,http請(qǐng)求線程。
JS作為腳本語(yǔ)言,它的主要用途是與用戶互動(dòng),以及操作DOM。這決定了它只能是單線程,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。(這里這些問(wèn)題我們不做研究)
但是單線程的語(yǔ)言,有一個(gè)很致命的確定。如果說(shuō)一個(gè)腳本語(yǔ)言在執(zhí)行時(shí),其中某一塊的功能在執(zhí)行時(shí)耗費(fèi)了大量的時(shí)間,那么就會(huì)造成阻塞。這樣的項(xiàng)目,用戶體驗(yàn)是非常差的,所以這種現(xiàn)象在項(xiàng)目的開(kāi)發(fā)過(guò)程中是不允許存在的。
其實(shí)JS為我們提供了一個(gè)Worker的類,它的作用就是為了解決這種阻塞的現(xiàn)象。當(dāng)我們使用這個(gè)類的時(shí)候,它就會(huì)向?yàn)g覽器申請(qǐng)一個(gè)新的線程。這個(gè)線程就用來(lái)單獨(dú)執(zhí)行一個(gè)js文件。
var worker = new Worker(js文件路徑);
那么這個(gè)語(yǔ)句就會(huì)申請(qǐng)一個(gè)線程用來(lái)執(zhí)行這個(gè)js文件。這樣也就實(shí)現(xiàn)了js的多線程。
53、 閉包的使用場(chǎng)景 ?
一個(gè)函數(shù)被當(dāng)作值返回時(shí),也就相當(dāng)于返回了一個(gè)通道,這個(gè)通道可以訪問(wèn)這個(gè)函數(shù)詞法作用域中的變量,即函數(shù)所需要的數(shù)據(jù)結(jié)構(gòu)保存了下來(lái),數(shù)據(jù)結(jié)構(gòu)中的值在外層函數(shù)執(zhí)行時(shí)創(chuàng)建,外層函數(shù)執(zhí)行完畢時(shí)理因銷毀,但由于內(nèi)部函數(shù)作為值返回出去,這些值得以保存下來(lái)。而且無(wú)法直接訪問(wèn),必須通過(guò)返回的函數(shù)。這也就是私有性。
本來(lái)執(zhí)行過(guò)程和詞法作用域是封閉的,這種返回的函數(shù)就好比是一個(gè)蟲(chóng)洞,開(kāi)了掛。
閉包的形成很簡(jiǎn)單,在執(zhí)行過(guò)程完畢后,返回函數(shù),或者將函數(shù)得以保留下來(lái),即形成閉包。
防抖
function debounce(fn, interval) {
let timer = null; // 定時(shí)器
return function() {
// 清除上一次的定時(shí)器
clearTimeout(timer);
// 拿到當(dāng)前的函數(shù)作用域
let _this = this;
// 拿到當(dāng)前函數(shù)的參數(shù)數(shù)組
let args = Array.prototype.slice.call(arguments, 0);
// 開(kāi)啟倒計(jì)時(shí)定時(shí)器
timer = setTimeout(function() {
// 通過(guò)apply傳遞當(dāng)前函數(shù)this,以及參數(shù)
fn.apply(_this, args);
// 默認(rèn)300ms執(zhí)行
}, interval || 300)
}
}
節(jié)流
function throttle(fn, interval) {
let timer = null; // 定時(shí)器
let firstTime = true; // 判斷是否是第一次執(zhí)行
// 利用閉包
return function() {
// 拿到函數(shù)的參數(shù)數(shù)組
let args = Array.prototype.slice.call(arguments, 0);
// 拿到當(dāng)前的函數(shù)作用域
let _this = this;
// 如果是第一次執(zhí)行的話,需要立即執(zhí)行該函數(shù)
if(firstTime) {
// 通過(guò)apply,綁定當(dāng)前函數(shù)的作用域以及傳遞參數(shù)
fn.apply(_this, args);
// 修改標(biāo)識(shí)為null,釋放內(nèi)存
firstTime = null;
}
// 如果當(dāng)前有正在等待執(zhí)行的函數(shù)則直接返回
if(timer) return;
// 開(kāi)啟一個(gè)倒計(jì)時(shí)定時(shí)器
timer = setTimeout(function() {
// 通過(guò)apply,綁定當(dāng)前函數(shù)的作用域以及傳遞參數(shù)
fn.apply(_this, args);
// 清除之前的定時(shí)器
timer = null;
// 默認(rèn)300ms執(zhí)行一次
}, interval || 300)
}
}
迭代器
var arr =['aa','bb','cc'];
function incre(arr){
var i=0;
return function(){
//這個(gè)函數(shù)每次被執(zhí)行都返回?cái)?shù)組arr中 i下標(biāo)對(duì)應(yīng)的元素
return arr[i++] || '數(shù)組值已經(jīng)遍歷完';
}
}
var next = incre(arr);
console.log(next());//aa
console.log(next());//bb
console.log(next());//cc
console.log(next());//數(shù)組值已經(jīng)遍歷完
緩存
var fn=(function(){
var cache={};//緩存對(duì)象
var calc=function(arr){//計(jì)算函數(shù)
var sum=0;
//求和
for(var i=0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
return function(){
var args = Array.prototype.slice.call(arguments,0);//arguments轉(zhuǎn)換成數(shù)組
var key=args.join(",");//將args用逗號(hào)連接成字符串
var result , tSum = cache[key];
if(tSum){//如果緩存有
console.log('從緩存中取:',cache)//打印方便查看
result = tSum;
}else{
//重新計(jì)算,并存入緩存同時(shí)賦值給result
result = cache[key]=calc(args);
console.log('存入緩存:',cache)//打印方便查看
}
return result;
}
})();
fn(1,2,3,4,5);
fn(1,2,3,4,5);
fn(1,2,3,4,5,6);
fn(1,2,3,4,5,8);
fn(1,2,3,4,5,6);
getter和setter
function fn(){
var name='hello'
setName=function(n){
name = n;
}
getName=function(){
return name;
}
//將setName,getName作為對(duì)象的屬性返回
return {
setName:setName,
getName:getName
}
}
var fn1 = fn();//返回對(duì)象,屬性setName和getName是兩個(gè)函數(shù)
console.log(fn1.getName());//getter
fn1.setName('world');//setter修改閉包里面的name
console.log(fn1.getName());//getter
柯里化
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
循環(huán)中綁定事件或執(zhí)行異步代碼
var p1 = "ss";
var p2 = "jj";
function testSetTime(para1,para2){
return (function(){
console.log(para1 + "-" + para2);
})
}
var test = testSetTime(p1, p2);
setTimeout(test, 1000);
setTimeout(function(){
console.log(p1 + "-" + p2)
},1000)
單例模式
var Singleton = (function () {
var instance;
function createInstance() {
return new Object("I am the instance");
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
54、 常見(jiàn)的兼容問(wèn)題有哪些 ?
獲取標(biāo)簽節(jié)點(diǎn)
document.getElementsByClassName(‘類名’)在低版本ie中不兼容。解決方法是使用其他方式獲?。?/p>
document.getElementById('id名')
document.getElementsByTagName('標(biāo)簽名')
document.getElementsByName('name屬性值')
document.querySelector('css選擇器')
document.querySelectorAll('css選擇器')
獲取卷去的高度
// 當(dāng)有文檔聲明的時(shí)候
document.documentElement.scrollTop
document.documentElement.srollLeft
// 沒(méi)有文檔聲明的時(shí)候
document.body.scrollTop
document.body.scrollLeft
解決辦法使用兼容寫法
// 獲取
var t = document.documentElement.scrollTop || document.body.scrollTop
var l = document.documentElement.srollLeft || document.body.scrollLeft
// 設(shè)置
document.documentElement.scrollTop = document.body.scrollTop = 數(shù)值
document.documentElement.srollLeft = document.body.scrollLeft = 數(shù)值
獲取樣式
// W3C標(biāo)準(zhǔn)瀏覽器
window.getComputedStyle(元素)
// 低版本IE中
元素.currentStyle
使用函數(shù)封裝的方式兼容
function getStyle(ele,attr){
if(window.getComputedStyle){
return getComputedStyle(ele)[attr]
}else{
return ele.currentStyle[attr]
}
}
事件偵聽(tīng)器
// W3C瀏覽器
ele.addEventListener(事件類型,函數(shù))
// 低版本Ie
ele.attachEvent('on事件類型',函數(shù))
使用函數(shù)封裝的方式解決
function bindEvent(ele,type,handler){
if(ele.addEventListener){
ele.addEventListener(type,handler)
}else if(ele.attachEvent){
ele.attachEvent('on'+type,handler)
}else{
ele['on'+type] = handler
}
}
事件解綁
// W3C瀏覽器
ele.removeEventListener(事件類型,函數(shù))
// 低版本Ie
ele.detachEvent('on事件類型',函數(shù))
使用函數(shù)封裝的方式解決
function unBind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener(type,handler)
}else if(ele.detachEvent){
ele.detachEvent('on'+type,handler)
}else{
ele['on'+type] = null
}
}
事件對(duì)象的獲取
// W3C瀏覽器
元素.on事件類型 = function(e){}
元素.addEventListener(事件類型,fn)
function fn(e){
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event }
元素.addEventListener(事件類型,fn)
function fn(){
window.event
}
使用短路運(yùn)算符解決
元素.on事件類型 = function(e){
var e = e || window.event
}
元素.addEventListener(事件類型,fn)
function fn(e){
var e = e || window.event
}
阻止默認(rèn)行為
// W3C瀏覽器
元素.on事件類型 = function(e){
e.preventDefault()
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.returnValue = false }
通過(guò)封裝函數(shù)解決
元素.on事件類型 = function(e){
var e = e || window.event
e.preventDefault?e.preventDefault():e.returnValue=false
}
阻止事件冒泡
// W3C瀏覽器
元素.on事件類型 = function(e){
e.stopPropagation()
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.cancelBubble = true }
通過(guò)函數(shù)封裝解決
元素.on事件類型 = function(e){
var e = e || window.event
e.stopPropagation?e.stopPropagation():e.cancelBubble=true
}
獲取精準(zhǔn)的目標(biāo)元素
// W3C瀏覽器
元素.on事件類型 = function(e){
e.target
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.srcElement }
通過(guò)短路運(yùn)算符解決
元素.on事件類型 = function(e){
var e = e || window.event
var target = e.target || e.srcElement;
}
獲取鍵盤碼
// W3C瀏覽器
元素.on事件類型 = function(e){
e.keyCode
}
// 在低版本火狐中
元素.on事件類型 = function(e){
e.which
}
通過(guò)短路運(yùn)算符解決
元素.on事件類型 = function(e){
var e = e || window.event
var keycode = e.keyCode || e.which;
}
55、 在 JS 中如何阻止事件冒泡 ?
使用事件對(duì)象阻止事件冒泡,以前的w3c瀏覽器中,使用事件對(duì)象的方法阻止:
事件對(duì)象.stopPropagation()
在ie低版本瀏覽器中,使用事件對(duì)象的屬性阻止:
事件對(duì)象.cancelBubble = true
現(xiàn)在的w3c瀏覽器也支持ie低版本瀏覽器中的寫法,所以以前在阻止事件冒泡的時(shí)候,需要考慮兼容寫法,現(xiàn)在就不需要了,直接用ie低版本瀏覽器中的寫法即可。
56、兩個(gè)數(shù)組 var A = [1, 5, 6]; var B = [2, 6, 7],實(shí)現(xiàn)一個(gè)方法,找出僅存在于A 或者 僅 存在于B中的所有數(shù)字。
function getDiff(arr, brr){
// 僅存在于arr中的內(nèi)容
var onlyArr = arr.filter(item => !brr.some(v => item === v))
// 僅存在于brr中的內(nèi)容
var onlyBrr = brr.filter(v => !arr.some(item => v === item))
// 需要哪個(gè)就返回哪個(gè),或者一起返回
return {
"僅存在于arr中的內(nèi)容": onlyArr,
"僅存在于brr中的內(nèi)容": onlyBrr
}
}
57、 你了解構(gòu)造函數(shù)嗎 ? class 是什么 ? 兩者有什么區(qū)別 ?
在es5中構(gòu)造函數(shù)其實(shí)就是在定義一個(gè)類,可以實(shí)例化對(duì)象,es6中class其實(shí)是構(gòu)造函數(shù)的語(yǔ)法糖。但還是有點(diǎn)區(qū)別的:
在class內(nèi)部和class的方法內(nèi)部,默認(rèn)使用嚴(yán)格模式
class類不存在預(yù)解析,也就是不能先調(diào)用class生成實(shí)例,再定義class類,但是構(gòu)造函數(shù)可以。
class中定義的方法默認(rèn)不能被枚舉,也就是不能被遍歷。
class必須使用new執(zhí)行,但是構(gòu)造函數(shù)沒(méi)有new也可以執(zhí)行。
class中的所有方法都沒(méi)有原型,也就不能被new
class中繼承可以繼承靜態(tài)方法,但是構(gòu)造函數(shù)的繼承不能。
58、是否存在a的值(a===0&&a===1)為true 的情況 ?
var value = -1
Object.defineProperty(window,'a',{
get(){
return value+=1;
}
})
if(a===0&&a===1){ // true
console.log('success')
}
59、for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); } 要求:輸出0,1,2,3,4
首先這個(gè)面試題考察的是對(duì)于js中異步代碼以及作用域的理解:
js中常見(jiàn)的異步代碼包括定時(shí)器和ajax。js執(zhí)行代碼的流程是碰到同步代碼就執(zhí)行,碰到異步就交給瀏覽器的webAPI處理,當(dāng)webAPI中的異步該執(zhí)行時(shí),webAPI會(huì)將需要執(zhí)行的回調(diào)函數(shù)放在任務(wù)隊(duì)列中,等候執(zhí)行,所以,js中所有的異步代碼總會(huì)在所有同步代碼執(zhí)行結(jié)束后,再執(zhí)行任務(wù)隊(duì)列中的代碼。
在這個(gè)問(wèn)題中,循環(huán)是同步代碼,定時(shí)器是異步代碼,所以整個(gè)循環(huán)都執(zhí)行結(jié)束以后才會(huì)執(zhí)行定時(shí)器代碼。
for循環(huán)中使用var定義的變量是全局變量,定時(shí)器回調(diào)函數(shù)中輸出變量的時(shí)候,根據(jù)作用域規(guī)則,先在當(dāng)前作用域中變量i的定義表達(dá)式,如果沒(méi)有找到,就去上一級(jí)作用域中找,此時(shí),在局部作用域中沒(méi)有找到,去上級(jí)作用域中,也就是全局找到了,全局中的i,因?yàn)檠h(huán)已經(jīng)執(zhí)行結(jié)束了,所以i的值是5。
最終,會(huì)輸出5個(gè)5。
其次考察的是對(duì)于類似問(wèn)題的解決方式,間接性判斷你是否有過(guò)類似情況的開(kāi)發(fā):
這個(gè)問(wèn)題的解決思路就是讓回調(diào)函數(shù)中輸出i的時(shí)候,不要去全局中找i,因?yàn)槿值膇在循環(huán)執(zhí)行結(jié)束后已經(jīng)變成5了,根據(jù)這個(gè)思路,有2種解決辦法:
在異步代碼外面嵌套一層函數(shù)作用域
for(var i = 0;i < 5; i++){
(function(i) {
setTimeout(function() {
console.log(i)
}, 1000)
})(i)
}
原理是自調(diào)用函數(shù)會(huì)產(chǎn)生作用域,循環(huán)5次就會(huì)產(chǎn)生5個(gè)作用域,每個(gè)作用域代碼在執(zhí)行的時(shí)候都有形參i傳遞。所以每個(gè)作用域中的i都是不同的,分別是:0 1 2 3 4。當(dāng)作用域中的異步代碼執(zhí)行的時(shí)候,自己作用域中沒(méi)有i變量的定義,然后上級(jí)作用域就是自調(diào)用函數(shù)的作用域,找到了單獨(dú)的i。最終可以輸出:0 1 2 3 4
將循環(huán)代碼中的var換成es6的let
for(let i = 0;i < 5; i++){
setTimeout(function() {
console.log(i)
}, 1000)
}
es6的let自帶塊級(jí)作用域,原理跟第一種解決思路是一樣的,轉(zhuǎn)成es5后,代碼是一樣的。
60、實(shí)現(xiàn)一個(gè) add 方法 使計(jì)算結(jié)果能夠滿足如下預(yù)期: - add(1)(2)(3)() = 6 - add(1,2,3)(4)() = 10
function add (...args) {
if (args.length === 3) return -(args[0] * args[1] * 2 + args[2] * 2)
else return -args[args.length - 1]
}
function currying (fn) {
let args = []
return function _c (...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return _c
} else return fn.apply(this, args)
}
}
let addCurry = currying(add)
var a = addCurry(1)(2)(3)()
console.log(-a); // 10
var b = addCurry(1, 2, 3)(4)()
console.log(6 - b); // 10
61、常見(jiàn)的 HTTP 請(qǐng)求有哪些 ? 他們的區(qū)別是什么 ?
常見(jiàn)的有5種,分別是GET、HEAD, POST、PUT、 DELETE
GET:它是最常見(jiàn)的方法,用于獲取資源,常用于向服務(wù)器查詢某些信息。打開(kāi)網(wǎng)頁(yè)一般都是用GET方法,因?yàn)橐獜?Web 服務(wù)器獲取信息
HEAD:類似于 GET請(qǐng)求,只不過(guò)返回的響應(yīng)中沒(méi)有具體的內(nèi)容,用于獲取報(bào)頭。
POST:向指定資源提交數(shù)據(jù)進(jìn)行處理請(qǐng)求(例如提交表單或者上傳文件), 數(shù)據(jù)被包含在請(qǐng)求體中。POST請(qǐng)求可能會(huì)導(dǎo)致新的資源的建立和/或?qū)σ延匈Y源的修改。
PUT:從客戶端向服務(wù)器傳送的數(shù)據(jù)取代指定文檔的內(nèi)容。
DELETE:請(qǐng)求服務(wù)器刪除指定的頁(yè)面。
最常見(jiàn)的HTTP請(qǐng)求方法是GET 和 POST。GET一般用于獲取/查詢資源信息,而POST一般用于更新資源信息。GET和POST的區(qū)別:
GET提交的數(shù)據(jù)會(huì)放在?之后,以問(wèn)號(hào)(?)分割URL 和傳輸數(shù)據(jù),參數(shù)之間以&相連
GET提交的數(shù)據(jù)大小有限制(因?yàn)闉g覽器對(duì)URL的長(zhǎng)度有限制), 而POST 方法提交的數(shù)據(jù)大小沒(méi)有限制。
GET方式提交數(shù)據(jù)會(huì)帶來(lái)安全問(wèn)題,比如一個(gè)登錄頁(yè)面通過(guò)GET方式提交數(shù)據(jù)時(shí),用戶名和密碼將出現(xiàn)在URL上,如果頁(yè)面可以被緩存或者其他人可以訪問(wèn)這臺(tái)機(jī)器,就可以從歷史記錄獲得該用戶的賬號(hào)和密碼。
62、 JS 的數(shù)據(jù)類型有哪些 ? 如何判斷數(shù)據(jù)類型 ?他們的優(yōu)缺點(diǎn)是什么?
基本數(shù)據(jù)類型
Number、String、Boolean、Null、Undefined、Symbol、BigInt
引用數(shù)據(jù)類型
Object、Array、Date、Function、RegExp
typeof
用來(lái)檢測(cè)數(shù)據(jù)類型的運(yùn)算符
檢測(cè)的不管是數(shù)組還是正則都返回的是"object",所以typeof不能判斷一個(gè)值是否為數(shù)組
console.log(typeof([1,2,3])); //object
console.log(typeof({name:"張三",age:12})); //object
console.log(typeof(null)); //object
instanceof/constructor
檢測(cè)某一個(gè)實(shí)例是否屬于某一個(gè)類使用instanceof/constructor可以檢測(cè)數(shù)組和正則
用instanceof檢測(cè)的時(shí)候,只要當(dāng)前的這個(gè)類在實(shí)例的原型鏈上(可以通過(guò)原型鏈__proto__找到它),檢測(cè)出來(lái)的結(jié)果都是true。
基本數(shù)據(jù)類型的值是不能用instanceof來(lái)檢測(cè)的
在類的原型繼承中,instanceof檢測(cè)出來(lái)的結(jié)果其實(shí)是不準(zhǔn)確的
console.log([typeof null, null instanceof Object]) //["object", false]
Object.prototype.toString.call(value)
找到Object原型上的toString方法,讓方法執(zhí)行,并且讓方法中的this變?yōu)関alue(value->就是我們要檢測(cè)數(shù)據(jù)類型的值)。檢測(cè)的類型比較多,也比較精準(zhǔn)。
console.log(Object.prototype.toString.call(123));//[object Number]
console.log(Object.prototype.toString.call('abc'));//[object String]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call({}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call(/^abc$/));//[object RegExp]
console.log(Object.prototype.toString.call(new Date()));//[object Date]
63、 symbol 你是怎么理解的 ?
Symbol 是 ES6 新推出的一種基本類型,它表示獨(dú)一無(wú)二的值
它可以選擇接受一個(gè)字符串作為參數(shù)或者不傳,但是相同參數(shù)的兩個(gè)Symbol 值不相等
//不傳參數(shù)
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false
// 傳入?yún)?shù)
const s3 = Symbol('debug');
const s4 = Symbol('debug');
console.log(s3 === s4); // false
可以通過(guò)typeof判斷是否為Symbol類型
console.log(typeof s1); // symbol
Symbol.for():用于將描述相同的Symbol變量指向同一個(gè)Symbol值
let a1 = Symbol.for('a');
let a2 = Symbol.for('a');
a1 === a2 // true
typeof a1 // "symbol"
typeof a2 // "symbol"
let a3= Symbol("a");
a1 === a3 // false
Symbol.keyFor():用來(lái)檢測(cè)該字符串參數(shù)作為名稱的 Symbol 值是否已被登記,返回一個(gè)已登記的 Symbol 類型值的 key
let a1 = Symbol.for("a");
Symbol.keyFor(a1); // "a"
let a2 = Symbol("a");
Symbol.keyFor(a2); // undefined
description:用來(lái)返回Symbol數(shù)據(jù)的描述:
// Symbol()定義的數(shù)據(jù)
let a = Symbol("acc");
a.description // "acc"
Symbol.keyFor(a); // undefined
// Symbol.for()定義的數(shù)據(jù)
let a1 = Symbol.for("acc");
a1.description // "acc"
Symbol.keyFor(a1); // "acc"
// 未指定描述的數(shù)據(jù)
let a2 = Symbol();
a2.description // undefined
使用場(chǎng)景一:對(duì)象添加屬性
let n = Symbol('N');
let obj = {
name: "hello world",
age: 11,
[n]: 100
};
使用場(chǎng)景二:給對(duì)象添加私有屬性
const speak = Symbol();
class Person {
[speak]() {
console.log(123)
}
}
let person = new Person()
console.log(person[speak]())
64、數(shù)組常用方法有那些
數(shù)組的常用方法 這樣的面試題 算是非?;A(chǔ)的面試題 面試官的目的 也不會(huì)只是單純的讓你背誦出 數(shù)組的所有方法
這里的關(guān)鍵點(diǎn) 是 常用 這兩個(gè)字 面試官的 目的是 通過(guò) 這個(gè)問(wèn)題 看你平時(shí)在項(xiàng)目中 對(duì)于 數(shù)組函數(shù)的應(yīng)用和理解 然后判斷出 你平時(shí)在項(xiàng)目中對(duì)于數(shù)組的應(yīng)用 然后推測(cè)出你真實(shí)的技術(shù)水平
這里建議的回答方式是 通過(guò)一個(gè) 自己用的最多的數(shù)組函數(shù)方法 深入展開(kāi)的說(shuō)一說(shuō) 在 實(shí)際項(xiàng)目中的應(yīng)用
例如談到 數(shù)組單元?jiǎng)h除 數(shù)組,splice() 除了要說(shuō) 函數(shù)的用法之外 還要談到 具體的項(xiàng)目中 刪除數(shù)組單元之后 數(shù)組坍塌的影響 以及如何處理
concat() 連接兩個(gè)或更多的數(shù)組,并返回結(jié)果。
join() 把數(shù)組的所有元素放入一個(gè)字符串。元素通過(guò)指定的分隔符進(jìn)行分隔。
pop() 刪除并返回?cái)?shù)組的最后一個(gè)元素。
shift() 刪除并返回?cái)?shù)組的第一個(gè)元素
push() 向數(shù)組的末尾添加一個(gè)或更多元素,并返回新的長(zhǎng)度。
unshift() 向數(shù)組的開(kāi)頭添加一個(gè)或更多元素,并返回新的長(zhǎng)度。
reverse() 顛倒數(shù)組中元素的順序。
slice() 從某個(gè)已有的數(shù)組返回選定的元素
sort() 對(duì)數(shù)組的元素進(jìn)行排序
splice() 刪除元素,并向數(shù)組添加新元素。
toSource() 返回該對(duì)象的源代碼。
toString() 把數(shù)組轉(zhuǎn)換為字符串,并返回結(jié)果。
toLocaleString() 把數(shù)組轉(zhuǎn)換為本地?cái)?shù)組,并返回結(jié)果。
valueOf() 返回?cái)?shù)組對(duì)象的原始值
65、JavaScript如何存儲(chǔ)cookie
基本語(yǔ)法是 document.cookie = ‘鍵名=鍵值;expires=時(shí)間對(duì)象;path=路徑’ ;
時(shí)效 如果不設(shè)定 默認(rèn)是 seeion 會(huì)話時(shí)效
路徑 如果不設(shè)定 默認(rèn)是 當(dāng)前文件所在文件夾
設(shè)定時(shí)效 要 設(shè)定一個(gè)時(shí)間對(duì)象 時(shí)間對(duì)象的時(shí)間戳 就是 時(shí)效期
要注意計(jì)算 當(dāng)前時(shí)區(qū) 和 世界標(biāo)磚時(shí)間的時(shí)間差
路徑一般設(shè)定為根目錄 也就是 ‘/’
66、柯理化函數(shù)
所謂的柯里化函數(shù) 指的是 把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)的函數(shù) 并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)
// 普通的add函數(shù)
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
優(yōu)點(diǎn):
1, 參數(shù)復(fù)用
例如 一個(gè)函數(shù) 有兩個(gè)參數(shù) 但是第一個(gè)參數(shù)會(huì)被反復(fù)使用 每次都需要輸入 一個(gè)重復(fù)的參數(shù)
使用柯里化函數(shù)之后 只需要 輸入一個(gè)參數(shù)就可以了
2, 提前確認(rèn)
提前定義好一個(gè)參數(shù) 也就 決定了整個(gè)函數(shù)程序的執(zhí)行方向 避免每次都執(zhí)行判斷比較等
缺點(diǎn):
只能提前定義一個(gè)參數(shù) 如果想要提前定義多個(gè)參數(shù) 這樣的語(yǔ)法是不支持
柯里化函數(shù)執(zhí)行效能上的問(wèn)題:
存取arguments對(duì)象通常要比存取命名參數(shù)要慢一點(diǎn)
一些老版本的瀏覽器在arguments.length的實(shí)現(xiàn)上是相當(dāng)慢的
使用 函數(shù).apply() 和 函數(shù).call() 通常比直接調(diào)用 fn() 稍微慢點(diǎn)
創(chuàng)建大量嵌套作用域和閉包函數(shù)會(huì)帶來(lái)花銷,無(wú)論是在內(nèi)存還是速度上
67、對(duì)象遍歷方法
JavaScript中 對(duì)象的遍歷方法
for...in
基本語(yǔ)法是 for( 變量 in 對(duì)象 ){ 循環(huán)體程序 }
這里要注意的是
1, 變量中存儲(chǔ)的鍵名 通過(guò)鍵名獲取對(duì)象中存儲(chǔ)的鍵值
因?yàn)槭亲兞?點(diǎn)語(yǔ)法取值 不支持解析變量 要使用 對(duì)象[鍵名] 獲取鍵值
2, 循環(huán)變量 定義 let 和 var 定義 執(zhí)行效果是不同的
Object.keys( 對(duì)象 )
返回一個(gè)數(shù)組 是 當(dāng)前對(duì)象 所有鍵名組成的數(shù)組
之后再循環(huán)遍歷這個(gè)數(shù)組 再執(zhí)行操作
Object.value( 對(duì)象 )
返回一個(gè)數(shù)組 是 當(dāng)前對(duì)象 所有鍵值組成的數(shù)組
之后再循環(huán)遍歷這個(gè)數(shù)組 再執(zhí)行操作
68、數(shù)組扁平化
所謂的數(shù)組扁平化就是將多維數(shù)組轉(zhuǎn)化為一維數(shù)組一般數(shù)組扁平化,數(shù)組中存儲(chǔ)的多維數(shù)據(jù)都是數(shù)組 不會(huì)是對(duì)象或者函數(shù)
最常用的方法就是數(shù)組.toString() 將數(shù)組轉(zhuǎn)化為字符串
結(jié)果是獲取數(shù)組中的每一個(gè)單元的數(shù)據(jù)組成一個(gè)字符串使用逗號(hào)間隔
再以逗號(hào)為間隔 將字符串轉(zhuǎn)化為數(shù)組
function fun1( arr ){
let str = arr.toString();
return str.split(',');
}
還可以使用數(shù)組.some() 方法 判斷數(shù)組中是不是還存在數(shù)組
在使用展開(kāi)運(yùn)算符賦值
function fun1(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
另外 ES6 語(yǔ)法中 新增的 flat函數(shù)也可以實(shí)現(xiàn)數(shù)組的扁平化
const arr = 原始數(shù)組.flat( Infinity );
實(shí)現(xiàn)數(shù)組扁平化
69、typeof 原理
利用 typeof 是根據(jù)返回值的結(jié)果來(lái)判斷數(shù)據(jù)類型
具體返回值 一共是 number, string, object, boolean, function, undefined
其中 數(shù)組 null 對(duì)象 的返回值 都是 object
這樣的話具體的數(shù)據(jù)類型就不能區(qū)分的非常明確 在實(shí)際項(xiàng)目中 就不能準(zhǔn)確的區(qū)分
如果想要具體的 區(qū)分 數(shù)據(jù)類型 需要使用 Object.prototype.toString.call() 方法 返回值是
object String 字符串
object Number 數(shù)值類型
object Boolean 布爾類型
object Undefined undefined類型
object Null null類型
object Function 函數(shù)類型
object Array 數(shù)組類型
70、介紹類型轉(zhuǎn)化
JavaScript 因?yàn)槭?弱類型計(jì)算機(jī)語(yǔ)言 存儲(chǔ)數(shù)據(jù)時(shí) 對(duì)變量?jī)?chǔ)存的數(shù)據(jù)類型沒(méi)有設(shè)定
因此一個(gè)變量中可以存儲(chǔ)任意類型的數(shù)據(jù)
在程序的執(zhí)行過(guò)程中 就會(huì)遇到需要數(shù)據(jù)類型轉(zhuǎn)化的情況
比較的時(shí)候會(huì)進(jìn)行自動(dòng)類型轉(zhuǎn)換
參考這篇文章的開(kāi)頭
71、執(zhí)行上下文
執(zhí)行上下文:指當(dāng)前執(zhí)行環(huán)境中的變量、函數(shù)聲明,參數(shù)(arguments),作用域鏈,this等信息。分為全局執(zhí)行上下文、函數(shù)執(zhí)行上下文,其區(qū)別在于全局執(zhí)行上下文只有一個(gè),函數(shù)執(zhí)行上下文在每次調(diào)用函數(shù)時(shí)候會(huì)創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文。
變量對(duì)象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲(chǔ)了上下文中定義的變量和函數(shù)聲明。
變量對(duì)象式一個(gè)抽象的概念,在不同的上下文中,表示不同的對(duì)象:
全局執(zhí)行上下文的變量對(duì)象
全局執(zhí)行上下文中,變量對(duì)象就是全局對(duì)象。
在頂層js代碼中,this指向全局對(duì)象,全局變量會(huì)作為該對(duì)象的屬性來(lái)被查詢。在瀏覽器中,window就是全局對(duì)象。
函數(shù)執(zhí)行上下文的變量對(duì)象
函數(shù)上下文中,變量對(duì)象VO就是活動(dòng)對(duì)象AO。
初始化時(shí),帶有arguments屬性。
函數(shù)代碼分成兩個(gè)階段執(zhí)行
進(jìn)入執(zhí)行上下文時(shí),此時(shí)變量對(duì)象包括
形參
函數(shù)聲明,會(huì)替換已有變量對(duì)象
變量聲明,不會(huì)替換形參和函數(shù)
函數(shù)執(zhí)行
執(zhí)行上下文棧的作用是用來(lái)跟蹤代碼的,由于JS是單線程的,每次只能做一件事情,其他的事情會(huì)放在指定的上下文棧中排隊(duì)等待執(zhí)行。
JS解釋器在初始化代碼的時(shí)候,首先會(huì)創(chuàng)建一個(gè)新的全局執(zhí)行上下文到執(zhí)行上下文棧頂中,然后隨著每次函數(shù)的調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文放入到棧頂中,隨著函數(shù)執(zhí)行完畢后被執(zhí)行上下文棧頂彈出,直到回到全局的執(zhí)行上下文中。
首先創(chuàng)建了全局執(zhí)行上下文,當(dāng)前全局執(zhí)行上下文處于活躍狀態(tài)。
全局代碼中有2個(gè)函數(shù) getName 和 getYear,然后調(diào)用 getName 函數(shù),JS引擎停止執(zhí)行全局執(zhí)行上下文,創(chuàng)建了新的函數(shù)執(zhí)行上下文,且把該函數(shù)上下文放入執(zhí)行上下文棧頂。
getName 函數(shù)里又調(diào)用了 getYear 函數(shù),此時(shí)暫停了 getName 的執(zhí)行上下文,創(chuàng)建了 getYear 函數(shù)的新執(zhí)行上下文,且把該函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧頂。
當(dāng) getYear 函數(shù)執(zhí)行完后,其執(zhí)行上下文從棧頂出棧,回到了 getName 執(zhí)行上下文中繼續(xù)執(zhí)行。
當(dāng) getName 執(zhí)行完后,其執(zhí)行上下文從棧頂出棧,回到了全局執(zhí)行上下文中。
72、閉包的問(wèn)題和優(yōu)化
閉包:是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見(jiàn)方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù)。
作用:
1、可以讀取函數(shù)內(nèi)部的變量
2、相當(dāng)于劃出了一塊私有作用域,避免數(shù)據(jù)污染;
3、讓變量始終保存在內(nèi)存中
閉包有三個(gè)特性:
1.函數(shù)嵌套函數(shù)
2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量
3.參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收
閉包的問(wèn)題:
閉包會(huì)產(chǎn)生不銷毀的上下文,會(huì)導(dǎo)致棧/堆內(nèi)存消耗過(guò)大,有時(shí)候也會(huì)導(dǎo)致內(nèi)存泄漏等,影響頁(yè)面的運(yùn)行性能,所以在真實(shí)項(xiàng)目中,要合理應(yīng)用閉包!
閉包的優(yōu)化
原始代碼
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
優(yōu)化代碼
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
73、瀏覽器和Node事件循環(huán)的區(qū)別
一、全局環(huán)境下this的指向
在node中this指向global而在瀏覽器中this指向window,這就是為什么underscore中一上來(lái)就定義了一 root;
而且在瀏覽器中的window下封裝了不少的API 比如 alert 、document、location、history 等等還有很多。我門就不能在node環(huán)境中xxx();或window.xxx();了。因?yàn)檫@些API是瀏覽器級(jí)別的封裝,存javascript中是沒(méi)有的。當(dāng)然node中也提供了不少node特有的API。
二、js引擎
在瀏覽器中不同的瀏覽器廠商提供了不同的瀏覽器內(nèi)核,瀏覽器依賴這些內(nèi)核解釋折我們編寫的js。但是考慮到不同內(nèi)核的少量差異,我們需要對(duì)應(yīng)兼容性好在有一些優(yōu)秀的庫(kù)幫助我們處理這個(gè)問(wèn)題比如jquery、underscore等等。
nodejs是基于Chromes JavaScript runtime,也就是說(shuō),實(shí)際上它是對(duì)GoogleV8引擎(應(yīng)用于Google Chrome瀏覽器)進(jìn)行了封裝。V8引 擎執(zhí)行Javascript的速度非常快,性能非常好。
NodeJS并不是提供簡(jiǎn)單的封裝,然后提供API調(diào)用,如果是這樣的話那么它就不會(huì)有現(xiàn)在這么火了。Node對(duì)一些特殊用例進(jìn)行了優(yōu)化,提供了替代的API,使得V8在非瀏覽器環(huán)境下運(yùn)行得更好。例如,在服務(wù)器環(huán)境中,處理二進(jìn)制數(shù)據(jù)通常是必不可少的,但Javascript對(duì)此支持不足,因此,V8.Node增加了Buffer類,方便并且高效地 處理二進(jìn)制數(shù)據(jù)。因此,Node不僅僅簡(jiǎn)單的使用了V8,還對(duì)其進(jìn)行了優(yōu)化,使其在各環(huán)境下更加給力。
三、DOM操作
瀏覽器中的js大多數(shù)情況下是在直接或間接(一些虛擬DOM的庫(kù)和框架)的操作DOM。因?yàn)闉g覽器中的代碼主要是在表現(xiàn)層工作。但是node是一門服務(wù)端技術(shù)。沒(méi)有一個(gè)前臺(tái)頁(yè)面,所以我門不會(huì)再node中操作DOM。
四、I/O讀寫
與瀏覽器不同,我們需要像其他服務(wù)端技術(shù)一樣讀寫文件,nodejs提供了比較方便的組件。而瀏覽器(確保兼容性的)想在頁(yè)面中直接打開(kāi)一個(gè)本地的圖片就麻煩了好多(別和我說(shuō)這還不簡(jiǎn)單,相對(duì)路徑。。。。。。試試就知道了要么找個(gè)庫(kù)要么二進(jìn)制流,要么上傳上去有了網(wǎng)絡(luò)地址在顯示。不然人家為什么要搞一個(gè)js庫(kù)呢),而這一切node都用一個(gè)組件搞定了。
五、模塊加載
javascript有個(gè)特點(diǎn),就是原生沒(méi)提供包引用的API一次性把要加載的東西全執(zhí)行一遍,這里就要看各位閉包的功力了。所用東西都在一起,沒(méi)有分而治之,搞的特別沒(méi)有邏輯性和復(fù)用性。如果頁(yè)面簡(jiǎn)單或網(wǎng)站當(dāng)然我們可以通過(guò)一些AMD、CMD的js庫(kù)(比如requireJS 和 seaJS)搞定事實(shí)上很多大型網(wǎng)站都是這么干的。
在nodeJS中提供了CMD的模塊加載的API,如果你用過(guò)seaJS,那么應(yīng)該上手很快。
node還提供了npm 這種包管理工具,能更有效方便的管理我們飲用的庫(kù)。
74、移動(dòng)端點(diǎn)擊延遲
原因
為了確定用戶是要做單擊 還是雙擊 還是要做其他的操作 因此移動(dòng)端 當(dāng)你點(diǎn)擊時(shí) 會(huì)有 300毫秒延遲 為了等待判斷用戶的下一步操作是什么
解決方案1
禁用縮放
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
當(dāng)HTML文檔頭部包含以上meta標(biāo)簽時(shí) 表明這個(gè)頁(yè)面是不可縮放的,那雙擊縮放的功能就沒(méi)有意義了,此時(shí)瀏覽器可以禁用默認(rèn)的雙擊縮放行為并且去掉300ms的點(diǎn)擊延遲。
這個(gè)方案有一個(gè)缺點(diǎn),就是必須通過(guò)完全禁用縮放來(lái)達(dá)到去掉點(diǎn)擊延遲的目的,然而完全禁用縮放并不是我們的初衷,我們只是想禁掉默認(rèn)的雙擊縮放行為,這樣就不用等待300ms來(lái)判斷當(dāng)前操作是否是雙擊。但是通常情況下,我們還是希望頁(yè)面能通過(guò)雙指縮放來(lái)進(jìn)行縮放操作,比如放大一張圖片,放大一段很小的文字。
解決方案2 更改默認(rèn)的視口寬度
<meta name="viewport" content="width=device-width">
一開(kāi)始,為了讓桌面站點(diǎn)能在移動(dòng)端瀏覽器正常顯示,移動(dòng)端瀏覽器默認(rèn)的視口寬度并不等于設(shè)備瀏覽器視窗寬度,而是要比設(shè)備瀏覽器視窗寬度大,通常是980px。我們可以通過(guò)以下標(biāo)簽來(lái)設(shè)置視口寬度為設(shè)備寬度。因?yàn)殡p擊縮放主要是用來(lái)改善桌面站點(diǎn)在移動(dòng)端瀏覽體驗(yàn)的,而隨著響應(yīng)式設(shè)計(jì)的普及,很多站點(diǎn)都已經(jīng)對(duì)移動(dòng)端坐過(guò)適配和優(yōu)化了,這個(gè)時(shí)候就不需要雙擊縮放了,如果能夠識(shí)別出一個(gè)網(wǎng)站是響應(yīng)式的網(wǎng)站,那么移動(dòng)端瀏覽器就可以自動(dòng)禁掉默認(rèn)的雙擊縮放行為并且去掉300ms的點(diǎn)擊延遲。如果設(shè)置了上述meta標(biāo)簽,那瀏覽器就可以認(rèn)為該網(wǎng)站已經(jīng)對(duì)移動(dòng)端做過(guò)了適配和優(yōu)化,就無(wú)需雙擊縮放操作了。
這個(gè)方案相比方案一的好處在于,它沒(méi)有完全禁用縮放,而只是禁用了瀏覽器默認(rèn)的雙擊縮放行為,但用戶仍然可以通過(guò)雙指縮放操作來(lái)縮放頁(yè)面。
解決方案3 CSS touch-action
跟300ms點(diǎn)擊延遲相關(guān)的,是touch-action這個(gè)CSS屬性。這個(gè)屬性指定了相應(yīng)元素上能夠觸發(fā)的用戶代理(也就是瀏覽器)的默認(rèn)行為。如果將該屬性值設(shè)置為touch-action: none,那么表示在該元素上的操作不會(huì)觸發(fā)用戶代理的任何默認(rèn)行為,就無(wú)需進(jìn)行300ms的延遲判斷。
最后的最后 我們還可以使用一些 插件來(lái)解決這個(gè)問(wèn)題 例如 FastClick 是 FT Labs 專門為解決移動(dòng)端瀏覽器 300 毫秒點(diǎn)擊延遲問(wèn)題所開(kāi)發(fā)的一個(gè)輕量級(jí)的庫(kù)。FastClick的實(shí)現(xiàn)原理是在檢測(cè)到touchend事件的時(shí)候,會(huì)通過(guò)DOM自定義事件立即出發(fā)模擬一個(gè)click事件,并把瀏覽器在300ms之后的click事件阻止掉。
安裝 npm install fastclick -S
使用 如何你是vue項(xiàng)目可以在main.js里面直接引入,當(dāng)然這樣是全局的,如果你需要某個(gè)頁(yè)面用到,那就單個(gè)頁(yè)面引入。
//引入
import fastClick from 'fastclick'
//初始化FastClick實(shí)例。在頁(yè)面的DOM文檔加載完成后
fastClick.attach(document.body)
75、cookie的常見(jiàn)屬性
鍵名 cookie鍵值對(duì)的鍵名
鍵值 cookie鍵值對(duì)的鍵值
expirescookie的時(shí)效 分為 session會(huì)話時(shí)效 時(shí)間時(shí)效 時(shí)間時(shí)效是服務(wù)器時(shí)間也就是世界標(biāo)準(zhǔn)時(shí)間
path路徑 符合路徑的文件才能訪問(wèn)cookie
httponly設(shè)置 為 true 了之后可以防止js程序訪問(wèn) 防止 xss攻擊 增加cookie的安全性
secure設(shè)置 為 true 了之后cookie只能通過(guò)https協(xié)議發(fā)送 http協(xié)議是不能發(fā)送的 這樣也是為了增加cookie的安全性
76、反柯里化
反柯里化的作用是,當(dāng)我們調(diào)用某個(gè)方法,不用考慮這個(gè)對(duì)象在被設(shè)計(jì)時(shí),是否擁有這個(gè)方法,只要這個(gè)方法適用于它,我們就可以對(duì)這個(gè)對(duì)象使用它
例如:
Function.prototype.uncurring = function() {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
};
};
我們先來(lái)看看上面這段代碼有什么作用。
我們要把Array.prototype.push方法轉(zhuǎn)換成一個(gè)通用的push函數(shù),只需要這樣做:
var push = Array.prototype.push.uncurring();
//測(cè)試一下
(function() {
push(arguments, 4);
console.log(arguments); //[1, 2, 3, 4]
})(1, 2, 3)
arguments本來(lái)是沒(méi)有push方法的,通常,我們都需要用Array.prototype.push.call來(lái)實(shí)現(xiàn)push方法,但現(xiàn)在,直接調(diào)用push函數(shù),既簡(jiǎn)潔又意圖明了。
我們不用考慮對(duì)象是否擁有這個(gè)方法,只要它適用于這個(gè)方法,那就可以使用這個(gè)方法(類似于鴨子類型)。
我們來(lái)分析一下調(diào)用Array.prototype.push.uncurring()這句代碼時(shí),發(fā)生了什么事情:
Function.prototype.uncurring = function() {
var self = this; //self此時(shí)是Array.prototype.push
return function() {
var obj = Array.prototype.shift.call(arguments);
//obj 是{
// "length": 1,
// "0": 1
//}
//arguments的第一個(gè)對(duì)象被截去(也就是調(diào)用push方法的對(duì)象),剩下[2]
return self.apply(obj, arguments);
//相當(dāng)于Array.prototype.push.apply(obj, 2);
};
};
測(cè)試一下
var push = Array.prototype.push.uncurring();
var obj = {
"length": 1,
"0" : 1
};
push(obj, 2);
console.log( obj ); //{0: 1,1: 2, length: 2 }
看到這里你應(yīng)該對(duì)柯里化和反柯里化有了一個(gè)初步的認(rèn)識(shí)了,但要熟練的運(yùn)用在開(kāi)發(fā)中,還需要我們更深入的去了解它們內(nèi)在的含義。
77、千分位
這里的需求 本質(zhì)上是要 將 數(shù)字 轉(zhuǎn)化為 帶有千分位字符串 方法有很多
方法1 正則表達(dá)式
console.info( str.replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
return s+','
}) )
方法2 字符串替換
console.info( str.replace(/(\d{1,3})(?=(\d{3})+$)/g,function($1){
return $1=$1+','
}) )
方法3 數(shù)字轉(zhuǎn)數(shù)組 反轉(zhuǎn)后 添加 , 再反轉(zhuǎn)回來(lái)拼接為字符串
console.info( str.split("").reverse().join("").replace(/(\d{3})+?/g,function(s){
return s+",";
}).replace(/,$/,"").split("").reverse().join("") )
方法4 利用while循環(huán)拼接字符串每隔3個(gè)數(shù)字加一個(gè)分隔符,首尾不加
var result="",
index = 0,
len = str.length-1;
while(len>=0) {
index%3===0&&index!==0 ? result+=","+str[len] : result+=str[len];
len--;
index++;
};
result=result.split("").reverse().join("");
console.info(result);
方法5 利用while循環(huán)在數(shù)組里push分隔符,首尾不加
// 利用while循環(huán)在數(shù)組里push分隔符
var result="",
index = 0,
len = str.length,
i = len-1,
arr = str.split("");
while(len-index>0){
len>=index&&len-index!==len && arr.splice(len-index,0,",");
index+=3;
i-=4;
};
console.log(arr.join(""));
78、load和ready區(qū)別
1、概念
document.ready:
是ready,表示文檔結(jié)構(gòu)已經(jīng)加載完成 不包含圖片等非文字媒體文件 只要html標(biāo)簽結(jié)構(gòu)加載完畢就可以;
document.load:
是onload,指示頁(yè)面包含圖片等文件在內(nèi)的所有元素都加載完成。
2、作用
document.ready:
在DOM加載完成后就可以可以對(duì)DOM進(jìn)行操作。
一般情況一個(gè)頁(yè)面響應(yīng)加載的順序是,域名解析-加載html-加載js和css-加載圖片等其他信息。
那么Dom Ready應(yīng)該在“加載js和css”和“加載圖片等其他信息”之間,就可以操作Dom了。
document.load:
在document文檔加載完成后就可以可以對(duì)DOM進(jìn)行操作,document文檔包括了加載圖片等其他信息。
那么Dom Load就是在頁(yè)面響應(yīng)加載的順序中的“加載圖片等其他信息”之后,就可以操作Dom了。
3、加載順序
document.ready:
文檔加載的順序:域名解析-->加載HTML-->加載JavaScript和CSS-->加載圖片等非文字媒體文件。
只要<img>標(biāo)簽加載完成,不用等該圖片加載完成,就可以設(shè)置圖片的屬性或樣式等。
在原生JavaScript中沒(méi)有Dom ready的直接方法。
document.load:
文檔加載的順序:域名解析-->加載HTML-->加載JavaScript和CSS-->加載圖片等非文字媒體文件。
DOM load在加載圖片等非文字媒體文件之后,表示在document文檔加載完成后才可以對(duì)DOM進(jìn)行操作,document文檔包括了加載圖片等非文字媒體文件。
例如,需要等該圖片加載完成,才可以設(shè)置圖片的屬性或樣式等。
在原生JavaScript中使用onload事件。
79、自定義事件
自定義事件,就是自己定義事件類型,自己定義事件處理函數(shù)。
我們平時(shí)操作dom時(shí)經(jīng)常會(huì)用到onclick、onmousemove等瀏覽器特定行為的事件類型。
封裝is自定義事件基本的構(gòu)思:
var eventTarget = {
addEvent: function(){
//添加事件
},
fireEvent: function(){
//觸發(fā)事件
},
removeEvent: function(){
//移除事件
}
};
在js默認(rèn)事件中事件類型以及對(duì)應(yīng)的執(zhí)行函數(shù)是一一對(duì)應(yīng)的,但是自定義事件,需要一個(gè)映射表來(lái)建立兩者之間的聯(lián)系。
如: 這樣每個(gè)類型可以處理多個(gè)事件函數(shù)
handlers = {
"type1":[
"fun1",
"fun2",
// "..."
],
"type2":[
"fun1",
"fun2"
// "..."
]
//"..."
}
代碼實(shí)現(xiàn):
function EventTarget(){
//事件處理程序數(shù)組集合
this.handlers={};
}
//自定義事件的原型對(duì)象
EventTarget.prototype={
//設(shè)置原型構(gòu)造函數(shù)鏈
constructor:EventTarget,
//注冊(cè)給定類型的事件處理程序
//type->自定義事件類型,如click,handler->自定義事件回調(diào)函數(shù)
addEvent:function(type,handler){
//判斷事件處理函數(shù)中是否有該類型事件
if(this.handlers[type]==undefined){
this.handlers[type]=[];
}
this.handlers[type].push(handler);
},
//觸發(fā)事件
//event為一個(gè)js對(duì)象,屬性中至少包含type屬性。
fireEvent:function(event){
//模擬真實(shí)事件的event
if(!event.target){
event.target=this;
}
//判斷是否存在該事件類型
if(this.handlers[event.type] instanceof Array){
var items=this.handlers[event.type];
//在同一事件類型下可能存在多個(gè)事件處理函數(shù),依次觸發(fā)
//執(zhí)行觸發(fā)
items.forEach(function(item){
item(event);
})
}
},
//刪除事件
removeEvent:function(type,handler){
//判斷是否存在該事件類型
if(this.handlers[type] instanceof Array){
var items=this.handlers[type];
//在同一事件類型下可能存在多個(gè)處理事件
for(var i=0;i<items.length;i++){
if(items[i]==handler){
//從該類型的事件數(shù)組中刪除該事件
items.splice(i,1);
break;
}
}
}
}
}
//調(diào)用方法
function fun(){
console.log('執(zhí)行該方法');
}
function fun1(obj){
console.log('run '+obj.min+'s');
}
var target=new EventTarget();
target.addEvent("run",fun);//添加事件
target.addEvent("run",fun1);//添加事件
target.fireEvent({type:"run",min:"30"});//執(zhí)行該方法 123
target.removeEvent("run",fun);//移除事件
target.fireEvent({type:"run",min:"20"});//123
為什么要把方法添加到對(duì)象原型上?
在構(gòu)造函數(shù)中加屬性,在原型中加方法。
將屬性和方法都寫在構(gòu)造函數(shù)里是沒(méi)有問(wèn)題的,但是每次進(jìn)行實(shí)例化的過(guò)程中,要重復(fù)創(chuàng)建功能不變的方法。
由于方法本質(zhì)上是函數(shù),其實(shí)也就是在堆內(nèi)存中又新建了一個(gè)對(duì)象空間存放存儲(chǔ)函數(shù),造成了不必要的資源浪費(fèi)。
在本身添加會(huì)導(dǎo)致每次對(duì)象實(shí)例化時(shí)代碼被復(fù)制,都需要申請(qǐng)一塊內(nèi)存存放該方法。
寫一個(gè)EventEmitter類,包括on()、off()、once()、emit()方法
once():為指定事件注冊(cè)一個(gè)單次監(jiān)聽(tīng)器,單次監(jiān)聽(tīng)器最多只觸發(fā)一次,觸發(fā)后立即解除監(jiān)聽(tīng)器。
class EventEmitter{
constructor(){
this.handlers={};
}
on(type,fn){
if(!this.handlers[type]){
this.handlers[type]=[];
}
this.handlers[type].push(fn);
return this;
}
off(type,fn){
let fns=this.handlers[type];
for(let i=0;i<fns.length;i++){
if(fns[i]==fn){
fns.splice(i,1);
break;
}
}
return this;
}
emit(...args){
let type=args[0];
let params=[].slice.call(args,1);
let fn=this.handlers[type];
fn.forEach((item)=>{
item.apply(this,params);//執(zhí)行函數(shù)
})
return this;
}
once(type,fn){
let wrap=(...args)=>{
fn.apply(this,args);//執(zhí)行事件后刪除
this.off(type,wrap);
}
this.on(type,wrap);//再添加上去
return this;
}
}
let emitter=new EventEmitter();
function fun1(){
console.log('fun1');
}
function fun2(){
console.log('fun2');
}
function fun3(){
console.log('fun3');
}
emitter.on('TEST1',fun1).on('TEST2',fun2).emit('TEST1').once('TEST2',fun3);
emitter.emit("TEST2");
80、setTimeout實(shí)現(xiàn)setInterval
setTimeout() :在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式,只執(zhí)行一次。
setInterval() :按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式。方法會(huì)不停地調(diào)用函數(shù),直到 clearInterval() 被調(diào)用或窗口被關(guān)閉。
思路是使用遞歸函數(shù),不斷地去執(zhí)行setTimeout從而達(dá)到setInterval的效果,看代碼
function mySetInterval(fn, millisec){
function interval(){
setTimeout(interval, millisec);
fn();
}
setTimeout(interval, millisec)
}
這個(gè)mySetInterval函數(shù)有一個(gè)叫做interval的內(nèi)部函數(shù),它通過(guò)setTimeout來(lái)自動(dòng)被調(diào)用,在interval中有一個(gè)閉包,調(diào)用了回調(diào)函數(shù)并通過(guò)setTimeout再次調(diào)用了interval。
一個(gè)更好的實(shí)現(xiàn):
我們?cè)僭黾右粋€(gè)額外的參數(shù)用來(lái)標(biāo)明代碼執(zhí)行的次數(shù)
function mySetInterval(fn, millisec,count){
function interval(){
if(typeof count===‘undefined’||count-->0){
setTimeout(interval, millisec);
try{
fn()
}catch(e){
count = 0;
throw e.toString();
}
}
}
setTimeout(interval, millisec)
}
81、避免回調(diào)地獄
使用 async await 配合 promise 是 解決回調(diào)地獄的終極方法
async/await特點(diǎn)
1, async/await更加語(yǔ)義化,async 是“異步”的簡(jiǎn)寫,async function 用于申明一個(gè) function 是異步的; await,可以認(rèn)為是async wait的簡(jiǎn)寫, 用于等待一個(gè)異步方法執(zhí)行完成;
2, async/await是一個(gè)用同步思維解決異步問(wèn)題的方案(等結(jié)果出來(lái)之后,代碼才會(huì)繼續(xù)往下執(zhí)行)
3, 可以通過(guò)多層 async function 的同步寫法代替?zhèn)鹘y(tǒng)的callback嵌套
async function語(yǔ)法
1, 自動(dòng)將常規(guī)函數(shù)轉(zhuǎn)換成Promise,返回值也是一個(gè)Promise對(duì)象
2, 只有async函數(shù)內(nèi)部的異步操作執(zhí)行完,才會(huì)執(zhí)行then方法指定的回調(diào)函數(shù)
3, 異步函數(shù)內(nèi)部可以使用await
await語(yǔ)法
1, await 放置在Promise調(diào)用之前,await 強(qiáng)制后面點(diǎn)代碼等待,直到Promise對(duì)象resolve,得到resolve的值作為await表達(dá)式的運(yùn)算結(jié)果
2. await只能在async函數(shù)內(nèi)部使用,用在普通函數(shù)里就會(huì)報(bào)錯(cuò)
函數(shù)形式
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {reject('error')}, ms); //reject模擬出錯(cuò),返回error
});
}
async function asyncPrint(ms) {
try {
console.log('start');
await timeout(ms); //這里返回了錯(cuò)誤
console.log('end'); //所以這句代碼不會(huì)被執(zhí)行了
} catch(err) {
console.log(err); //這里捕捉到錯(cuò)誤error
}
}
82、callee和caller的作用
caller
caller返回一個(gè)函數(shù)的引用,這個(gè)函數(shù)調(diào)用了當(dāng)前的函數(shù)。
使用這個(gè)屬性要注意:
1 這個(gè)屬性只有當(dāng)函數(shù)在執(zhí)行時(shí)才有用
2 如果在javascript程序中,函數(shù)是由頂層調(diào)用的,則返回null
functionName.caller: functionName是當(dāng)前正在執(zhí)行的函數(shù)。
var a = function() {
alert(a.caller);
}
var b = function() {
a();
}
b();
上面的代碼中,b調(diào)用了a,那么a.caller返回的是b的引用,結(jié)果如下:
var b = function() {
a();
}
如果直接調(diào)用a(即a在任何函數(shù)中被調(diào)用,也就是頂層調(diào)用),返回null:
var a = function() {
alert(a.caller);
}
var b = function() {
a();
}
//b();
a();
輸出結(jié)果:
null
callee
callee放回正在執(zhí)行的函數(shù)本身的引用,它是arguments的一個(gè)屬性
使用callee時(shí)要注意:
1 這個(gè)屬性只有在函數(shù)執(zhí)行時(shí)才有效
2 它有一個(gè)length屬性,可以用來(lái)獲得形參的個(gè)數(shù),因此可以用來(lái)比較形參和實(shí)參個(gè)數(shù)是否一致,即比較arguments.length是否等于arguments.callee.length
3 它可以用來(lái)遞歸匿名函數(shù)。
var a = function() {
alert(arguments.callee);
}
var b = function() {
a();
}
b();
a在b中被調(diào)用,但是它返回了a本身的引用,結(jié)果如下:
var a = function() {
alert(arguments.callee);
}
83、統(tǒng)計(jì)字符串中字母?jìng)€(gè)數(shù)或統(tǒng)計(jì)最多的字母數(shù)
統(tǒng)計(jì)字母出現(xiàn)的次數(shù)
function count( str ){
var obj={};
for(var i=0;i<str.length; i++){
if(obj[ str[i] ]==undefined){
//對(duì)象初始化;如果key在對(duì)象中找不到,那么會(huì)返回undefined,反向思維
obj[ str[i] ]= 1;
} else{
obj[ str[i] ]++;
}
}
//取出各個(gè)字母和它的個(gè)數(shù),作為一個(gè)新對(duì)象保存在obj對(duì)象中
return obj;
}
cosnle.log( count( "shhkfahkahsadhadskhdskdha" ) );
統(tǒng)計(jì)字符出現(xiàn)次數(shù)最多的字母
function allProMax(obj){
var mm="";
for(var m in obj){
if(mm==""){
mm=new Object();
mm[m]=obj[m];
}else{
for(var j in mm){
if(mm[j]<obj[m]){
//清空原來(lái)的內(nèi)容
mm=new Object();
//放入新的內(nèi)容
mm[m]=obj[m];
}
}
}
}
return mm ;
}
console.log( allProMax(count()) )
84、面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別
一、面向?qū)ο笈c面向過(guò)程的區(qū)別
面向過(guò)程就是分析出解決問(wèn)題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時(shí)候一個(gè)一個(gè)依次調(diào)用就可以了;面向?qū)ο笫前褬?gòu)成問(wèn)題事務(wù)分解成各個(gè)對(duì)象,建立對(duì)象的目的不是為了完成一個(gè)步驟,而是為了描敘某個(gè)事物在整個(gè)解決問(wèn)題的步驟中的行為。
可以拿生活中的實(shí)例來(lái)理解面向過(guò)程與面向?qū)ο?,例如五子棋,面向過(guò)程的設(shè)計(jì)思路就是首先分析問(wèn)題的步驟:1、開(kāi)始游戲,2、黑子先走,3、繪制畫面,4、判斷輸贏,5、輪到白子,6、繪制畫面,7、判斷輸贏,8、返回步驟2,9、輸出最后結(jié)果。把上面每個(gè)步驟用不同的方法來(lái)實(shí)現(xiàn)。
如果是面向?qū)ο蟮脑O(shè)計(jì)思想來(lái)解決問(wèn)題。面向?qū)ο蟮脑O(shè)計(jì)則是從另外的思路來(lái)解決問(wèn)題。整個(gè)五子棋可以分為1、黑白雙方,這兩方的行為是一模一樣的,2、棋盤系統(tǒng),負(fù)責(zé)繪制畫面,3、規(guī)則系統(tǒng),負(fù)責(zé)判定諸如犯規(guī)、輸贏等。第一類對(duì)象(玩家對(duì)象)負(fù)責(zé)接受用戶輸入,并告知第二類對(duì)象(棋盤對(duì)象)棋子布局的變化,棋盤對(duì)象接收到了棋子的變化就要負(fù)責(zé)在屏幕上面顯示出這種變化,同時(shí)利用第三類對(duì)象(規(guī)則系統(tǒng))來(lái)對(duì)棋局進(jìn)行判定。
可以明顯地看出,面向?qū)ο笫且怨δ軄?lái)劃分問(wèn)題,而不是步驟。同樣是繪制棋局,這樣的行為在面向過(guò)程的設(shè)計(jì)中分散在了多個(gè)步驟中,很可能出現(xiàn)不同的繪制版本,因?yàn)橥ǔTO(shè)計(jì)人員會(huì)考慮到實(shí)際情況進(jìn)行各種各樣的簡(jiǎn)化。而面向?qū)ο蟮脑O(shè)計(jì)中,繪圖只可能在棋盤對(duì)象中出現(xiàn),從而保證了繪圖的統(tǒng)一。
上述的內(nèi)容是從網(wǎng)上查到的,覺(jué)得這個(gè)例子非常的生動(dòng)形象,我就寫了下來(lái),現(xiàn)在就應(yīng)該理解了他倆的區(qū)別了吧,其實(shí)就是兩句話,面向?qū)ο缶褪歉叨葘?shí)物抽象化、面向過(guò)程就是自頂向下的編程!
二、面向?qū)ο蟮奶攸c(diǎn)
在了解其特點(diǎn)之前,咱們先談?wù)剬?duì)象,對(duì)象就是現(xiàn)實(shí)世界存在的任何事務(wù)都可以稱之為對(duì)象,有著自己獨(dú)特的個(gè)性
1, 概念 對(duì) 具有相同特性的一類事物的抽象描述
2, 組成 屬性 和 方法
3, 模板 構(gòu)造函數(shù)
4, 特點(diǎn) 封裝 繼承 多態(tài)
屬性用來(lái)描述具體某個(gè)對(duì)象的特征。比如小志身高180M,體重70KG,這里身高、體重都是屬性。
面向?qū)ο蟮乃枷刖褪前岩磺卸伎闯蓪?duì)象,而對(duì)象一般都由屬性+方法組成!
屬性屬于對(duì)象靜態(tài)的一面,用來(lái)形容對(duì)象的一些特性,方法屬于對(duì)象動(dòng)態(tài)的一面,咱們舉一個(gè)例子,小明會(huì)跑,會(huì)說(shuō)話,跑、說(shuō)話這些行為就是對(duì)象的方法!所以為動(dòng)態(tài)的一面, 我們把屬性和方法稱為這個(gè)對(duì)象的成員!
類:具有同種屬性的對(duì)象稱為類,是個(gè)抽象的概念。比如“人”就是一類,期中有一些人名,比如小明、小紅、小玲等等這些都是對(duì)象,類就相當(dāng)于一個(gè)模具,他定義了它所包含的全體對(duì)象的公共特征和功能,對(duì)象就是類的一個(gè)實(shí)例化,小明就是人的一個(gè)實(shí)例化!我們?cè)谧龀绦虻臅r(shí)候,經(jīng)常要將一個(gè)變量實(shí)例化,就是這個(gè)原理!我們一般在做程序的時(shí)候一般都不用類名的,比如我們?cè)诮行∶鞯臅r(shí)候,不會(huì)喊“人,你干嘛呢!”而是說(shuō)的是“小明,你在干嘛呢!”
面向?qū)ο笥腥筇匦裕謩e是封裝性、繼承性和多態(tài)性,這里小編不給予太多的解釋,因?yàn)樵诤筮叺牟┛蜁?huì)專門總結(jié)的!
三、面向過(guò)程與面向?qū)ο蟮膬?yōu)缺點(diǎn)
很多資料上全都是一群很難理解的理論知識(shí),整的小編頭都大了,后來(lái)發(fā)現(xiàn)了一個(gè)比較好的文章,寫的真是太棒了,通俗易懂,想要不明白都難!
用面向過(guò)程的方法寫出來(lái)的程序是一份蛋炒飯,而用面向?qū)ο髮懗鰜?lái)的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什么菜,你就澆上什么菜。我覺(jué)得這個(gè)比喻還是比較貼切的。
蛋炒飯制作的細(xì)節(jié),我不太清楚,因?yàn)槲覜](méi)當(dāng)過(guò)廚師,也不會(huì)做飯,但最后的一道工序肯定是把米飯和雞蛋混在一起炒勻。蓋澆飯呢,則是把米飯和蓋菜分別做好,你如果要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;如果要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。
蛋炒飯的好處就是入味均勻,吃起來(lái)香。如果恰巧你不愛(ài)吃雞蛋,只愛(ài)吃青菜的話,那么唯一的辦法就是全部倒掉,重新做一份青菜炒飯了。蓋澆飯就沒(méi)這么多麻煩,你只需要把上面的蓋菜撥掉,更換一份蓋菜就可以了。蓋澆飯的缺點(diǎn)是入味不均,可能沒(méi)有蛋炒飯那么香。
到底是蛋炒飯好還是蓋澆飯好呢?其實(shí)這類問(wèn)題都很難回答,非要比個(gè)上下高低的話,就必須設(shè)定一個(gè)場(chǎng)景,否則只能說(shuō)是各有所長(zhǎng)。如果大家都不是美食家,沒(méi)那么多講究,那么從飯館角度來(lái)講的話,做蓋澆飯顯然比蛋炒飯更有優(yōu)勢(shì),他可以組合出來(lái)任意多的組合,而且不會(huì)浪費(fèi)。
蓋澆飯的好處就是"菜"“飯"分離,從而提高了制作蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業(yè)術(shù)語(yǔ)就是"可維護(hù)性"比較好,“飯” 和"菜"的耦合度比較低。蛋炒飯將"蛋”“飯"攪和在一起,想換"蛋”"飯"中任何一種都很困難,耦合度很高,以至于"可維護(hù)性"比較差。軟件工程追求的目標(biāo)之一就是可維護(hù)性,可維護(hù)性主要表現(xiàn)在3個(gè)方面:可理解性、可測(cè)試性和可修改性。面向?qū)ο蟮暮锰幹痪褪秋@著的改善了軟件系統(tǒng)的可維護(hù)性。
我們最后簡(jiǎn)單總結(jié)一下
面向過(guò)程
優(yōu)點(diǎn):性能比面向?qū)ο蟾?,因?yàn)轭愓{(diào)用時(shí)需要實(shí)例化,開(kāi)銷比較大,比較消耗資源;比如單片機(jī)、嵌入式開(kāi)發(fā)、 Linux/Unix等一般采用面向過(guò)程開(kāi)發(fā),性能是最重要的因素。
缺點(diǎn):沒(méi)有面向?qū)ο笠拙S護(hù)、易復(fù)用、易擴(kuò)展
面向?qū)ο?/h5>
優(yōu)點(diǎn):易維護(hù)、易復(fù)用、易擴(kuò)展,由于面向?qū)ο笥蟹庋b、繼承、多態(tài)性的特性,可以設(shè)計(jì)出低耦合的系統(tǒng),使系統(tǒng) 更加靈活、更加易于維護(hù)
缺點(diǎn):性能比面向過(guò)程低
85、eval
eval()是全局對(duì)象的一個(gè)函數(shù)屬性。
eval()的參數(shù)是一個(gè)字符串。如果字符串表示的是表達(dá)式,eval()會(huì)對(duì)表達(dá)式進(jìn)行求值。如果參數(shù)表示一個(gè)或多個(gè)JavaScript語(yǔ)句, 那么eval()就會(huì)執(zhí)行這些語(yǔ)句。注意不要用eval()來(lái)執(zhí)行一個(gè)四則運(yùn)算表達(dá)式;因?yàn)?JavaScript 會(huì)自動(dòng)為四則運(yùn)算求值并不需要用eval來(lái)包裹。
這里的四則運(yùn)算是指數(shù)學(xué)上的運(yùn)算,如:3 + 4 * 4 / 6。注意這里面并沒(méi)有變量,只是單純的數(shù)學(xué)運(yùn)算,這樣的運(yùn)算式并不需要調(diào)用eval來(lái)計(jì)算,直接在代碼中計(jì)算就可以。其實(shí)即便帶有變量,JavaScript也是可以直接計(jì)算的,但是如果你現(xiàn)在只想聲明一個(gè)帶有變量的表達(dá)式,但是想稍后進(jìn)行運(yùn)算(你有可能在聲明這個(gè)帶有變量的運(yùn)算式之后還有可能對(duì)里面的變量進(jìn)行修改),就可以使用eval。
如果要將算數(shù)表達(dá)式構(gòu)造成為一個(gè)字符串,你可以用eval()在隨后對(duì)其求值。比如,假如你有一個(gè)變量 x ,你可以通過(guò)一個(gè)字符串表達(dá)式來(lái)對(duì)涉及x的表達(dá)式延遲求值,將 “3 * x + 2”,存儲(chǔ)為變量,然后在你的腳本后面的一個(gè)地方調(diào)用eval()。
如果eval()的參數(shù)不是字符串,eval()將會(huì)將參數(shù)原封不動(dòng)的返回。在下面的例子中,字符串構(gòu)造器被指定,eval()返回了字符串對(duì)象而不是對(duì)字符串求值。
// 返回了包含"2 + 2"的字符串對(duì)象
eval(new String("2 + 2"));
// returns 4
eval("2 + 2");
eval() 是一個(gè)危險(xiǎn)的函數(shù), 他執(zhí)行的代碼擁有著執(zhí)行者的權(quán)利。如果你用eval()運(yùn)行的字符串代碼被惡意方(不懷好意的人)操控修改,您可能會(huì)利用最終在用戶機(jī)器上運(yùn)行惡意方部署的惡意代碼,并導(dǎo)致您失去您的網(wǎng)頁(yè)或者擴(kuò)展程序的權(quán)限。更重要的是,第三方代碼可以看到某一個(gè)eval()被調(diào)用時(shí)的作用域,這也有可能導(dǎo)致一些不同方式的攻擊。相似的Function就是不容易被攻擊的。
eval()的運(yùn)行效率也普遍的比其他的替代方案慢,因?yàn)樗麜?huì)調(diào)用js解析器,即便現(xiàn)代的JS引擎中已經(jīng)對(duì)此做了優(yōu)化。
在常見(jiàn)的案例中我們都會(huì)找更安全或者更快的方案去替換他
86、proxy
proxy在目標(biāo)對(duì)象的外層搭建了一層攔截,外界對(duì)目標(biāo)對(duì)象的某些操作,必須通過(guò)這層攔截
var proxy = new Proxy(target, handler);
new Proxy()表示生成一個(gè)Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為
var target = {
name: 'poetries'
};
var logHandler = {
get: function(target, key) {
console.log(`${key} 被讀取`);
return target[key];
},
set: function(target, key, value) {
console.log(`${key} 被設(shè)置為 ${value}`);
target[key] = value;
}
}
var targetWithLog = new Proxy(target, logHandler);
targetWithLog.name; // 控制臺(tái)輸出:name 被讀取
targetWithLog.name = 'others'; // 控制臺(tái)輸出:name 被設(shè)置為 others
console.log(target.name); // 控制臺(tái)輸出: others
targetWithLog 讀取屬性的值時(shí),實(shí)際上執(zhí)行的是 logHandler.get :在控制臺(tái)輸出信息,并且讀取被代理對(duì)象 target 的屬性。
在 targetWithLog 設(shè)置屬性值時(shí),實(shí)際上執(zhí)行的是 logHandler.set :在控制臺(tái)輸出信息,并且設(shè)置被代理對(duì)象 target 的屬性的值
// 由于攔截函數(shù)總是返回35,所以訪問(wèn)任何屬性都得到35
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
Proxy 實(shí)例也可以作為其他對(duì)象的原型對(duì)象
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
proxy對(duì)象是obj對(duì)象的原型,obj對(duì)象本身并沒(méi)有time屬性,所以根據(jù)原型鏈,會(huì)在proxy對(duì)象上讀取該屬性,導(dǎo)致被攔截
Proxy的作用
對(duì)于代理模式 Proxy 的作用主要體現(xiàn)在三個(gè)方面:
1 攔截和監(jiān)視外部對(duì)對(duì)象的訪問(wèn)
2 降低函數(shù)或類的復(fù)雜度
3 在復(fù)雜操作前對(duì)操作進(jìn)行校驗(yàn)或?qū)λ栀Y源進(jìn)行管理
Proxy所能代理的范圍–handler
實(shí)際上 handler 本身就是ES6所新設(shè)計(jì)的一個(gè)對(duì)象.它的作用就是用來(lái) 自定義代理對(duì)象的各種可代理操作 。它本身一共有13中方法,每種方法都可以代理一種操作.其13種方法如下
// 在讀取代理對(duì)象的原型時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getPrototypeOf(proxy) 時(shí)。
handler.getPrototypeOf()
// 在設(shè)置代理對(duì)象的原型時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.setPrototypeOf(proxy, null) 時(shí)。
handler.setPrototypeOf()
// 在判斷一個(gè)代理對(duì)象是否是可擴(kuò)展時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.isExtensible(proxy) 時(shí)。
handler.isExtensible()
// 在讓一個(gè)代理對(duì)象不可擴(kuò)展時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.preventExtensions(proxy) 時(shí)。
handler.preventExtensions()
// 在獲取代理對(duì)象某個(gè)屬性的屬性描述時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyDescriptor(proxy, "foo") 時(shí)。
handler.getOwnPropertyDescriptor()
// 在定義代理對(duì)象某個(gè)屬性時(shí)的屬性描述時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.defineProperty(proxy, "foo", {}) 時(shí)。
andler.defineProperty()
// 在判斷代理對(duì)象是否擁有某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 "foo" in proxy 時(shí)。
handler.has()
// 在讀取代理對(duì)象的某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy.foo 時(shí)。
handler.get()
// 在給代理對(duì)象的某個(gè)屬性賦值時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy.foo = 1 時(shí)。
handler.set()
// 在刪除代理對(duì)象的某個(gè)屬性時(shí)觸發(fā)該操作,比如在執(zhí)行 delete proxy.foo 時(shí)。
handler.deleteProperty()
// 在獲取代理對(duì)象的所有屬性鍵時(shí)觸發(fā)該操作,比如在執(zhí)行 Object.getOwnPropertyNames(proxy) 時(shí)。
handler.ownKeys()
// 在調(diào)用一個(gè)目標(biāo)對(duì)象為函數(shù)的代理對(duì)象時(shí)觸發(fā)該操作,比如在執(zhí)行 proxy() 時(shí)。
handler.apply()
// 在給一個(gè)目標(biāo)對(duì)象為構(gòu)造函數(shù)的代理對(duì)象構(gòu)造實(shí)例時(shí)觸發(fā)該操作,比如在執(zhí)行new proxy() 時(shí)。
handler.construct()
Proxy場(chǎng)景
實(shí)現(xiàn)私有變量
var target = {
name: 'poetries',
_age: 22
}
var logHandler = {
get: function(target,key){
if(key.startsWith('_')){
console.log('私有變量age不能被訪問(wèn)')
return false
}
return target[key];
},
set: function(target, key, value) {
if(key.startsWith('_')){
console.log('私有變量age不能被修改')
return false
}
target[key] = value;
}
}
var targetWithLog = new Proxy(target, logHandler);
// 私有變量age不能被訪問(wèn)
targetWithLog.name;
// 私有變量age不能被修改
targetWithLog.name = 'others';
在下面的代碼中,我們聲明了一個(gè)私有的 apiKey,便于 api 這個(gè)對(duì)象內(nèi)部的方法調(diào)用,但不希望從外部也能夠訪問(wèn) api._apiKey
var api = {
_apiKey: '123abc456def',
/* mock methods that use this._apiKey */
getUsers: function(){},
getUser: function(userId){},
setUser: function(userId, config){}
};
// logs '123abc456def';
console.log("An apiKey we want to keep private", api._apiKey);
// get and mutate _apiKeys as desired
var apiKey = api._apiKey;
api._apiKey = '987654321';
很顯然,約定俗成是沒(méi)有束縛力的。使用 ES6 Proxy 我們就可以實(shí)現(xiàn)真實(shí)的私有變量了,下面針對(duì)不同的讀取方式演示兩個(gè)不同的私有化方法。第一種方法是使用 set / get 攔截讀寫請(qǐng)求并返回 undefined:
let api = {
_apiKey: '123abc456def',
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, value, proxy);
}
});
// 以下操作都會(huì)拋出錯(cuò)誤
console.log(api._apiKey);
api._apiKey = '987654321';
第二種方法是使用 has 攔截 in 操作
var api = {
_apiKey: '123abc456def',
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
has(target, key) {
return (RESTRICTED.indexOf(key) > -1) ?
false :
Reflect.has(target, key);
}
});
// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);
for (var key in api) {
if (api.hasOwnProperty(key) && key === "_apiKey") {
console.log("This will never be logged because the proxy obscures _apiKey...")
}
}
抽離校驗(yàn)?zāi)K
讓我們從一個(gè)簡(jiǎn)單的類型校驗(yàn)開(kāi)始做起,這個(gè)示例演示了如何使用 Proxy 保障數(shù)據(jù)類型的準(zhǔn)確性
let numericDataStore = {
count: 0,
amount: 1234,
total: 14
};
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("Properties in numericDataStore can only be numbers");
}
return Reflect.set(target, key, value, proxy);
}
});
// 拋出錯(cuò)誤,因?yàn)?"foo" 不是數(shù)值
numericDataStore.count = "foo";
// 賦值成功
numericDataStore.count = 333;
如果要直接為對(duì)象的所有屬性開(kāi)發(fā)一個(gè)校驗(yàn)器可能很快就會(huì)讓代碼結(jié)構(gòu)變得臃腫,使用 Proxy 則可以將校驗(yàn)器從核心邏輯分離出來(lái)自成一體
function createValidator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
let validator = this._validator[key];
if (!!validator(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error(`Cannot set ${key} to ${value}. Invalid.`);
}
} else {
throw Error(`${key} is not a valid property`)
}
}
});
}
const personValidators = {
name(val) {
return typeof val === 'string';
},
age(val) {
return typeof age === 'number' && val > 18;
}
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
return createValidator(this, personValidators);
}
}
const bill = new Person('Bill', 25);
// 以下操作都會(huì)報(bào)錯(cuò)
bill.name = 0;
bill.age = 'Bill';
bill.age = 15;
通過(guò)校驗(yàn)器和主邏輯的分離,你可以無(wú)限擴(kuò)展 personValidators 校驗(yàn)器的內(nèi)容,而不會(huì)對(duì)相關(guān)的類或函數(shù)造成直接破壞。更復(fù)雜一點(diǎn),我們還可以使用 Proxy 模擬類型檢查,檢查函數(shù)是否接收了類型和數(shù)量都正確的參數(shù)
let obj = {
pickyMethodOne: function(obj, str, num) { /* ... */ },
pickyMethodTwo: function(num, obj) { /*... */ }
};
const argTypes = {
pickyMethodOne: ["object", "string", "number"],
pickyMethodTwo: ["number", "object"]
};
obj = new Proxy(obj, {
get: function(target, key, proxy) {
var value = target[key];
return function(...args) {
var checkArgs = argChecker(key, args, argTypes[key]);
return Reflect.apply(value, target, args);
};
}
});
function argChecker(name, args, checkers) {
for (var idx = 0; idx < args.length; idx++) {
var arg = args[idx];
var type = checkers[idx];
if (!arg || typeof arg !== type) {
console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
}
}
}
obj.pickyMethodOne();
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3
obj.pickyMethodTwo("wopdopadoo", {});
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1
// No warnings logged
obj.pickyMethodOne({}, "a little string", 123);
obj.pickyMethodOne(123, {});
訪問(wèn)日志
對(duì)于那些調(diào)用頻繁、運(yùn)行緩慢或占用執(zhí)行環(huán)境資源較多的屬性或接口,開(kāi)發(fā)者會(huì)希望記錄它們的使用情況或性能表現(xiàn),這個(gè)時(shí)候就可以使用 Proxy 充當(dāng)中間件的角色,輕而易舉實(shí)現(xiàn)日志功能
let api = {
_apiKey: '123abc456def',
getUsers: function() { /* ... */ },
getUser: function(userId) { /* ... */ },
setUser: function(userId, config) { /* ... */ }
};
function logMethodAsync(timestamp, method) {
setTimeout(function() {
console.log(`${timestamp} - Logging ${method} request asynchronously.`);
}, 0)
}
api = new Proxy(api, {
get: function(target, key, proxy) {
var value = target[key];
return function(...arguments) {
logMethodAsync(new Date(), key);
return Reflect.apply(value, target, arguments);
};
}
});
api.getUsers();
預(yù)警和攔截
假設(shè)你不想讓其他開(kāi)發(fā)者刪除 noDelete 屬性,還想讓調(diào)用 oldMethod 的開(kāi)發(fā)者了解到這個(gè)方法已經(jīng)被廢棄了,或者告訴開(kāi)發(fā)者不要修改 doNotChange 屬性,那么就可以使用 Proxy 來(lái)實(shí)現(xiàn)
let dataStore = {
noDelete: 1235,
oldMethod: function() {/*...*/ },
doNotChange: "tried and true"
};
const NODELETE = ['noDelete'];
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];
dataStore = new Proxy(dataStore, {
set(target, key, value, proxy) {
if (NOCHANGE.includes(key)) {
throw Error(`Error! ${key} is immutable.`);
}
return Reflect.set(target, key, value, proxy);
},
deleteProperty(target, key) {
if (NODELETE.includes(key)) {
throw Error(`Error! ${key} cannot be deleted.`);
}
return Reflect.deleteProperty(target, key);
},
get(target, key, proxy) {
if (DEPRECATED.includes(key)) {
console.warn(`Warning! ${key} is deprecated.`);
}
var val = target[key];
return typeof val === 'function' ?
function(...args) {
Reflect.apply(target[key], target, args);
} :
val;
}
});
// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo";
delete dataStore.noDelete;
dataStore.oldMethod();
過(guò)濾操作
某些操作會(huì)非常占用資源,比如傳輸大文件,這個(gè)時(shí)候如果文件已經(jīng)在分塊發(fā)送了,就不需要在對(duì)新的請(qǐng)求作出相應(yīng)(非絕對(duì)),這個(gè)時(shí)候就可以使用 Proxy 對(duì)當(dāng)請(qǐng)求進(jìn)行特征檢測(cè),并根據(jù)特征過(guò)濾出哪些是不需要響應(yīng)的,哪些是需要響應(yīng)的。下面的代碼簡(jiǎn)單演示了過(guò)濾特征的方式,并不是完整代碼,相信大家會(huì)理解其中的妙處
let obj = {
getGiantFile: function(fileId) {/*...*/ }
};
obj = new Proxy(obj, {
get(target, key, proxy) {
return function(...args) {
const id = args[0];
let isEnroute = checkEnroute(id);
let isDownloading = checkStatus(id);
let cached = getCached(id);
if (isEnroute || isDownloading) {
return false;
}
if (cached) {
return cached;
}
return Reflect.apply(target[key], target, args);
}
}
});
87、事件代理
事件代理 也就是 事件委托
不是直接給標(biāo)簽添加事件 是給標(biāo)簽的父級(jí)添加事件 通過(guò) 事件對(duì)象 判斷觸發(fā)事件的標(biāo)簽對(duì)象是誰(shuí) 執(zhí)行不同的函數(shù)程序的語(yǔ)法形式
委托的優(yōu)點(diǎn)
減少內(nèi)存消耗
試想一下,若果我們有一個(gè)列表,列表之中有大量的列表項(xiàng),我們需要在點(diǎn)擊列表項(xiàng)的時(shí)候響應(yīng)一個(gè)事件
如果給每個(gè)列表項(xiàng)一一都綁定一個(gè)函數(shù),那對(duì)于內(nèi)存消耗是非常大的,效率上需要消耗很多性能;
因此,比較好的方法就是把這個(gè)點(diǎn)擊事件綁定到他的父層,也就是 ul 上,然后在執(zhí)行事件的時(shí)候再去匹配判斷目標(biāo)元素;
所以事件委托可以減少大量的內(nèi)存消耗,節(jié)約效率。
動(dòng)態(tài)綁定事件
比如上述的例子中列表項(xiàng)就幾個(gè),我們給每個(gè)列表項(xiàng)都綁定了事件;
在很多時(shí)候,我們需要通過(guò) AJAX 或者用戶操作動(dòng)態(tài)的增加或者去除列表項(xiàng)元素,那么在每一次改變的時(shí)候都需要重新給新增的元素綁定事件,給即將刪去的元素解綁事件;
如果用了事件委托就沒(méi)有這種麻煩了,因?yàn)槭录墙壎ㄔ诟笇拥?,和目?biāo)元素的增減是沒(méi)有關(guān)系的,執(zhí)行到目標(biāo)元素是在真正響應(yīng)執(zhí)行事件函數(shù)的過(guò)程中去匹配的;
所以使用事件在動(dòng)態(tài)綁定事件的情況下是可以減少很多重復(fù)工作的。
88、不卡頓
如何在不卡住頁(yè)面的情況下渲染數(shù)據(jù),也就是說(shuō)不能一次性將幾萬(wàn)條 都渲染出來(lái),而應(yīng)該一次渲染部分 DOM,那么就可以通過(guò) requestAnimationFrame 來(lái) 每 16 ms 刷新一次。
<ul>控件</ul>
<script>
setTimeout(() => {
// 插入十萬(wàn)條數(shù)據(jù)
const total = 100000
// 一次插入 20 條,如果覺(jué)得性能不好就減少
const once = 20
// 渲染數(shù)據(jù)總共需要幾次
const loopCount = total / once
let countOfRender = 0
let ul = document.querySelector("ul");
function add() {
// 優(yōu)化性能,插入不會(huì)造成回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0);
</script>
89、JavaScript中的instanceof
JavaScript中變量的類型判斷常常使用typeof運(yùn)算符,但使用typeof時(shí)存在一個(gè)缺陷,就是判斷引用類型存儲(chǔ)值時(shí),無(wú)論引用的是什么類型的對(duì)象,它都返回 object。ECMAScript 引入了另一個(gè) Java 運(yùn)算符 instanceof 來(lái)解決這個(gè)問(wèn)題。instanceof 運(yùn)算符與 typeof 運(yùn)算符相似,用于識(shí)別正在處理的對(duì)象的類型。與 typeof 方法不同的是,instanceof 方法要求開(kāi)發(fā)者明確地確認(rèn)對(duì)象為某特定類型。
1.instanceof運(yùn)算符用法
var strObj = new String("字符串");
console.log(strObj instanceof String);// true
該段代碼判斷的是變量strObj是否為String對(duì)象的實(shí)例,strObj 是 String 對(duì)象的實(shí)例,因此是”true”。盡管不像 typeof 方法那樣靈活,但是在 typeof 方法返回 “object” 的情況下,instanceof 方法就很有用。
// 判斷 foo 是否是 Foo 類的實(shí)例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)
2.instanceof在繼承關(guān)系中使用
// 判斷 foo 是否是 Foo 類的實(shí)例 , 并且是否是其父類型的實(shí)例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo(); //JavaScript 原型繼承
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
foo作為構(gòu)造函數(shù)Foo的實(shí)例,因?yàn)闃?gòu)造函數(shù)Foo原型繼承了構(gòu)造函數(shù)Aoo,因此返回true。該代碼中是判斷了一層繼承關(guān)系中的父類,在多層繼承關(guān)系中,instanceof 運(yùn)算符同樣適用。
3.instanceof運(yùn)算符代碼
function instance_of(L, R) { //L 表示左表達(dá)式,R 表示右表達(dá)式
var O = R.prototype; // 取 R 的顯示原型
L = L.__proto__; // 取 L 的隱式原型
while (true) {
if (L === null)
return false;
if (O === L) // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true
return true;
L = L.__proto__;
}
}
90、forEach中的await
不知道你是否寫過(guò)類似的代碼:
function test() {
let arr = [3, 2, 1]
arr.forEach(async item => {
const res = await fetch(item)
console.log(res)
})
console.log('end')
}
function fetch(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 500 * x)
})
}
test()
我當(dāng)時(shí)期望的打印順序是
3
2
1
end
結(jié)果現(xiàn)實(shí)與我開(kāi)了個(gè)玩笑,打印順序居然是
end
1
2
3
為什么?
其實(shí)原因很簡(jiǎn)單,那就是 forEach 只支持同步代碼。
我們可以參考下 Polyfill 版本的 forEach,簡(jiǎn)化以后類似就是這樣的偽代碼
while (index < arr.length) {
callback(item, index) //也就是我們傳入的回調(diào)函數(shù)
}
從上述代碼中我們可以發(fā)現(xiàn),forEach 只是簡(jiǎn)單的執(zhí)行了下回調(diào)函數(shù)而已,并不會(huì)去處理異步的情況。并且你在 callback 中即使使用 break 也并不能結(jié)束遍歷。
怎么解決?
一般來(lái)說(shuō)解決的辦法有2種,for…of和for循環(huán)。
使用 Promise.all 的方式行不行,答案是: 不行
async function test() {
let arr = [3, 2, 1]
await Promise.all(
arr.map(async item => {
const res = await fetch(item)
console.log(res)
})
)
console.log('end')
}
可以看到并沒(méi)有按照我們期望的輸出。
這樣可以生效的原因是 async 函數(shù)肯定會(huì)返回一個(gè) Promise 對(duì)象,調(diào)用 map 以后返回值就是一個(gè)存放了 Promise 的數(shù)組了,這樣我們把數(shù)組傳入 Promise.all 中就可以解決問(wèn)題了。但是這種方式其實(shí)并不能達(dá)成我們要的效果,如果你希望內(nèi)部的 fetch 是順序完成的,可以選擇第二種方式。
第1種方法是使用 for…of
async function test() {
let arr = [3, 2, 1]
for (const item of arr) {
const res = await fetch(item)
console.log(res)
}
console.log('end')
}
這種方式相比 Promise.all 要簡(jiǎn)潔的多,并且也可以實(shí)現(xiàn)開(kāi)頭我想要的輸出順序。
但是這時(shí)候你是否又多了一個(gè)疑問(wèn)?為啥 for…of 內(nèi)部就能讓 await 生效呢。
因?yàn)?for…of 內(nèi)部處理的機(jī)制和 forEach 不同,forEach 是直接調(diào)用回調(diào)函數(shù),for…of 是通過(guò)迭代器的方式去遍歷。
async function test() {
let arr = [3, 2, 1]
const iterator = arr[Symbol.iterator]()
let res = iterator.next()
while (!res.done) {
const value = res.value
const res1 = await fetch(value)
console.log(res1)
res = iterator.next()
}
console.log('end')
}
第2種方法是使用 for循環(huán)
async function test() {
let arr = [3, 2, 1]
for (var i=0;i<arr.length;i++) {
const res = await fetch(arr[i])
console.log(res)
}
console.log('end')
}
function fetch(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 500 * x)
})
}
test()
第3種方法是使用 while循環(huán)
async function test() {
let arr = [3, 2, 1]
var i=0;
while(i!==arr.length){
const res = await fetch(arr[i])
console.log(res)
i++;
}
console.log('end')
}
function fetch(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 500 * x)
})
}
test()
要想在循環(huán)中使用async await,請(qǐng)使用for…of 或者 for 循環(huán), while循環(huán)
forEach支持async await
forEach 在正常情況像下面這么寫肯定是做不到同步的,程序不會(huì)等一個(gè)循環(huán)中的異步完成再進(jìn)行下一個(gè)循環(huán)。原因很明顯,在上面的模擬中,while 循環(huán)只是簡(jiǎn)單執(zhí)行了 callback,所以盡管 callback 內(nèi)使用了 await ,也只是影響到 callback 內(nèi)部。
arr.myforeach(async v => {
await fetch(v);
});
要支持上面這種寫法,只要稍微改一下就好
Array.prototype.myforeach = async function (fn, context = null) {
let index = 0;
let arr = this;
if (typeof fn !== 'function') {
throw new TypeError(fn + ' is not a function');
}
while (index < arr.length) {
if (index in arr) {
try {
await fn.call(context, arr[index], index, arr);
} catch (e) {
console.log(e);
}
}
index ++;
}
};
91、src和href
src和href都是用在外部資源的引入上,比如圖像,CSS文件,HTML文件,以及其他的web頁(yè)面等等,那么src和href的區(qū)別都有哪些呢?
1、請(qǐng)求資源類型不同
(1) href是Hypertext Reference的縮寫,表示超文本引用。用來(lái)建立當(dāng)前元素和文檔之間的鏈接。常用的有:link、a。
(2)在請(qǐng)求 src 資源時(shí)會(huì)將其指向的資源下載并應(yīng)用到文檔中,常用的有script,img 、iframe;
2、作用結(jié)果不同
(1)href 用于在當(dāng)前文檔和引用資源之間確立聯(lián)系;
(2)src 用于替換當(dāng)前內(nèi)容;
3、 瀏覽器解析方式不同
(1)若在文檔中添加href ,瀏覽器會(huì)識(shí)別該文檔為 CSS 文件,就會(huì)并行下載資源并且不會(huì)停止對(duì)當(dāng)前文檔的處理。這也是為什么建議使用 link 方式加載 CSS,而不是使用 @import 方式。
(2)當(dāng)瀏覽器解析到src ,會(huì)暫停其他資源的下載和處理,直到將該資源加載、編譯、執(zhí)行完畢,圖片和框架等也如此,類似于將所指向資源應(yīng)用到當(dāng)前內(nèi)容。這也是為什么建議把 js 腳本放在底部而不是頭部的原因。
92、JavaScript中事件綁定的方法
在JavaScript的學(xué)習(xí)中,我們經(jīng)常會(huì)遇到JavaScript的事件機(jī)制,例如,事件綁定、事件監(jiān)聽(tīng)、事件委托(事件代理)等。這些名詞是什么意思呢,有什么作用呢?
一、事件綁定
要想讓 JavaScript 對(duì)用戶的操作作出響應(yīng),首先要對(duì) DOM 元素綁定事件處理函數(shù)。所謂事件處理函數(shù),就是處理用戶操作的函數(shù),不同的操作對(duì)應(yīng)不同的名稱。
在JavaScript中,有三種常用的綁定事件的方法:
在DOM元素中直接綁定;
在JavaScript代碼中綁定;
綁定事件監(jiān)聽(tīng)函數(shù)。
1、在DOM中直接綁定事件
我們可以在DOM元素上綁定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等。好多不一一列出了。如果想知道更多事件類型請(qǐng)查看, DOM事件 。
<input type="button" value="click me" onclick="hello()">
<script>
function hello(){
alert("hello world!");
}
</script>
2、在JavaScript代碼中綁定事件
在 JS 代碼中(即 script 標(biāo)簽內(nèi))綁定事件可以使 JS 代碼與HTML標(biāo)簽分離,文檔結(jié)構(gòu)清晰,便于管理和開(kāi)發(fā)。
<input type="button" value="click me" id="btn">
<script>
document.getElementById("btn").onclick = function(){
alert("hello world!");
}
</script>
3、使用事件監(jiān)聽(tīng)綁定事件
綁定事件的另一種方法是用 addEventListener() 或 attachEvent() 來(lái)綁定事件監(jiān)聽(tīng)函數(shù)。下面詳細(xì)介紹,事件監(jiān)聽(tīng)。
1)事件監(jiān)聽(tīng)
關(guān)于事件監(jiān)聽(tīng),W3C規(guī)范中定義了3個(gè)事件階段,依次是捕獲階段、目標(biāo)階段、冒泡階段。
起初Netscape制定了JavaScript的一套事件驅(qū)動(dòng)機(jī)制(即事件捕獲)。隨即IE也推出了自己的一套事件驅(qū)動(dòng)機(jī)制(即事件冒泡)。最后W3C規(guī)范了兩種事件機(jī)制,分為捕獲階段、目標(biāo)階段、冒泡階段。IE8以前IE一直堅(jiān)持自己的事件機(jī)制(前端人員一直頭痛的兼容性問(wèn)題),IE9以后IE也支持了W3C規(guī)范。
W3C規(guī)范
element.addEventListener(event, function, useCapture)
event : (必需)事件名,支持所有 DOM事件 。
function:(必需)指定要事件觸發(fā)時(shí)執(zhí)行的函數(shù)。
useCapture:(可選)指定事件是否在捕獲或冒泡階段執(zhí)行。true,捕獲。false,冒泡。默認(rèn)false。
注:IE8以下不支持。
<input type="button" value="click me" id="btn1">
document.getElementById("btn1").addEventListener("click", hello);
function hello() {
alert("hello world!");
}
// IE標(biāo)準(zhǔn)
element.attachEvent(event, function)
// event:(必需)事件類型。需加“on“,例如:onclick。
// function:(必需)指定要事件觸發(fā)時(shí)執(zhí)行的函數(shù)。
<input type="button" value="click me" id="btn2" />
document.getElementById("btn2").attachEvent("onclick", hello);
function hello() {
alert("hello world!");
}
2)事件監(jiān)聽(tīng)的優(yōu)點(diǎn)
1、可以綁定多個(gè)事件;常規(guī)的事件綁定只執(zhí)行最后綁定的事件。
<input type="button" value="click me" id="btn3">
<input type="button" value="click me" id="btn4">
<script>
var btn3 = document.getElementById("btn3");
btn3.onclick = function () { //動(dòng)態(tài)綁定事件
alert("hello 1"); //不執(zhí)行
}
btn3.onclick = function () {
alert("hello 2"); //執(zhí)行
}
var btn4 = document.getElementById("btn4");
btn4.addEventListener("click", hello1); //添加事件監(jiān)聽(tīng)器
btn4.addEventListener("click", hello2);
function hello1() {
alert("hello 1"); //執(zhí)行
}
function hello2() {
alert("hello 2"); //執(zhí)行 (順序執(zhí)行)
}
</script>
2、可以解除相應(yīng)的綁定
<input type="button" value="click me" id="btn5">
<script>
var btn5 = document.getElementById("btn5");
btn5.addEventListener("click", hello1);//執(zhí)行了
btn5.addEventListener("click", hello2);//不執(zhí)行
btn5.removeEventListener("click", hello2);
function hello1() {
alert("hello 1");
}
function hello2() {
alert("hello 2");
}
</script>
3)封裝事件監(jiān)聽(tīng)
<input type="button" value="click me" id="btn5">
<script>
//綁定監(jiān)聽(tīng)事件
function addEventHandler(target, type, fn) {
if (target.addEventListener) {
target.addEventListener(type, fn);
} else {
target.attachEvent("on" + type, fn);
}
}
//移除監(jiān)聽(tīng)事件
function removeEventHandler(target, type, fn) {
if (target.removeEventListener) {
target.removeEventListener(type, fn);
} else {
target.detachEvent("on" + type, fn);
}
}
</script>
93、JavaScript偽數(shù)組
數(shù)組
定義: 數(shù)組是一種類列表對(duì)象,它的原型中提供了遍歷和修改元素的相關(guān)操作。JavaScript 數(shù)組的長(zhǎng)度和
元素類型都是非固定的。只能用整數(shù)作為數(shù)組元素的索引,而不能用字符串。對(duì)象是沒(méi)有索引的,是數(shù)
組的基本特征。
var obj = {};
var arr = [];
obj[2] = 'a';
arr[2] = 'a';
console.log(obj[2]); // => a
console.log(arr[2]); // => a
console.log(obj.length); // => undefined
console.log(arr.length); // => 3
obj[2]輸出’a’,是因?yàn)閷?duì)象就是普通的鍵值對(duì)存取數(shù)據(jù)
而arr[2]輸出’a’ 則不同,數(shù)組是通過(guò)索引來(lái)存取數(shù)據(jù),arr[2]之所以輸出’a’,是因?yàn)閿?shù)組arr索引2的位置已
經(jīng)存儲(chǔ)了數(shù)據(jù)
obj.length并不具有數(shù)組的特性,并且obj沒(méi)有保存屬性length,那么自然就會(huì)輸出undefined
而對(duì)于數(shù)組來(lái)說(shuō),length是數(shù)組的一個(gè)內(nèi)置屬性,數(shù)組會(huì)根據(jù)索引長(zhǎng)度來(lái)更改length的值
為什么arr.length輸出3,而不是1
在給數(shù)組添加元素時(shí),并沒(méi)有按照連續(xù)的索引添加,所以導(dǎo)致數(shù)組的索引不連續(xù),那么就導(dǎo)致索引長(zhǎng)度
大于元素個(gè)數(shù)
偽數(shù)組
定義:
偽數(shù)組是一個(gè)對(duì)象(Object),而真實(shí)的數(shù)組是一個(gè)數(shù)組(Array)
擁有l(wèi)ength屬性,且必須是number類型,其它屬性(索引)為字符串
不具有數(shù)組所具有的方法,forEach()等,不過(guò)有Object的方法
偽數(shù)組長(zhǎng)度不可變,真數(shù)組長(zhǎng)度可以變
可以通過(guò)for in遍歷
var fakeArray = {
length: 3,
"0": "first",
"1": "second",
"2": "third"
}
var arr = [1, 2, 3, 4]
// 真數(shù)組的方法來(lái)自Array.prototype
console.log(fakeArray instanceof Array) //false
console.log(arr instanceof Array) // true
Array.isArray(fakeArray) // false;
Array.isArray(arr) // true;
console.log(arr.__proto__ === Array.prototype) // true
console.log(fakeArray.__proto__ === Array.prototype) // false
console.log(fakeArray.__proto__ === Object.prototype) // true
arr.forEach(x => console.log(x)) // 1 2 3 4
fakeArray.forEach(x => console.log(x)) // fakeArray.forEach is not a function
Object.keys(fakeArray) // ["0", "1", "2", "length"]
常見(jiàn)的偽數(shù)組有:
函數(shù)內(nèi)部的 arguments
DOM 對(duì)象列表(比如通過(guò) document.getElementsByTags 得到的列表)
jQuery 對(duì)象(比如 $(“div”) )
偽數(shù)組是一個(gè) Object,而真實(shí)的數(shù)組是一個(gè) Array。
偽數(shù)組存在的意義,是可以讓普通的對(duì)象也能正常使用數(shù)組的很多方法,比如:
// 使用Array.prototype.slice.call();
var arr = Array.prototype.slice.call(arguments);
Array.prototype.forEach.call(arguments, function (v) {
// 循環(huán)arguments對(duì)象
});
// push
// some
// every
// filter
// map
// ...
// 使用[].slice.call()
var fakeArray = {
length: 3,
"0": "first",
"1": "second",
"2": "third"
}
var arr = [].slice.call(fakeArray)
console.log(arr) // ["first", "second", "third"]
// 使用ES6中的Array.from方法
var fakeArray = {
length: 3,
"0": "first",
"1": "second",
"2": "third"
}
var arr = Array.from(fakeArray)
console.log(arr) // ["first", "second", "third"]
// 使用擴(kuò)展運(yùn)算符,也是ES6的語(yǔ)法
var fakeArray = document.querySelectorAll('div')
var newArr = [...fakeArray]
console.log(newArr.__proto__ === Array.prototype) // true
// 偽數(shù)組轉(zhuǎn)換為真數(shù)組原理
Array.prototype.slice = function (start, end) {
start = start || 0
end = start || this.length
const arr = []
for (var i = start; i < end; i++) {
arr.push(this[i])
}
return arr
}
94、同源策略
何為同源?
域名、協(xié)議、端口完全一致即為同源。
www.juejin.com 和juejin.com
不同源,因?yàn)橛蛎煌?/p>
www.bilibili.tv和http://www.bilibili.com
不同源,因?yàn)橛蛎煌?/p>
http://localhost:3000 和 http://localhost:3001
不同源,因?yàn)槎丝诓煌?/p>
qq.com 和https://qq.com
不同源,因?yàn)閰f(xié)議不同
www.pixiv.net 和 www.pixiv.net/manage/illu…
同源,因?yàn)橛蛎?,協(xié)議,端口都相同
何為策略?
策略主要限制js的能力
1.無(wú)法讀取非同源的 cookie、Storage、indexDB的內(nèi)容
2.無(wú)法讀取非同源的DOM
3.無(wú)法發(fā)送非同源的AJAX,更加準(zhǔn)確的說(shuō)應(yīng)該是發(fā)送了請(qǐng)求但被瀏覽器攔截了。
為什么會(huì)有同源策略?
為了保護(hù)用戶數(shù)據(jù)安全
1.為了防止惡意網(wǎng)頁(yè)可以獲取其他網(wǎng)站的本地?cái)?shù)據(jù)。
2.為了防止惡意網(wǎng)站iframe其他網(wǎng)站的時(shí)候,獲取數(shù)據(jù)。
3.為了防止惡意網(wǎng)站在自已網(wǎng)站有訪問(wèn)其他網(wǎng)站的權(quán)利,以免通過(guò)cookie免登,拿到數(shù)據(jù)。
跨域問(wèn)題
前后端分離,和使用服務(wù)商數(shù)據(jù)時(shí),導(dǎo)致前端頁(yè)面地址和后端API不是同源的,例如前端地址為baidu.com,后端API
為api.baidu.com。直接訪問(wèn)API會(huì)觸發(fā)同源策略,所以需要想辦法跨過(guò)去。
常見(jiàn)的跨域方法的原理
1.CORS
? CORS(跨域資源共享)使用專用的HTTP頭,服務(wù)器(api.baidu.com)告訴瀏覽器,特定
URL(baidu.com)的ajax請(qǐng)求可以直接使用,不會(huì)激活同源策略。
2.JSONP
? 這個(gè)方案相當(dāng)于黑魔法,因?yàn)閖s調(diào)用(實(shí)際上是所有擁有src屬性的 <\script>、<\img>、<\iframe>)是
不會(huì)經(jīng)過(guò)同源策略,例如baidu.com引用了CDN的jquery。所以我通過(guò)調(diào)用js腳本的方式,從服務(wù)器上獲取JSON數(shù)據(jù)繞過(guò)同源策略。
3.nginx反向代理
? 當(dāng)你訪問(wèn)baidu.com/api/login的時(shí)候,通過(guò)在baidu.com的nginx服務(wù)器會(huì)識(shí)別你是api下的資源,會(huì)自動(dòng)代理到api.baidu.com/login,瀏覽器本身是不知道我實(shí)際上是訪問(wèn)的api.baidu.com的數(shù)據(jù),和前端資源同源,所以也就不會(huì)觸發(fā)瀏覽器的同源策略。
95、獲取第二大的數(shù)字
方法一
將數(shù)組從大到小排序然后找第二個(gè)
當(dāng)然在JS中有sort()方法可以進(jìn)行數(shù)組排序
var arr = [5, 2, 10, 8, 0, 4, 7, 11, 9, 1];
function array1() {
var max, min;
if (arr[0] < arr[1]) {
max = arr[1];
min = arr[0];
}
else {
max = arr[0];
min = arr[1];
}
for (i = 2; i < arr.length; i++) {
if (arr[i] > min) {
if (arr[i] > max) {
min = max;
max = arr[i];
}
else
min = arr[i];
}
}
alert(min);
}
array1();
方法二
定義兩個(gè)變量max min循環(huán)遍歷分別存儲(chǔ)當(dāng)前最大和第二大的數(shù)然后輸出第二大的數(shù)min;
var arr = [5, 2, 10, 8, 0, 4, 7, 11, 9, 1];
function array2() {
var temp, min;
for (var i = 0; i < arr.length - 1; i++) {
min = i;
for (var j = i + 1; j < arr.length; j++) {
if (arr[j] > arr[i]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
alert(arr[1]);
}
array2();
96、for in和Object.keys的區(qū)別
使用for in 去遍歷 對(duì)象會(huì)將prototype上面擴(kuò)展的方法或者屬性也打印出來(lái)
// 遞歸寫法
Object.prototype.clone = function () {
let o = this.constructor === Array ? [] : {};
for (let e in this) {
o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
}
return o;
}
let obj = {
a: 1,
b: {
c: 2
}
}
let obj2 = obj.clone();
console.log(obj2);
// { a: 1, b: { c: 2, clone: [Function] }, clone: [Function] }
解決方法可以為每一次的遍歷加上hasOwnProperty
hasOwnProperty具體的作用就是判斷該屬性是否屬于對(duì)象自身的屬性文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-721889.html
// 遞歸寫法
Object.prototype.clone = function () {
let o = this.constructor === Array ? [] : {};
for (let e in this) {
if (this.hasOwnProperty(e)) {
o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
}
}
return o;
}
let obj = {
a: 1,
b: {
c: 2
}
}
let obj2 = obj.clone();
console.log(obj2); // { a: 1, b: { c: 2 } }
也可以使用Object.keys()方式完成遍歷操作文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-721889.html
// 遞歸寫法
Object.prototype.clone = function () {
let o = this.constructor === Array ? [] : {};
Object.keys(this).forEach(item => {
o[item] = typeof this[item] === "object" ? this[item].clone() : this[item]
})
return o;
}
let obj = {
a: 1,
b: {
c: 2
}
}
let obj2 = obj.clone();
console.log(obj2);// { a: 1, b: { c: 2 } }
到了這里,關(guān)于JavaScript最新面試題合集(2023年)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!