1、深淺拷貝
(1) 定義
淺拷貝: 將原對(duì)象或原數(shù)組的引用直接賦給新對(duì)象读整,新數(shù)組蚀苛,新對(duì)象/數(shù)組只是原對(duì)象的一個(gè)引用
深拷貝: 創(chuàng)建一個(gè)新的對(duì)象和數(shù)組统阿,將原對(duì)象的各項(xiàng)屬性的“值”(數(shù)組的所有元素)拷貝過(guò)來(lái),是“值”而不是“引用”
(2) 淺拷貝
下面這段代碼就是淺拷貝姥卢,有時(shí)候我們只是想備份數(shù)組卷要,但是只是簡(jiǎn)單讓它賦給一個(gè)變量,改變其中一個(gè)独榴,另外一個(gè)就緊跟著改變僧叉,但很多時(shí)候這不是我們想要的
var obj = {
name:'wsscat',
age:0
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj);//Object {name: "wsscat", age: 0, c: 5}
console.log(obj2);////Object {name: "wsscat", age: 0, c: 5}
var arr1 = [1,2,3,4];
var arr2 = arr1;
arr2.push(5);
console.log(arr1); // [1,2,3,4,5]
console.log(arr2); // [1,2,3,4,5]
(3) 深拷貝(只拷貝第一層)
拷貝數(shù)組
// 使用slice實(shí)現(xiàn)
var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']
// 使用concat實(shí)現(xiàn)
var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.concat();
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']
// 使用擴(kuò)展運(yùn)算符...
var arr1 = [1,2,3,4];
var arr2 = [...arr1];
拷貝對(duì)象
// 遍歷屬性并賦給新對(duì)象
var obj = {
name:'wsscat',
age:0
}
var obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age
obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj2);//Object {name: "wsscat", age: 0}
// es6的Object.assign方法
// Object.assign:用于對(duì)象的合并,將源對(duì)象(source)的所有可枚舉屬性棺榔,復(fù)制到目標(biāo)對(duì)象(target)瓶堕,并返回合并后的target
var obj = {
name: '彭湖灣',
job: '學(xué)生'
}
var copyObj = Object.assign({}, obj);
// 擴(kuò)展運(yùn)算符
let obj1 = {a:2, b:3};
let obj2 = {...obj1};
(4) 深拷貝(拷貝所有層級(jí))
方法一: 封裝一個(gè)方法來(lái)處理對(duì)象的深拷貝,代碼如下:
var obj = {
name: 'wsscat',
age: 0
}
var deepCopy = function(source) {
var result = {};
for(var key in source) {
if(typeof source[key] === 'object') {
result[key] = deepCopy(source[key])
} else {
result[key] = source[key]
}
}
return result;
}
var obj3 = deepCopy(obj)
obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj3);//Object {name: "wsscat", age: 0}
方法二: 使用JSON
var obj = {
a:2,
b:3,
o: {
x: 100
}
}
var objStr = JSON.stringify(obj);
var obj2 = JSON.parse(objStr);
obj2.o.x = 1000;
consolo.log(obj2.o.x); // 1000
consolo.log(obj.o.x); // 100
2.typeof運(yùn)算符和instanceof運(yùn)算符以及isPrototypeOf()方法的區(qū)別
typeof是一個(gè)運(yùn)算符症歇,用于檢測(cè)數(shù)據(jù)的類型郎笆,比如基本數(shù)據(jù)類型null、undefined忘晤、string宛蚓、number、boolean设塔,以及引用數(shù)據(jù)類型object苍息、function,但是對(duì)于正則表達(dá)式、日期竞思、數(shù)組這些引用數(shù)據(jù)類型表谊,它會(huì)全部識(shí)別為object; instanceof同樣也是一個(gè)運(yùn)算符盖喷,它就能很好識(shí)別數(shù)據(jù)具體是哪一種引用類型爆办。它與isPrototypeOf的區(qū)別就是它是用來(lái)檢測(cè)構(gòu)造函數(shù)的原型是否存在于指定對(duì)象的原型鏈當(dāng)中;而isPrototypeOf是用來(lái)檢測(cè)調(diào)用此方法的對(duì)象是否存在于指定對(duì)象的原型鏈中课梳,所以本質(zhì)上就是檢測(cè)目標(biāo)不同距辆。
3.什么是事件代理/事件委托?
事件代理/事件委托是利用事件冒泡的特性暮刃,將本應(yīng)該綁定在多個(gè)元素上的事件綁定在他們的祖先元素上跨算,尤其在動(dòng)態(tài)添加子元素的時(shí)候,可以非常方便的提高程序性能椭懊,減小內(nèi)存空間诸蚕。
4.什么是事件冒泡?什么是事件捕獲氧猬?
冒泡型事件:事件按照從最特定的事件目標(biāo)到最不特定的事件目標(biāo)(document對(duì)象)的順序觸發(fā)背犯。
捕獲型事件:事件從最不精確的對(duì)象(document 對(duì)象)開(kāi)始觸發(fā),然后到最精確(也可以在窗口級(jí)別捕獲事件盅抚,不過(guò)必須由開(kāi)發(fā)人員特別指定)漠魏。
5.請(qǐng)指出document.onload和document.ready兩個(gè)事件的區(qū)別
頁(yè)面加載完成有兩種事件,一是ready妄均,表示文檔結(jié)構(gòu)已經(jīng)加載完成(不包含圖片等非文字媒體文件)柱锹,二是onload,指示頁(yè)面包含圖片等文件在內(nèi)的所有元素都加載完成丰包。
6.如何從瀏覽器的URL中獲取查詢字符串參數(shù)禁熏?
getUrlParam : function(name){
//baidu.com/product/list?keyword=XXX&page=1
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
var result = window.location.search.substr(1).match(reg);
return result ? decodeURIComponent(result[2]) : null;
}
7.什么是"use strict";?使用它的好處和壞處分別是什么?
在代碼中出現(xiàn)表達(dá)式-"use strict"; 意味著代碼按照嚴(yán)格模式解析烫沙,這種模式使得Javascript在更嚴(yán)格的條件下運(yùn)行匹层。
好處:
- 消除Javascript語(yǔ)法的一些不合理、不嚴(yán)謹(jǐn)之處锌蓄,減少一些怪異行為;
- 消除代碼運(yùn)行的一些不安全之處升筏,保證代碼運(yùn)行的安全;
- 提高編譯器效率瘸爽,增加運(yùn)行速度您访;
- 為未來(lái)新版本的Javascript做好鋪墊。
壞處:
- 同樣的代碼剪决,在"嚴(yán)格模式"中灵汪,可能會(huì)有不一樣的運(yùn)行結(jié)果檀训;
- 一些在"正常模式"下可以運(yùn)行的語(yǔ)句,在"嚴(yán)格模式"下將不能運(yùn)行享言。
8.請(qǐng)解釋JSONP的工作原理峻凫,以及它為什么不是真正的AJAX。
JSONP (JSON with Padding)是一個(gè)簡(jiǎn)單高效的跨域方式览露,HTML中的script標(biāo)簽可以加載并執(zhí)行其他域的javascript荧琼,于是我們可以通過(guò)script標(biāo)記來(lái)動(dòng)態(tài)加載其他域的資源。例如我要從域A的頁(yè)面pageA加載域B的數(shù)據(jù)差牛,那么在域B的頁(yè)面pageB中我以JavaScript的形式聲明pageA需要的數(shù)據(jù)命锄,然后在 pageA中用script標(biāo)簽把pageB加載進(jìn)來(lái),那么pageB中的腳本就會(huì)得以執(zhí)行偏化。JSONP在此基礎(chǔ)上加入了回調(diào)函數(shù)脐恩,pageB加載完之后會(huì)執(zhí)行pageA中定義的函數(shù),所需要的數(shù)據(jù)會(huì)以參數(shù)的形式傳遞給該函數(shù)侦讨。JSONP易于實(shí)現(xiàn)驶冒,但是也會(huì)存在一些安全隱患,如果第三方的腳本隨意地執(zhí)行搭伤,那么它就可以篡改頁(yè)面內(nèi)容只怎,截獲敏感數(shù)據(jù)袜瞬。但是在受信任的雙方傳遞數(shù)據(jù)怜俐,JSONP是非常合適的選擇。
AJAX是不跨域的邓尤,而JSONP是一個(gè)是跨域的拍鲤,還有就是二者接收參數(shù)形式不一樣!
9.防抖和節(jié)流
1??.如果用戶持續(xù)點(diǎn)擊一個(gè)按鈕汞扎,如何只提交一次請(qǐng)求季稳,且不影響后續(xù)使用?(其實(shí)就是如何節(jié)流這個(gè)真的問(wèn)的好多3浩恰>笆蟆!痹扇!)
何為節(jié)流 觸發(fā)函數(shù)事件后铛漓,短時(shí)間間隔內(nèi)無(wú)法連續(xù)調(diào)用,只有上一次函數(shù)執(zhí)行后鲫构,過(guò)了規(guī)定的時(shí)間間隔浓恶,才能進(jìn)行下一次的函數(shù)調(diào)用,一般用于http請(qǐng)求结笨。
解決原理 對(duì)處理函數(shù)進(jìn)行延時(shí)操作包晰,若設(shè)定的延時(shí)到來(lái)之前湿镀,再次觸發(fā)事件,則清除上一次的延時(shí)操作定時(shí)器伐憾,重新定時(shí)勉痴。
function conso(){
console.log('is run');
}
var btnUse=true;
$("#btn").click(function(){
if(btnUse){
conso();
btnUse=false;
}
setTimeout(function(){
btnUse=true;
},1500) //點(diǎn)擊后相隔多長(zhǎng)時(shí)間可執(zhí)行
})
復(fù)制代碼
2??.如何防抖?(一般都和節(jié)流一起問(wèn)树肃,一定要搞懂J赐取!)
何為防抖 多次觸發(fā)事件后扫外,事件處理函數(shù)只執(zhí)行一次莉钙,并且是在觸發(fā)操作結(jié)束時(shí)執(zhí)行,一般用于scroll事件筛谚。
解決原理 對(duì)處理函數(shù)進(jìn)行延時(shí)操作磁玉,若設(shè)定的延時(shí)到來(lái)之前再次觸發(fā)事件,則清除上一次的延時(shí)操作定時(shí)器驾讲,重新定時(shí)蚊伞。
let timer;
window.onscroll = function () {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(function () {
//滾動(dòng)條位置
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動(dòng)條位置:' + scrollTop);
timer = undefined;
},200)
}
復(fù)制代碼
或者是這樣:
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 處理函數(shù)
function handle() {
console.log(Math.random());
}
// 滾動(dòng)事件
window.addEventListener('scroll', debounce(handle, 1000));
10.數(shù)組去重的方法
1??:利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
2??:利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一個(gè)等同于第二個(gè)吮铭,splice方法刪除第二個(gè)
arr.splice(j,1);
j--;
}
}
}
return arr;
}
3??:利用對(duì)象的屬性不能相同的特點(diǎn)進(jìn)行去重(這種數(shù)組去重的方法有問(wèn)題时迫,不建議用,但是有人考)
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var arrry= [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
arrry.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return arrry;
}
4??:利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//當(dāng)前元素谓晌,在原始數(shù)組中的第一個(gè)索引==當(dāng)前索引值掠拳,否則返回當(dāng)前元素
return arr.indexOf(item, 0) === index;
});
}
5??:Map數(shù)據(jù)結(jié)構(gòu)去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 數(shù)組用于返回結(jié)果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有該key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果沒(méi)有該key值
array .push(arr[i]);
}
}
return array ;
}
11.數(shù)組的排序
1??:冒泡排序:思路:重復(fù)遍歷數(shù)組中的元素,依次比較兩個(gè)相鄰的元素纸肉,如果前一個(gè)元素大于后一個(gè)元素溺欧,就依靠第三個(gè)變量將它們換過(guò)來(lái),直到所有元素遍歷完柏肪。
function bubbleSort(arr){
for(let i = 0; i < arr.length - 1; i ++){
for(let j = 0; j < arr.length - 1 - i; j ++){
if(arr[j] > arr[j+1]){
let tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
2??:選擇排序:思路:每一次從數(shù)組中選出最小的一個(gè)元素姐刁,存放在數(shù)組的起始位置,然后烦味,再?gòu)氖S辔磁判虻臄?shù)組中繼續(xù)尋找最小元素聂使,然后放到已排序序列的末尾。直到全部數(shù)據(jù)元素排完谬俄。
function selectSort(arr){
let min = 0; // 用來(lái)保存數(shù)組中最小的數(shù)的索引值
for(let i = 0; i < arr.length - 1; i ++){
min = i;
for(let j = i + 1; j < arr.length; j ++){
if(arr[j] < arr[min]){
min = j;
}
}
if(min != i){
swap(arr,i,min);
}
}
console.log(arr);
};
function swap(arr,index1,index2){
let tem = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tem;
}
selectSort([7,5,1,2,6,4,8,3,2]); // output: [1, 2, 2, 3, 4, 5, 6, 7, 8]
3??:快速排序: 對(duì)冒泡排序的一種改進(jìn)柏靶。通過(guò)一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都比另一部分的所有數(shù)據(jù)小凤瘦,然后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序宿礁,整個(gè)排序過(guò)程可以遞歸進(jìn)行,以此達(dá)到整個(gè)數(shù)據(jù)變成有序序列蔬芥。
思路:
(1)找基準(zhǔn)(一般以中間項(xiàng)為基準(zhǔn))
(2)遍歷數(shù)組梆靖,小于基準(zhǔn)的放在 left控汉,大于基準(zhǔn)的放在 right
(3)遞歸
function quickSort(arr){
if(arr.length<=1){return arr;} //如果數(shù)組<=1,則直接返回
let pivotIndex = Math.floor(arr.length/2);
let pivot = arr.splice(pivotIndex,1)[0]; //找基準(zhǔn),并把基準(zhǔn)從原數(shù)組刪除
let left=[], right=[]; //定義左右數(shù)組
for(let i=0; i<arr.length; i++){ //比基準(zhǔn)小的放在left返吻,比基準(zhǔn)大的放在right
if(arr[i] <= pivot){
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot],quickSort(right)); //遞歸
}
12.繼承
第一種姑子,prototype的方式:
//父類
function person(){
this.hair = 'black';
this.eye = 'black';
this.skin = 'yellow';
this.view = function(){
return this.hair + ',' + this.eye + ',' + this.skin;
}
}
//子類
function man(){
this.feature = ['beard','strong'];
}
man.prototype = new person();
var one = new man();
這種方式最為簡(jiǎn)單,只需要讓子類的prototype屬性值賦值為被繼承的一個(gè)實(shí)例就行了测僵,之后就可以直接使用被繼承類的方法了街佑。
prototype 屬性是啥意思呢? prototype 即為原型捍靠,每一個(gè)對(duì)象 ( 由 function 定義出來(lái) ) 都有一個(gè)默認(rèn)的原型屬性沐旨,該屬性是個(gè)對(duì)象類型。
并且該默認(rèn)屬性用來(lái)實(shí)現(xiàn)鏈的向上攀查榨婆。意思就是說(shuō)磁携,如果某個(gè)對(duì)象的屬性不存在,那么將通過(guò)prototype屬性所屬對(duì)象來(lái)查找這個(gè)屬性良风。如果 prototype 查找不到呢谊迄?
js會(huì)自動(dòng)地找prototype的prototype屬性所屬對(duì)象來(lái)查找,這樣就通過(guò)prototype一直往上索引攀查烟央,直到查找到了該屬性或者prototype最后為空 (“undefined”);
例如上例中的one.view()方法统诺,js會(huì)先在one實(shí)例中查找是否有view()方法,因?yàn)闆](méi)有疑俭,所以查找man.prototype屬性粮呢,而prototype的值為person的一個(gè)實(shí)例,
該實(shí)例有view()方法怠硼,于是調(diào)用成功鬼贱。
第二種移怯,apply的方式:
//父類
function person(){
this.hair = 'black';
this.eye = 'black';
this.skin = 'yellow';
this.view = function(){
return this.hair + ',' + this.eye + ',' + this.skin;
}
}
//子類
function man(){
// person.apply(this,new Array());
person.apply(this,[]);
this.feature = ['beard','strong'];
}
第三種,call+prototype的方式:
/父類
function person(){
this.hair = 'black';
this.eye = 'black';
this.skin = 'yellow';
this.view = function(){
return this.hair + ',' + this.eye + ',' + this.skin;
}
}
//子類
function man(){
// person.apply(this,new Array());
person.call(this,[]);
this.feature = ['beard','strong'];
}
man.prototype = new person();
var one = new man();
13. for of , for in 和 forEach,map 的區(qū)別。
for...of循環(huán):具有 iterator 接口裂垦,就可以用for...of循環(huán)遍歷它的成員(屬性值)魔招。for...of循環(huán)可以使用的范圍包括數(shù)組、Set 和 Map 結(jié)構(gòu)嵌溢、某些類似數(shù)組的對(duì)象眯牧、Generator 對(duì)象,以及字符串赖草。for...of循環(huán)調(diào)用遍歷器接口学少,數(shù)組的遍歷器接口只返回具有數(shù)字索引的屬性。對(duì)于普通的對(duì)象秧骑,for...of結(jié)構(gòu)不能直接使用版确,會(huì)報(bào)錯(cuò)扣囊,必須部署了 Iterator 接口后才能使用∪蘖疲可以中斷循環(huán)侵歇。
for...in循環(huán):遍歷對(duì)象自身的和繼承的可枚舉的
屬性
, 不能直接獲取屬性值∠拍ⅲ可以中斷循環(huán)惕虑。
forEach: 只能遍歷數(shù)組,不能中斷磨镶,沒(méi)有返回值(或認(rèn)為返回值是undefined)溃蔫。
map: 只能遍歷數(shù)組,不能中斷琳猫,返回值是修改后的數(shù)組酒唉。
14.說(shuō)下ES6中的class
ES6 class 內(nèi)部所有定義的方法都是不可枚舉的;
ES6 class 必須使用 new 調(diào)用;
ES6 class 不存在變量提升;
ES6 class 默認(rèn)即是嚴(yán)格模式;
ES6 class 子類必須在父類的構(gòu)造函數(shù)中調(diào)用super(),這樣才有this對(duì)象;ES5中類繼承的關(guān)系是相反的沸移,先有子類的this痪伦,然后用父類的方法應(yīng)用在this上。
15.在JS中什么是變量提升雹锣?什么是暫時(shí)性死區(qū)网沾?
變量提升就是變量在聲明之前就可以使用,值為undefined蕊爵。
在代碼塊內(nèi)辉哥,使用 let/const 命令聲明變量之前,該變量都是不可用的(會(huì)拋出錯(cuò)誤)攒射。這在語(yǔ)法上醋旦,稱為“暫時(shí)性死區(qū)”。暫時(shí)性死區(qū)也意味著 typeof 不再是一個(gè)百分百安全的操作会放。
暫時(shí)性死區(qū)的本質(zhì)就是饲齐,只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了咧最,但是不可獲取捂人,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量矢沿。
16.什么是閉包滥搭?閉包的作用是什么?閉包有哪些使用場(chǎng)景捣鲸?
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)瑟匆,創(chuàng)建閉包最常用的方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。
閉包的作用有:
- 封裝私有變量
- 模仿塊級(jí)作用域(ES5中沒(méi)有塊級(jí)作用域)
- 實(shí)現(xiàn)JS的模塊
17.call栽惶、apply有什么區(qū)別愁溜?call,aplly和bind的內(nèi)部是如何實(shí)現(xiàn)的无午?
call 和 apply 的功能相同,區(qū)別在于傳參的方式不一樣:祝谚,apply的實(shí)現(xiàn)和call很類似宪迟,但是需要注意他們的參數(shù)是不一樣的,apply的第二個(gè)參數(shù)是數(shù)組或類數(shù)組.
bind 和 call/apply 有一個(gè)很重要的區(qū)別交惯,一個(gè)函數(shù)被 call/apply 的時(shí)候次泽,會(huì)直接調(diào)用,但是 bind 會(huì)創(chuàng)建一個(gè)新函數(shù)席爽。當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí)意荤,bind() 的第一個(gè)參數(shù)將作為它運(yùn)行時(shí)的 this,之后的一序列參數(shù)將會(huì)在傳遞的實(shí)參前傳入作為它的參數(shù)只锻。
18:new的原理是什么玖像?通過(guò)new的方式創(chuàng)建對(duì)象和通過(guò)字面量創(chuàng)建有什么區(qū)別?
- 創(chuàng)建一個(gè)新對(duì)象齐饮。
- 這個(gè)新對(duì)象會(huì)被執(zhí)行[[原型]]連接捐寥。
- 將構(gòu)造函數(shù)的作用域賦值給新對(duì)象,即this指向這個(gè)新對(duì)象.
- 如果函數(shù)沒(méi)有返回其他對(duì)象祖驱,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象握恳。
19。ES6新的特性有哪些捺僻?
新增了塊級(jí)作用域(let,const)
提供了定義類的語(yǔ)法糖(class)
新增了一種基本數(shù)據(jù)類型(Symbol)
新增了變量的解構(gòu)賦值
函數(shù)參數(shù)允許設(shè)置默認(rèn)值乡洼,引入了rest參數(shù),新增了箭頭函數(shù)
數(shù)組新增了一些API匕坯,如 isArray / from / of 方法;數(shù)組實(shí)例新增了 entries()束昵,keys() 和 values() 等方法
對(duì)象和數(shù)組新增了擴(kuò)展運(yùn)算符
ES6 新增了模塊化(import/export)
ES6 新增了 Set 和 Map 數(shù)據(jù)結(jié)構(gòu)
ES6 原生提供 Proxy 構(gòu)造函數(shù),用來(lái)生成 Proxy 實(shí)例
ES6 新增了生成器(Generator)和遍歷器(Iterator)
20葛峻。setTimeout倒計(jì)時(shí)為什么會(huì)出現(xiàn)誤差锹雏?
setTimeout() 只是將事件插入了“任務(wù)隊(duì)列”,必須等當(dāng)前代碼(執(zhí)行棧)執(zhí)行完泞歉,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)逼侦。要是當(dāng)前代碼消耗時(shí)間很長(zhǎng),也有可能要等很久腰耙,所以并沒(méi)辦法保證回調(diào)函數(shù)一定會(huì)在 setTimeout() 指定的時(shí)間執(zhí)行。所以铲球, setTimeout() 的第二個(gè)參數(shù)表示的是最少時(shí)間挺庞,并非是確切時(shí)間。
HTML5標(biāo)準(zhǔn)規(guī)定了 setTimeout() 的第二個(gè)參數(shù)的最小值不得小于4毫秒稼病,如果低于這個(gè)值选侨,則默認(rèn)是4毫秒掖鱼。在此之前。老版本的瀏覽器都將最短時(shí)間設(shè)為10毫秒援制。另外戏挡,對(duì)于那些DOM的變動(dòng)(尤其是涉及頁(yè)面重新渲染的部分),通常是間隔16毫秒執(zhí)行晨仑。這時(shí)使用 requestAnimationFrame() 的效果要好于 setTimeout();
21.為什么 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是因?yàn)樵谶M(jìn)制轉(zhuǎn)換和進(jìn)階運(yùn)算的過(guò)程中出現(xiàn)精度損失褐墅。
下面是詳細(xì)解釋:
JavaScript使用 Number 類型表示數(shù)字(整數(shù)和浮點(diǎn)數(shù)),使用64位表示一個(gè)數(shù)字洪己。
[圖片上傳失敗...(image-bc1b91-1574390988740)]
圖片說(shuō)明:
- 第0位:符號(hào)位妥凳,0表示正數(shù),1表示負(fù)數(shù)(s)
- 第1位到第11位:儲(chǔ)存指數(shù)部分(e)
- 第12位到第63位:儲(chǔ)存小數(shù)部分(即有效數(shù)字)f
計(jì)算機(jī)無(wú)法直接對(duì)十進(jìn)制的數(shù)字進(jìn)行運(yùn)算, 需要先對(duì)照 IEEE 754 規(guī)范轉(zhuǎn)換成二進(jìn)制答捕,然后對(duì)階運(yùn)算逝钥。
1.進(jìn)制轉(zhuǎn)換
0.1和0.2轉(zhuǎn)換成二進(jìn)制后會(huì)無(wú)限循環(huán)
0.1 -> 0.0001100110011001...(無(wú)限循環(huán))
0.2 -> 0.0011001100110011...(無(wú)限循環(huán))
復(fù)制代碼復(fù)制代碼
但是由于IEEE 754尾數(shù)位數(shù)限制,需要將后面多余的位截掉拱镐,這樣在進(jìn)制之間的轉(zhuǎn)換中精度已經(jīng)損失艘款。
2.對(duì)階運(yùn)算
由于指數(shù)位數(shù)不相同,運(yùn)算時(shí)需要對(duì)階運(yùn)算 這部分也可能產(chǎn)生精度損失沃琅。
按照上面兩步運(yùn)算(包括兩步的精度損失)磷箕,最后的結(jié)果是
0.0100110011001100110011001100110011001100110011001100
結(jié)果轉(zhuǎn)換成十進(jìn)制之后就是 0.30000000000000004。
22.Promise和setTimeout的區(qū)別 ?
Promise 是微任務(wù)阵难,setTimeout 是宏任務(wù)岳枷,同一個(gè)事件循環(huán)中,promise.then總是先于 setTimeout 執(zhí)行呜叫。
23.如何實(shí)現(xiàn) Promise.all ?
要實(shí)現(xiàn) Promise.all,首先我們需要知道 Promise.all 的功能:
- 如果傳入的參數(shù)是一個(gè)空的可迭代對(duì)象空繁,那么此promise對(duì)象回調(diào)完成(resolve),只有此情況,是同步執(zhí)行的朱庆,其它都是異步返回的盛泡。
- 如果傳入的參數(shù)不包含任何 promise,則返回一個(gè)異步完成. promises 中所有的promise都“完成”時(shí)或參數(shù)中不包含 promise 時(shí)回調(diào)完成娱颊。
- 如果參數(shù)中有一個(gè)promise失敗傲诵,那么Promise.all返回的promise對(duì)象失敗
- 在任何情況下,Promise.all 返回的 promise 的完成狀態(tài)的結(jié)果都是一個(gè)數(shù)組
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then((data) => {
processValue(i, data);
}, (err) => {
reject(err);
return;
});
}
}
});
}
24.如何實(shí)現(xiàn) Promise.finally ?
不管成功還是失敗箱硕,都會(huì)走到finally中,并且finally之后拴竹,還可以繼續(xù)then。并且會(huì)將值原封不動(dòng)的傳遞給后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
25.什么是函數(shù)柯里化剧罩?實(shí)現(xiàn) sum(1)(2)(3) 返回結(jié)果是1,2,3之和
函數(shù)柯里化是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)栓拜,并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。
function sum(a) {
return function(b) {
return function(c) {
return a+b+c;
}
}
}
console.log(sum(1)(2)(3)); // 6
26.談?wù)剬?duì) async/await 的理解,async/await 的實(shí)現(xiàn)原理是什么?
async/await 就是 Generator 的語(yǔ)法糖幕与,使得異步操作變得更加方便挑势。來(lái)張圖對(duì)比一下:
[圖片上傳失敗...(image-fc5dc6-1574390988740)]
async 函數(shù)就是將 Generator 函數(shù)的星號(hào)(*)替換成 async,將 yield 替換成await啦鸣。
我們說(shuō) async 是 Generator 的語(yǔ)法糖潮饱,那么這個(gè)糖究竟甜在哪呢?
1)async函數(shù)內(nèi)置執(zhí)行器诫给,函數(shù)調(diào)用之后香拉,會(huì)自動(dòng)執(zhí)行,輸出最后結(jié)果蝙搔。而Generator需要調(diào)用next或者配合co模塊使用缕溉。
2)更好的語(yǔ)義,async和await吃型,比起星號(hào)和yield证鸥,語(yǔ)義更清楚了。async表示函數(shù)里有異步操作勤晚,await表示緊跟在后面的表達(dá)式需要等待結(jié)果枉层。
3)更廣的適用性。co模塊約定赐写,yield命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象鸟蜡,而async 函數(shù)的 await 命令后面,可以是 Promise 對(duì)象和原始類型的值挺邀。
4)返回值是Promise揉忘,async函數(shù)的返回值是 Promise 對(duì)象,Generator的返回值是 Iterator端铛,Promise 對(duì)象使用起來(lái)更加方便泣矛。
async 函數(shù)的實(shí)現(xiàn)原理,就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器禾蚕,包裝在一個(gè)函數(shù)里您朽。
function my_co(it) {
return new Promise((resolve, reject) => {
function next(data) {
try {
var { value, done } = it.next(data);
}catch(e){
return reject(e);
}
if (!done) {
//done為true,表示迭代完成
//value 不一定是 Promise,可能是一個(gè)普通值换淆。使用 Promise.resolve 進(jìn)行包裝哗总。
Promise.resolve(value).then(val => {
next(val);
}, reject);
} else {
resolve(value);
}
}
next(); //執(zhí)行一次next
});
}
function* test() {
yield new Promise((resolve, reject) => {
setTimeout(resolve, 100);
});
yield new Promise((resolve, reject) => {
// throw Error(1);
resolve(10)
});
yield 10;
return 1000;
}
my_co(test()).then(data => {
console.log(data); //輸出1000
}).catch((err) => {
console.log('err: ', err);
});
27.requestAnimationFrame 和 setTimeout/setInterval 有什么區(qū)別?使用 requestAnimationFrame 有哪些好處倍试?
在 requestAnimationFrame 之前讯屈,我們主要使用 setTimeout/setInterval 來(lái)編寫JS動(dòng)畫。
編寫動(dòng)畫的關(guān)鍵是循環(huán)間隔的設(shè)置易猫,一方面耻煤,循環(huán)間隔足夠短具壮,動(dòng)畫效果才能顯得平滑流暢准颓;另一方面哈蝇,循環(huán)間隔還要足夠長(zhǎng),才能確保瀏覽器有能力渲染產(chǎn)生的變化攘已。
大部分的電腦顯示器的刷新頻率是60HZ炮赦,也就是每秒鐘重繪60次。大多數(shù)瀏覽器都會(huì)對(duì)重繪操作加以限制样勃,不超過(guò)顯示器的重繪頻率吠勘,因?yàn)榧词钩^(guò)那個(gè)頻率用戶體驗(yàn)也不會(huì)提升。因此峡眶,最平滑動(dòng)畫的最佳循環(huán)間隔是 1000ms / 60 剧防,約為16.7ms。
setTimeout/setInterval 有一個(gè)顯著的缺陷在于時(shí)間是不精確的辫樱,setTimeout/setInterval 只能保證延時(shí)或間隔不小于設(shè)定的時(shí)間峭拘。因?yàn)樗鼈儗?shí)際上只是把任務(wù)添加到了任務(wù)隊(duì)列中,但是如果前面的任務(wù)還沒(méi)有執(zhí)行完成狮暑,它們必須要等待鸡挠。
requestAnimationFrame 才有的是系統(tǒng)時(shí)間間隔,保持最佳繪制效率搬男,不會(huì)因?yàn)殚g隔時(shí)間過(guò)短拣展,造成過(guò)度繪制,增加開(kāi)銷缔逛;也不會(huì)因?yàn)殚g隔時(shí)間太長(zhǎng)备埃,使用動(dòng)畫卡頓不流暢,讓各種網(wǎng)頁(yè)動(dòng)畫效果能夠有一個(gè)統(tǒng)一的刷新機(jī)制褐奴,從而節(jié)省系統(tǒng)資源按脚,提高系統(tǒng)性能,改善視覺(jué)效果歉糜。
綜上所述乘寒,requestAnimationFrame 和 setTimeout/setInterval 在編寫動(dòng)畫時(shí)相比,優(yōu)點(diǎn)如下:
1.requestAnimationFrame 不需要設(shè)置時(shí)間匪补,采用系統(tǒng)時(shí)間間隔伞辛,能達(dá)到最佳的動(dòng)畫效果。
2.requestAnimationFrame 會(huì)把每一幀中的所有DOM操作集中起來(lái)夯缺,在一次重繪或回流中就完成蚤氏。
3.當(dāng) requestAnimationFrame() 運(yùn)行在后臺(tái)標(biāo)簽頁(yè)或者隱藏的 <iframe>
里時(shí),requestAnimationFrame() 會(huì)被暫停調(diào)用以提升性能和電池壽命(大多數(shù)瀏覽器中)踊兜。
28.簡(jiǎn)述下對(duì) webWorker 的理解竿滨?
HTML5則提出了 Web Worker 標(biāo)準(zhǔn),表示js允許多線程,但是子線程完全受主線程控制并且不能操作dom于游,只有主線程可以操作dom毁葱,所以js本質(zhì)上依然是單線程語(yǔ)言。
web worker就是在js單線程執(zhí)行的基礎(chǔ)上開(kāi)啟一個(gè)子線程贰剥,進(jìn)行程序處理倾剿,而不影響主線程的執(zhí)行,當(dāng)子線程執(zhí)行完之后再回到主線程上蚌成,在這個(gè)過(guò)程中不影響主線程的執(zhí)行前痘。子線程與主線程之間提供了數(shù)據(jù)交互的接口postMessage和onmessage,來(lái)進(jìn)行數(shù)據(jù)發(fā)送和接收担忧。
var worker = new Worker('./worker.js'); //創(chuàng)建一個(gè)子線程
worker.postMessage('Hello');
worker.onmessage = function (e) {
console.log(e.data); //Hi
worker.terminate(); //結(jié)束線程
};復(fù)制代碼
//worker.js
onmessage = function (e) {
console.log(e.data); //Hello
postMessage("Hi"); //向主進(jìn)程發(fā)送消息
};復(fù)制代碼
僅是最簡(jiǎn)示例代碼芹缔,項(xiàng)目中通常是將一些耗時(shí)較長(zhǎng)的代碼,放在子線程中運(yùn)行瓶盛。
29.跨域的方法有哪些最欠?原理是什么?
知其然知其所以然蓬网,在說(shuō)跨域方法之前窒所,我們先了解下什么叫跨域,瀏覽器有同源策略帆锋,只有當(dāng)“協(xié)議”吵取、“域名”、“端口號(hào)”都相同時(shí)锯厢,才能稱之為是同源皮官,其中有一個(gè)不同,即是跨域实辑。
那么同源策略的作用是什么呢捺氢?同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制剪撬。
那么我們又為什么需要跨域呢摄乒?一是前端和服務(wù)器分開(kāi)部署,接口請(qǐng)求需要跨域残黑,二是我們可能會(huì)加載其它網(wǎng)站的頁(yè)面作為iframe內(nèi)嵌馍佑。
跨域的方法有哪些?
常用的跨域方法
- jsonp
盡管瀏覽器有同源策略梨水,但是 <script>
標(biāo)簽的 src 屬性不會(huì)被同源策略所約束拭荤,可以獲取任意服務(wù)器上的腳本并執(zhí)行。jsonp 通過(guò)插入script標(biāo)簽的方式來(lái)實(shí)現(xiàn)跨域疫诽,參數(shù)只能通過(guò)url傳入舅世,僅能支持get請(qǐng)求旦委。
實(shí)現(xiàn)原理:
Step1: 創(chuàng)建 callback 方法
Step2: 插入 script 標(biāo)簽
Step3: 后臺(tái)接受到請(qǐng)求,解析前端傳過(guò)去的 callback 方法雏亚,返回該方法的調(diào)用缨硝,并且數(shù)據(jù)作為參數(shù)傳入該方法
Step4: 前端執(zhí)行服務(wù)端返回的方法調(diào)用
下面代碼僅為說(shuō)明 jsonp 原理,項(xiàng)目中請(qǐng)使用成熟的庫(kù)评凝。分別看一下前端和服務(wù)端的簡(jiǎn)單實(shí)現(xiàn):
//前端代碼
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
//創(chuàng)建script標(biāo)簽
let script = document.createElement('script');
//將回調(diào)函數(shù)掛在 window 上
window[cb] = function(data) {
resolve(data);
//代碼執(zhí)行后追葡,刪除插入的script標(biāo)簽
document.body.removeChild(script);
}
//回調(diào)函數(shù)加在請(qǐng)求地址上
params = {...params, cb} //wb=b&cb=show
let arrs = [];
for(let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
});
}
//使用
function sayHi(data) {
console.log(data);
}
jsonp({
url: 'http://localhost:3000/say',
params: {
//code
},
cb: 'sayHi'
}).then(data => {
console.log(data);
});復(fù)制代碼
//express啟動(dòng)一個(gè)后臺(tái)服務(wù)
let express = require('express');
let app = express();
app.get('/say', (req, res) => {
let {cb} = req.query; //獲取傳來(lái)的callback函數(shù)名腺律,cb是key
res.send(`${cb}('Hello!')`);
});
app.listen(3000);復(fù)制代碼
從今天起奕短,jsonp的原理就要了然于心啦~
- cors
jsonp 只能支持 get 請(qǐng)求,cors 可以支持多種請(qǐng)求匀钧。cors 并不需要前端做什么工作翎碑。
簡(jiǎn)單跨域請(qǐng)求:
只要服務(wù)器設(shè)置的Access-Control-Allow-Origin Header和請(qǐng)求來(lái)源匹配,瀏覽器就允許跨域
- 請(qǐng)求的方法是get之斯,head或者post日杈。
- Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一個(gè)值,或者不設(shè)置也可以佑刷,一般默認(rèn)就是application/x-www-form-urlencoded莉擒。
- 請(qǐng)求中沒(méi)有自定義的HTTP頭部,如x-token瘫絮。(應(yīng)該是這幾種頭部 Accept涨冀,Accept-Language,Content-Language麦萤,Last-Event-ID鹿鳖,Content-Type)
//簡(jiǎn)單跨域請(qǐng)求
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'XXXX');
});復(fù)制代碼
帶預(yù)檢(Preflighted)的跨域請(qǐng)求
不滿于簡(jiǎn)單跨域請(qǐng)求的,即是帶預(yù)檢的跨域請(qǐng)求壮莹。服務(wù)端需要設(shè)置 Access-Control-Allow-Origin (允許跨域資源請(qǐng)求的域) 翅帜、 Access-Control-Allow-Methods (允許的請(qǐng)求方法) 和 Access-Control-Allow-Headers (允許的請(qǐng)求頭)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'XXX');
res.setHeader('Access-Control-Allow-Headers', 'XXX'); //允許返回的頭
res.setHeader('Access-Control-Allow-Methods', 'XXX');//允許使用put方法請(qǐng)求接口
res.setHeader('Access-Control-Max-Age', 6); //預(yù)檢的存活時(shí)間
if(req.method === "OPTIONS") {
res.end(); //如果method是OPTIONS,不做處理
}
});復(fù)制代碼
更多CORS的知識(shí)可以訪問(wèn): HTTP訪問(wèn)控制(CORS)
- nginx 反向代理
使用nginx反向代理實(shí)現(xiàn)跨域命满,只需要修改nginx的配置即可解決跨域問(wèn)題涝滴。
A網(wǎng)站向B網(wǎng)站請(qǐng)求某個(gè)接口時(shí),向B網(wǎng)站發(fā)送一個(gè)請(qǐng)求胶台,nginx根據(jù)配置文件接收這個(gè)請(qǐng)求歼疮,代替A網(wǎng)站向B網(wǎng)站來(lái)請(qǐng)求。 nginx拿到這個(gè)資源后再返回給A網(wǎng)站概作,以此來(lái)解決了跨域問(wèn)題腋妙。
例如nginx的端口號(hào)為 8090,需要請(qǐng)求的服務(wù)器端口號(hào)為 3000讯榕。(localhost:8090 請(qǐng)求 localhost:3000/say)
nginx配置如下:
server {
listen 8090;
server_name localhost;
location / {
root /Users/liuyan35/Test/Study/CORS/1-jsonp;
index index.html index.htm;
}
location /say {
rewrite ^/say/(.*)$ /$1 break;
proxy_pass http://localhost:3000;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
## others
}復(fù)制代碼
- websocket
Websocket 是 HTML5 的一個(gè)持久化的協(xié)議骤素,它實(shí)現(xiàn)了瀏覽器與服務(wù)器的全雙工通信匙睹,同時(shí)也是跨域的一種解決方案。
Websocket 不受同源策略影響济竹,只要服務(wù)器端支持痕檬,無(wú)需任何配置就支持跨域。
前端頁(yè)面在 8080 的端口送浊。
let socket = new WebSocket('ws://localhost:3000'); //協(xié)議是ws
socket.onopen = function() {
socket.send('Hi,你好');
}
socket.onmessage = function(e) {
console.log(e.data)
}復(fù)制代碼
服務(wù)端 3000端口梦谜。可以看出websocket無(wú)需做跨域配置袭景。
let WebSocket = require('ws');
let wss = new WebSocket.Server({port: 3000});
wss.on('connection', function(ws) {
ws.on('message', function(data) {
console.log(data); //接受到頁(yè)面發(fā)來(lái)的消息'Hi,你好'
ws.send('Hi'); //向頁(yè)面發(fā)送消息
});
});復(fù)制代碼
- postMessage
postMessage 通過(guò)用作前端頁(yè)面之前的跨域唁桩,如父頁(yè)面與iframe頁(yè)面的跨域。window.postMessage方法耸棒,允許跨窗口通信荒澡,不論這兩個(gè)窗口是否同源。
話說(shuō)工作中兩個(gè)頁(yè)面之前需要通信的情況并不多与殃,我本人工作中单山,僅使用過(guò)兩次,一次是H5頁(yè)面中發(fā)送postMessage信息幅疼,ReactNative的webview中接收此此消息米奸,并作出相應(yīng)處理。另一次是可輪播的頁(yè)面爽篷,某個(gè)輪播頁(yè)使用的是iframe頁(yè)面悴晰,為了解決滑動(dòng)的事件沖突,iframe頁(yè)面中去監(jiān)聽(tīng)手勢(shì)狼忱,發(fā)送消息告訴父頁(yè)面是否左滑和右滑膨疏。
子頁(yè)面向父頁(yè)面發(fā)消息
父頁(yè)面
window.addEventListener('message', (e) => {
this.props.movePage(e.data);
}, false);復(fù)制代碼
子頁(yè)面(iframe):
if(/*左滑*/) {
window.parent && window.parent.postMessage(-1, '*')
}else if(/*右滑*/){
window.parent && window.parent.postMessage(1, '*')
}復(fù)制代碼
父頁(yè)面向子頁(yè)面發(fā)消息
父頁(yè)面:
let iframe = document.querySelector('#iframe');
iframe.onload = function() {
iframe.contentWindow.postMessage('hello', 'http://localhost:3002');
}復(fù)制代碼
子頁(yè)面:
window.addEventListener('message', function(e) {
console.log(e.data);
e.source.postMessage('Hi', e.origin); //回消息
});復(fù)制代碼
- node 中間件
node 中間件的跨域原理和nginx代理跨域,同源策略是瀏覽器的限制钻弄,服務(wù)端沒(méi)有同源策略佃却。
node中間件實(shí)現(xiàn)跨域的原理如下:
1.接受客戶端請(qǐng)求
2.將請(qǐng)求 轉(zhuǎn)發(fā)給服務(wù)器。
3.拿到服務(wù)器 響應(yīng) 數(shù)據(jù)窘俺。
4.將 響應(yīng) 轉(zhuǎn)發(fā)給客戶端饲帅。
不常用跨域方法
以下三種跨域方式很少用,如有興趣瘤泪,可自行查閱相關(guān)資料灶泵。
- window.name + iframe
- location.hash + iframe
- document.domain (主域需相同)
30 js異步加載的方式有哪些?
-
<script>
的 defer 屬性对途,HTML4 中新增 -
<script>
的 async 屬性赦邻,HTML5 中新增
<script>
標(biāo)簽打開(kāi)defer屬性,腳本就會(huì)異步加載实檀。渲染引擎遇到這一行命令惶洲,就會(huì)開(kāi)始下載外部腳本按声,但不會(huì)等它下載和執(zhí)行,而是直接執(zhí)行后面的命令恬吕。
defer 和 async 的區(qū)別在于: defer要等到整個(gè)頁(yè)面在內(nèi)存中正常渲染結(jié)束签则,才會(huì)執(zhí)行;
async一旦下載完铐料,渲染引擎就會(huì)中斷渲染渐裂,執(zhí)行這個(gè)腳本以后,再繼續(xù)渲染钠惩。defer是“渲染完再執(zhí)行”柒凉,async是“下載完就執(zhí)行”。
如果有多個(gè) defer 腳本妻柒,會(huì)按照它們?cè)陧?yè)面出現(xiàn)的順序加載扛拨。
多個(gè)async腳本是不能保證加載順序的。
31.實(shí)現(xiàn)雙向綁定 Proxy 與 Object.defineProperty 相比優(yōu)劣如何?
- Object.definedProperty 的作用是劫持一個(gè)對(duì)象的屬性举塔,劫持屬性的getter和setter方法,在對(duì)象的屬性發(fā)生變化時(shí)進(jìn)行特定的操作求泰。而 Proxy 劫持的是整個(gè)對(duì)象央渣。
- Proxy 會(huì)返回一個(gè)代理對(duì)象,我們只需要操作新對(duì)象即可渴频,而
Object.defineProperty
只能遍歷對(duì)象屬性直接修改芽丹。 - Object.definedProperty 不支持?jǐn)?shù)組,更準(zhǔn)確的說(shuō)是不支持?jǐn)?shù)組的各種API卜朗,因?yàn)槿绻麅H僅考慮arry[i] = value 這種情況拔第,是可以劫持的,但是這種劫持意義不大场钉。而 Proxy 可以支持?jǐn)?shù)組的各種API蚊俺。
- 盡管 Object.defineProperty 有諸多缺陷,但是其兼容性要好于 Proxy.
PS: Vue2.x 使用 Object.defineProperty 實(shí)現(xiàn)數(shù)據(jù)雙向綁定逛万,V3.0 則使用了 Proxy.
//攔截器
let obj = {};
let temp = 'Yvette';
Object.defineProperty(obj, 'name', {
get() {
console.log("讀取成功");
return temp
},
set(value) {
console.log("設(shè)置成功");
temp = value;
}
});
obj.name = 'Chris';
console.log(obj.name);復(fù)制代碼
PS: Object.defineProperty 定義出來(lái)的屬性泳猬,默認(rèn)是不可枚舉,不可更改棕兼,不可配置【無(wú)法delete】
我們可以看到 Proxy 會(huì)劫持整個(gè)對(duì)象曲掰,讀取對(duì)象中的屬性或者是修改屬性值汽久,那么就會(huì)被劫持。但是有點(diǎn)需要注意忙上,復(fù)雜數(shù)據(jù)類型,監(jiān)控的是引用地址闲坎,而不是值疫粥,如果引用地址沒(méi)有改變洋腮,那么不會(huì)觸發(fā)set。
let obj = {name: 'Yvette', hobbits: ['travel', 'reading'], info: {
age: 20,
job: 'engineer'
}};
let p = new Proxy(obj, {
get(target, key) { //第三個(gè)參數(shù)是 proxy手形, 一般不使用
console.log('讀取成功');
return Reflect.get(target, key);
},
set(target, key, value) {
if(key === 'length') return true; //如果是數(shù)組長(zhǎng)度的變化啥供,返回。
console.log('設(shè)置成功');
return Reflect.set([target, key, value]);
}
});
p.name = 20; //設(shè)置成功
p.age = 20; //設(shè)置成功; 不需要事先定義此屬性
p.hobbits.push('photography'); //讀取成功;注意不會(huì)觸發(fā)設(shè)置成功
p.info.age = 18; //讀取成功;不會(huì)觸發(fā)設(shè)置成功復(fù)制代碼
最后库糠,我們?cè)倏聪聦?duì)于數(shù)組的劫持伙狐,Object.definedProperty 和 Proxy 的差別
Object.definedProperty 可以將數(shù)組的索引作為屬性進(jìn)行劫持,但是僅支持直接對(duì) arry[i] 進(jìn)行操作瞬欧,不支持?jǐn)?shù)組的API贷屎,非常雞肋。
let arry = []
Object.defineProperty(arry, '0', {
get() {
console.log("讀取成功");
return temp
},
set(value) {
console.log("設(shè)置成功");
temp = value;
}
});
arry[0] = 10; //觸發(fā)設(shè)置成功
arry.push(10); //不能被劫持復(fù)制代碼
Proxy 可以監(jiān)聽(tīng)到數(shù)組的變化艘虎,支持各種API唉侄。注意數(shù)組的變化觸發(fā)get和set可能不止一次,如有需要野建,自行根據(jù)key值決定是否要進(jìn)行處理属划。
let hobbits = ['travel', 'reading'];
let p = new Proxy(hobbits, {
get(target, key) {
// if(key === 'length') return true; //如果是數(shù)組長(zhǎng)度的變化,返回候生。
console.log('讀取成功');
return Reflect.get(target, key);
},
set(target, key, value) {
// if(key === 'length') return true; //如果是數(shù)組長(zhǎng)度的變化同眯,返回。
console.log('設(shè)置成功');
return Reflect.set([target, key, value]);
}
});
p.splice(0,1) //觸發(fā)get和set唯鸭,可以被劫持
p.push('photography');//觸發(fā)get和set
p.slice(1); //觸發(fā)get须蜗;因?yàn)?slice 是不會(huì)修改原數(shù)組的
32.Object.is() 與比較操作符 ===、== 有什么區(qū)別目溉?
以下情況明肮,Object.is認(rèn)為是相等
兩個(gè)值都是 undefined
兩個(gè)值都是 null
兩個(gè)值都是 true 或者都是 false
兩個(gè)值是由相同個(gè)數(shù)的字符按照相同的順序組成的字符串
兩個(gè)值指向同一個(gè)對(duì)象
兩個(gè)值都是數(shù)字并且
都是正零 +0
都是負(fù)零 -0
都是 NaN
都是除零和 NaN 外的其它同一個(gè)數(shù)字復(fù)制代碼
Object.is() 類似于 ===,但是有一些細(xì)微差別缭付,如下:
- NaN 和 NaN 相等
- -0 和 +0 不相等
console.log(Object.is(NaN, NaN));//true
console.log(NaN === NaN);//false
console.log(Object.is(-0, +0)); //false
console.log(-0 === +0); //true復(fù)制代碼
Object.is 和 ==
差得遠(yuǎn)了柿估, ==
在類型不同時(shí),需要進(jìn)行類型轉(zhuǎn)換
33.toString
和String
的區(qū)別
toString
-
toString()
可以將數(shù)據(jù)都轉(zhuǎn)為字符串蛉腌,但是null
和undefined
不可以轉(zhuǎn)換官份。console.log(null.toString()) //報(bào)錯(cuò) TypeError: Cannot read property 'toString' of null console.log(undefined.toString()) //報(bào)錯(cuò) TypeError: Cannot read property 'toString' of undefined 復(fù)制代碼
-
toString()
括號(hào)中可以寫數(shù)字,代表進(jìn)制二進(jìn)制:.toString(2);
八進(jìn)制:.toString(8);
十進(jìn)制:.toString(10);
十六進(jìn)制:.toString(16);
String
-
String()
可以將null
和undefined
轉(zhuǎn)換為字符串烙丛,但是沒(méi)法轉(zhuǎn)進(jìn)制字符串console.log(String(null)); // null console.log(String(undefined)); // undefined
34.TCP的三次握手和四次揮手
三次握手
- 第一次握手:客戶端發(fā)送一個(gè)SYN碼給服務(wù)器舅巷,要求建立數(shù)據(jù)連接;
- 第二次握手: 服務(wù)器SYN和自己處理一個(gè)SYN(標(biāo)志)河咽;叫SYN+ACK(確認(rèn)包)钠右;發(fā)送給客戶端,可以建立連接
- 第三次握手: 客戶端再次發(fā)送ACK向服務(wù)器忘蟹,服務(wù)器驗(yàn)證ACK沒(méi)有問(wèn)題飒房,則建立起連接搁凸;
四次揮手
- 第一次揮手: 客戶端發(fā)送FIN(結(jié)束)報(bào)文,通知服務(wù)器數(shù)據(jù)已經(jīng)傳輸完畢狠毯;
- 第二次揮手: 服務(wù)器接收到之后护糖,通知客戶端我收到了SYN,發(fā)送ACK(確認(rèn))給客戶端,數(shù)據(jù)還沒(méi)有傳輸完成
- 第三次揮手: 服務(wù)器已經(jīng)傳輸完畢嚼松,再次發(fā)送FIN通知客戶端嫡良,數(shù)據(jù)已經(jīng)傳輸完畢
- 第四次揮手: 客戶端再次發(fā)送ACK,進(jìn)入TIME_WAIT狀態(tài);服務(wù)器和客戶端關(guān)閉連接献酗;
35.為什么建立連接是三次握手寝受,而斷開(kāi)連接是四次揮手呢?
建立連接的時(shí)候, 服務(wù)器在LISTEN狀態(tài)下罕偎,收到建立連接請(qǐng)求的SYN報(bào)文后很澄,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端。 而關(guān)閉連接時(shí)颜及,服務(wù)器收到對(duì)方的FIN報(bào)文時(shí)甩苛,僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),而自己也未必全部數(shù)據(jù)都發(fā)送給對(duì)方了器予,所以己方可以立即關(guān)閉浪藻,也可以發(fā)送一些數(shù)據(jù)給對(duì)方后,再發(fā)送FIN報(bào)文給對(duì)方來(lái)表示同意現(xiàn)在關(guān)閉連接乾翔,因此,己方ACK和FIN一般都會(huì)分開(kāi)發(fā)送施戴,從而導(dǎo)致多了一次反浓。
36.map和forEach的區(qū)別
相同點(diǎn)
- 都是循環(huán)遍歷數(shù)組中的每一項(xiàng) forEach和map方法里每次執(zhí)行匿名函數(shù)都支持3個(gè)參數(shù),參數(shù)分別是item(當(dāng)前每一項(xiàng))赞哗、index(索引值)雷则、arr(原數(shù)組),需要用哪個(gè)的時(shí)候就寫哪個(gè) 匿名函數(shù)中的this都是指向window 只能遍歷數(shù)組
不同點(diǎn)
- map方法返回一個(gè)新的數(shù)組肪笋,數(shù)組中的元素為原始數(shù)組調(diào)用函數(shù)處理后的值月劈。(原數(shù)組進(jìn)行處理之后對(duì)應(yīng)的一個(gè)新的數(shù)組。) map()方法不會(huì)改變?cè)紨?shù)組 map()方法不會(huì)對(duì)空數(shù)組進(jìn)行檢測(cè) forEach()方法用于調(diào)用數(shù)組的每個(gè)元素藤乙,將元素傳給回調(diào)函數(shù).(沒(méi)有return猜揪,返回值是undefined)
注意:forEach對(duì)于空數(shù)組是不會(huì)調(diào)用回調(diào)函數(shù)的。
37.setTimeout 和 setInterval的機(jī)制
因?yàn)閖s是單線程的坛梁。瀏覽器遇到etTimeout 和 setInterval會(huì)先執(zhí)行完當(dāng)前的代碼塊而姐,在此之前會(huì)把定時(shí)器推入瀏覽器的待執(zhí)行時(shí)間隊(duì)列里面,等到瀏覽器執(zhí)行完當(dāng)前代碼之后會(huì)看下事件隊(duì)列里有沒(méi)有任務(wù)划咐,有的話才執(zhí)行定時(shí)器里的代碼
38.JS中常見(jiàn)的異步任務(wù)
定時(shí)器拴念、ajax钧萍、事件綁定、回調(diào)函數(shù)政鼠、async await风瘦、promise
39.瀏覽器渲染的主要流程是什么?
將html代碼按照深度優(yōu)先遍歷來(lái)生成DOM樹(shù)。 css文件下載完后也會(huì)進(jìn)行渲染公般,生成相應(yīng)的CSSOM万搔。 當(dāng)所有的css文件下載完且所有的CSSOM構(gòu)建結(jié)束后,就會(huì)和DOM一起生成Render Tree俐载。 接下來(lái)蟹略,瀏覽器就會(huì)進(jìn)入Layout環(huán)節(jié),將所有的節(jié)點(diǎn)位置計(jì)算出來(lái)遏佣。 最后挖炬,通過(guò)Painting環(huán)節(jié)將所有的節(jié)點(diǎn)內(nèi)容呈現(xiàn)到屏幕上。
40.ajax中g(shù)et和post請(qǐng)求的區(qū)別
get 一般用于獲取數(shù)據(jù)
get請(qǐng)求如果需要傳遞參數(shù)状婶,那么會(huì)默認(rèn)將參數(shù)拼接到url的后面意敛;然后發(fā)送給服務(wù)器;
get請(qǐng)求傳遞參數(shù)大小是有限制的膛虫;是瀏覽器的地址欄有大小限制草姻;
get安全性較低
get 一般會(huì)走緩存,為了防止走緩存稍刀,給url后面每次拼的參數(shù)不同撩独;放在?后面,一般用個(gè)時(shí)間戳
post 一般用于發(fā)送數(shù)據(jù)
post傳遞參數(shù)账月,需要把參數(shù)放進(jìn)請(qǐng)求體中综膀,發(fā)送給服務(wù)器;
post請(qǐng)求參數(shù)放進(jìn)了請(qǐng)求體中局齿,對(duì)大小沒(méi)有要求剧劝;
post安全性比較高;
post請(qǐng)求不會(huì)走緩存抓歼;
42.DOM diff原理
- 如果元素類型發(fā)生變化讥此,直接替換
- 如果是文本,則比較文本里面的內(nèi)容谣妻,是否有差異萄喳,如果是元素就需要比較當(dāng)前元素的屬性是否相等,會(huì)先比較key, 在比較類型 為什么 react中循環(huán) 建議不要使用索引 ,如果純?yōu)榱苏故?那可以使用索引
43.動(dòng)手實(shí)現(xiàn)一個(gè)bind(原理通過(guò)apply拌禾,call)
一句話概括:1.bind()返回一個(gè)新函數(shù)取胎,并不會(huì)立即執(zhí)行。
2.bind的第一個(gè)參數(shù)將作為他運(yùn)行時(shí)的this,之后的一系列參數(shù)將會(huì)在傳遞的實(shí)參前傳入作為他的參數(shù)
3.bind返回函數(shù)作為構(gòu)造函數(shù)闻蛀,就是可以new的匪傍,bind時(shí)指定的this值就會(huì)消失,但傳入的參數(shù)依然生效
Function.prototype.bind = function (obj, arg) {
var arg = Array.prototype.slice.call(arguments, 1);
var context = this;
var bound = function (newArg) {
arg = arg.concat(Array.prototype.slice.call(newArg);
return context.apply(obj, arg)
}
var F = function () {} // 在new一個(gè)bind會(huì)生成新函數(shù)觉痛,必須的條件就是要繼承原函數(shù)的原型役衡,因此用到寄生繼承來(lái)完成我們的過(guò)程
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
}