寫在前面
由於本人還是一個前端菜鳥,所以是非常有可能出錯誤的,熱烈歡迎指正!
JavaScript
JavaScript是一門什麼樣的語言?有哪些特點?
執行在瀏覽器的V8引擎中的指令碼語言,不要編譯就可以由直譯器直接執行的,此外變數鬆散定義,屬於弱型別語言。
說幾條JavaScript的基本規範?
- 不要在同一行宣告多個變數
- 用===替代==
- switch語句要有default分支
- 建構函式首字母大寫,常量用大寫字母
- 使用物件字面量替代new Function(){}
頁面渲染原理
- 瀏覽器解析HTML生成一個DOMTree
- 接著解析CSS會產生CSSRuleTree
- 解析完成後,瀏覽器引擎會通過DOMTree和CSSRuleTree來構造RenderingTree。
- 瀏覽器呼叫
預編譯
- 首先掃描var關鍵字,提升到頂端;
- 然後掃描function定義,提到var之前
- 然後再順序執行
defer和async
- 在
<script>
元素中設定defer屬性,相當於告訴瀏覽器立刻下載,延遲執行。會等頁面解析完後,按指定順序執行 - async屬性會告訴瀏覽器立刻下載,一旦下載完就會開始執行,且不能保證按照指定它們的先後順序執行。
資料型別
原始型別有哪幾種?null是物件嗎?
原始型別有:boolean,string,number,null,undefined,symbol 6種。
null不是物件,typeof null會輸出object,是因為以前32為系統會將000開頭的代表為物件,但null表示為全零所以被錯誤判斷成object
原始型別和引用型別的區別
1.原始型別儲存的是值,引用型別儲存的是指標。 2.原始資料型別直接儲存在棧中,引用資料型別儲存在堆中。
使用typeof可以得到哪些型別?
undefined,string,number,boolean,object,function
typeof只能區分除null的原始型別,引用型別只能區分function。
什麼是提升?什麼是暫時性死區?var,let及const區別
- 函式提升優先於變數提升,函式提升會將整個函式挪到作用域頂部,變數提升只將宣告提到作用域頂部
- var存在提升,可以在宣告前使用,let,const因為暫時性死區,不能在宣告前使用
- var在全域性作用域下宣告變數會導致變數掛載到window上,其他兩者不會
- let和const作用基本一致,但const宣告的變數不能再次賦值
- let和const不允許重複宣告
原始型別
為什麼0.1+0.2!=0.3?如何解決這個問題?
在計算機中,數字以二進位制形式儲存。在JavaScript中數字採用IEEE754的雙精度標準進行儲存,因為儲存空間有限,當出現無法整除的小數時會取一個近似值。
在0.1+0.2中0.1和0.2都是近似表示的,所以相加時兩個近似值進行計算導致最後結果為0.3000,0000,0000,0004,此時對於JS來說不夠近似於0.3,所以出現了0.1+0.2!=0.3
解決方法:parseFloat((0.1+0.2).toFixed(10)) === 0.3 // true
null和undefined的區別
undefine: 表示變數被宣告瞭但沒有賦值。
null:變數被定義賦值了,但是為空,沒有任何屬性方法和值
Symbol的使用場景
作為屬性名的使用
var mySymbol=Symbol();
var a={};
a[mySymbol]='hello'
複製程式碼
操作符
何時使用===,何時使用==
==會進行型別轉換後再比較,===不會,儘量都用===.
以下兩種情況可以用==
- 判斷物件屬性是否存在
var obj={}
if(obj.a == null){
// 相當於obj.a===null || obj.a===undefined
}
複製程式碼
- 判斷函式引數是否存在
function fn(a, b){
if(b == null){
// 相當於b===null || b===undefined
}
}
複製程式碼
Object.is()與===,==的區別?
Object.is()可以說是升級版,Object.is(NaN,NaN)會返回true,Object.is(-0,+0)返回false
引用型別
Array型別
如何判斷一個變數是陣列?
1.判斷是否具有陣列某些方法
if(arr.splice){}
2.instanceof(某些IE版本不正確)
arr instanceof Array
3.Array.isArray()
4.Object.prototype.toString.call(arr); // '[object Array]'
5.constructor方法
arr.constructor === Array
將類陣列轉化為陣列
let arrayLike = {
'0' : 'a',
'1' : 'b',
'2' : 'c',
length : 3
};
let arr1 = Array.prototype.slice.call(arrayLike);
let arr2 = [].slice.call(arrayLike);
let arr3 = Array.from(arrayLike);
複製程式碼
陣列的方法
// 會改變原陣列
pop() // 末尾刪除
push() // 末尾新增
shift() // 開頭刪除
unshift() // 開頭新增
reverse() // 陣列反轉
sort() // 排序
splice() // 修改陣列(刪除插入替換)
// 不會改變原陣列
concat() // 合併陣列
slice() // 選擇陣列的一部分
indexOf() // 順序查詢指定元素下標
lastIndexOf() // 倒序查詢指定元素下標
複製程式碼
// 迭代方法
// every()查詢陣列是否每一項都滿足條件
// some()查詢陣列中是否有滿足條件的項
// filter()過濾,返回true的項組成的陣列
// map()對每一項執行給定函式,返回每次函式呼叫結果組成的陣列
// forEach()對每一項執行給定函式,無返回值
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.every(function(item,index,array){
return item>2;
}) // false
numbers.some(function(item,index,array){
return item>2;
}) // true
numbers.filter(function(item,index,array){
return item>2;
}) // [3,4,5,4,3]
numbers.map(function(item,index,array){
return item*2;
}) // [2,4,6,8,10,8,6,4,2]
numbers.forEach(function(item,index,array){
// 執行某些操作
}) // 無返回值
複製程式碼
// 歸併方法
// reduce()從第一項開始逐個遍歷到最後
// reduceRight()從最後一項開始向前遍歷到第一項
var values = [1,2,3,4,5];
values.reduce(function(prev,cur,index,array){
return prev+cur;
}) // 15
// reduceRight()結果一樣,順序相反
複製程式碼
原生sort使用的是哪些排序演算法?
插入排序和快速排序結合的排序演算法
['1','2','3'].map(parseInt)的答案是?
[1,NaN,NaN]
因為parentInt需要兩個引數(val,radix),radix表示解析進位制,而map傳入三個(element,index,array)導致對應的radix不合法導致解析錯誤。
Date()型別
獲取2019-02-16格式的日期
function formatDate(dt){
if(!dt){
dt=new Date()
}
var year = dt.getFullYear();
var month = dt.getMonth()+1;
var day = dt.getDate();
if(month<10){
month= '0'+month;
}
if(day<0){
day = '0'+day;
}
var formatDt = year+'-'+month+'-'+day
return formatDt;
}
複製程式碼
其他一些方法
getHour() // 返回時
getMinutes() // 返回分
getSeconds() // 返回秒
getDay() // 返回星期天數
getTime() // 返回毫秒數
複製程式碼
Function型別
函式宣告和函式表示式
解析器會先讀取函式宣告,提升到最前。而函式表示式會等到執行到它所在的程式碼才真正被解釋執行
// 函式宣告
function sum(a,b){
return a+b;
}
// 函式表示式
var sum = function(a,b){
return a+b;
}
複製程式碼
談談對this物件的理解
this引用函式執行的環境物件,總是指向函式的直接呼叫者,在執行時才能確定值
this的指向
1.預設繫結,在瀏覽器中為window,在node中是global
2.隱式繫結 例:window.a()
3.顯式繫結
- 硬繫結 call,apply,bind
- api呼叫的上下文 filter,forEach等有一個可選引數,在執行callback時用於this值
4.new繫結
5.箭頭函式
這裡寫的不是很詳細,推薦看JavaScript深入之史上最全--5種this繫結全面解析
bind,call和apply各自有什麼區別?
相同點
- 都是用來改變函式的this指向
- 第一個引數是this要執行的值
- 都可以利用後續引數傳參
不同點
- call和apply是對函式直接呼叫,bind返回一個函式,需要()進行呼叫
- call傳參是一個個傳,bind與call一樣,apply第二個引數是陣列
callee和caller的作用?
callee是arguments物件的一個屬性,指向arguments物件的函式即當前函式。遞迴可以使用arguments.callee()。 箭頭函式中this作用域與函式外一致,且沒有arguments物件,所以箭頭函式沒有callee
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1)
}
}
複製程式碼
caller是函式物件的一個屬性,指向呼叫當前函式的函式,比如A呼叫B,則B.caller指向A()。全域性作用域下呼叫當前函式,caller的值為null
基本包裝型別
Number
toFixed()按指定小數位返回數值的字串表示
var num=10; num.toFixed(2); // '10.00'
String
// charAt()根據字元位置返回所在位置的字串
// charCodeAt()根據字元位置返回所在位置字串的字元編碼
var str = 'hello world';
str.charAt(1); // 'e'
str.charCode(1); // '101'
// fromCharCode() 接收字元編碼轉為字串
String.fromCharCode(104,101,108,108,111) //'hello'
// concat()將字元拼接起來得到新字串
var str="hello"
str.concat(' world'); // "hello world"
// indexOf()和lastIndexOf() 返回字元位置
// trim() 刪除空格
// toLowerCase() 轉小寫,toUpperCase() 轉大寫
// localeCompare() 根據字母表比較排序
複製程式碼
slice,substr和substring的區別
slice和substring接收的是起始位置和結束位置,substr接收的是起始位置和所要返回的字串長度
對於負數,slice會將負數與字串長度相加,substr會將負的第一個引數加上字串長度,第二個引數轉為0,substring會將所有負數轉為0
split()和join()的區別
join()將陣列中的元素放入一個字串中,split將字串分割成陣列
var arr=[1,2,3];
var str=arr.join('|'); // '1|2|3'
str.split('|'); // [1,2,3]
複製程式碼
單體內建物件
Global物件
URI編碼方法
encodeURI和encodeURICcomponent encodeURI和decodeURICcomponent
eval的作用?
功能是將對於字串解析出JS程式碼執行。要避免使用eval,不安全且非常消耗效能
Math物件
- min()和max()
var max=Math.max(3,54,32,16); // 54
複製程式碼
- 舍入方法 Math.ceil() // 向上舍入 Math.floor() // 向下舍入 Math.round() // 四捨五入
- random()
原型和原型鏈
原型
- 所有引用型別(陣列、物件、函式)都有一個__proto__隱式原型屬性,屬性值是一個普通物件。 此外,Object.prototype.__proto__指向null
- 所有函式都有一個prototype顯式原型屬性,屬性值是一個普通物件。 Function.prototype.bind()沒有prototype屬性
- 所有引用型別(陣列、物件、函式)的__proto__執行它的建構函式的prototype屬性
建構函式
建構函式特點:函式名首字母大寫,它就類似一個模板,可以new出多個例項
var a={} // var a=new Object()的語法糖
var a=[] // var a=new Array()的語法糖
function Foo(){} // var Foo=new Function()的語法糖
複製程式碼
instanceof
instanceof判斷引用型別屬於哪個建構函式
f instanceof Foo的判斷邏輯:f的__proto__一層層往上,能否找到Foo.prototype。
但是因為原型鏈上所有特殊物件的__proto__最終都會指向Object.prototype,所以instanceof判斷型別也不完全準確
new操作符具體幹了什麼?
- 建立一個空物件
- 將物件的__proto指向建構函式的原型prototype
- 執行建構函式中的程式碼,傳遞引數,並將this指向這個物件
- 返回物件
function _new(){
let obj = {};
let con=[].shift.call(arguments);
obj.__proto__ = con.prototype;
let res = con.apply(obj, arguments);
return res instanceof Object ? res : obj;
}
複製程式碼
通過new的方式建立物件和通過字面量建立的區別
更推薦字面量的方式建立物件,效能和可讀性都更好。使用var o=new Object()和var o={}的區別是前者會呼叫建構函式
hasOwnProperty
hasOwnProperty判斷該物件本身是否有指定屬性,不會到原型鏈上查詢。
使用方法:object.hasOwnProperty(proName)
利用它可以迴圈物件自身屬性
for(let item in f){
if(f.hasOwnProperty(item)){
console.log(item)
}
}
複製程式碼
原型鏈
訪問一個物件的屬性時,先在基本屬性中查詢,如果沒有,再沿著__proto__這條鏈向上找,這就是原型鏈
物件導向
建立物件的幾種方式
- 工廠模式
function createPerson(name, age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
}
return o;
}
var person1 = createPerson('chen',21)
複製程式碼
- 建構函式模式
沒有顯示建立物件,直接將屬性方法賦給this,沒有return語句
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name)
}
}
var person1 = new Person('chen',21)
複製程式碼
缺點:每個方法都要在每個例項上重新定義一遍,無法得到複用
- 原型模式
function Person(){}
Person.prototype.name="chen"
Person.prototype.age=21
Person.prototype.sayName=function(){
console.log(this.name)
}
var person1 = new Person()
複製程式碼
缺點:所有例項都取得相同屬性值
- 混合建構函式原型模式 建構函式模式用於定義例項屬性,原型模式用於定義方法和共享的屬性。
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name)
}
}
var person1=new Person('chen',21)
複製程式碼
實現繼承
JavaScript通過原型鏈實現繼承
- 原型鏈繼承 核心:將父類的例項作為子類的繼承
function Parent(){
this.name = 'parent'
}
Parent.prototype.sayName = function(){
return this.name
}
function Child(){
}
// 繼承了Parent
Child.prototype = new Parent();
var child1=new Child();
child1.say();
複製程式碼
缺點:物件例項共享所有繼承的屬性和方法
- 借用建構函式 核心:在子建構函式內部呼叫父建構函式
function Parent(){
this.arr=[1,2,3]
}
function Child(){
// 繼承了Parent
Parent.call(this)
}
var child1 = new Child();
child.arr.push(4); // [1,2,3,4]
var child2 = new Child();
child.arr; // [1,2,3]
複製程式碼
- 組合繼承
使用原型鏈繼承共享的屬性和方法,通過借用建構函式繼承例項屬性
function Parent(name){
this.name = name;
this.arr = [1,2,3]
}
Parent.prototype.sayName = function(){
console.log(this.name)
}
function Child(name,age){
// 繼承屬性
Parent.call(this, name)
this.age=age
}
// 繼承方法
Child.prototype = new Parent()
Child.prototype.constructor = Child;
Child.prototype.sayAge = function(){
console.log(this.age)
}
var child1=new Child('chen',21);
child1.arr.push(4); //[1,2,3,4]
child1.sayName() // 'chen'
child1.sayAge() // 21
var child2=new Child('miao', 12)
child2.arr // [1,2,3]
child2.sayName() // "miao"
child2.sayAge() // 12
複製程式碼
缺點:無論在什麼情況都會呼叫兩次父建構函式,一次是建立子型別原型,另一次是在子建構函式內部
- 原型式繼承 核心:執行對給定物件的淺複製
var person = {
name: 'chen',
arr: [1,2,3]
}
var person1 = Object.create(person);
person1.name = 'run'
person1.arr.push(4)
var person2 = Object.create(person);
person2.name = 'miao'
person2.arr.push(5)
person.arr; // [1,2,3,4,5]
複製程式碼
- 寄生式繼承 核心:基於某個物件建立一個物件,然後增強物件,返回物件。
function create(original){
// 通過呼叫函式建立一個新物件
var clone = object(original);
// 以某種方式增強物件
clone.sayHi = function(){
console.log('hi')
}
return clone;
}
var person = {
name: 'chen'
}
var person1 = create(person);
person1.sayHi();
複製程式碼
- 寄生組合式繼承
function Parent(name){
this.name = name;
this.arr = [1,2,3]
}
// 將共享的屬性/方法放到原型上
Parent.prototype.sayName = function(){
console.log(this.name)
}
// 借用建構函式增強子類例項屬性(支援傳參和避免篡改)
function Child(name,age){
// 繼承屬性
Parent.call(this, name)
this.age=age
}
function inheritPrototype(Child, Parent){
var prototype=Object.create(Parent.prototype);
prototype.constructor=Child;
Child.prototype=prototype;
}
// 將父類原型指向子類
inheritPrototype(Child, Parent);
Child.prototype.sayAge=function(){
console.log(this.age)
}
var child1=new Child('chen',21);
child1.arr.push(4); //[1,2,3,4] 繼承自父類例項屬性
child1.sayName() // 'chen' 繼承自父類原型方法
child1.sayAge() // 21 繼承自子類原型方法
var child2=new Child('miao', 12)
child2.arr // [1,2,3]
child2.sayName() // "miao"
child2.sayAge() // 12
複製程式碼
class如何實現繼承,class本質是什麼?
class只是語法糖,本質是函式
class Parent{
constructor(value){
this.val=value
}
getValue(){
console.log(this.val)
}
}
class Child extends Parent{
constructor(value){
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
複製程式碼
核心:使用extends表明繼承自哪個父類,並且在子類建構函式中必須使用super,可以看做是Parent.call(this,value)
什麼是物件導向程式設計及程式導向程式設計,它們的異同和優缺點
程式導向就是分析出解決問題所需的步驟,然後用函式把這些步驟一步步實現,使用的時候一個個依次呼叫
物件導向是把構成問題的事務分解成各個物件,獎勵物件的目的不是為了完成一個步驟,而是為了描述某個事物在整個解決問題的步驟中的行為。
優點:易維護,可讀性高,易擴充套件,繼承性高,降低重複工作量,縮短了開發走起
閉包
閉包指有權訪問另一個函式內部變數的函式,當在函式內部定義了其他函式,也就建立了閉包
談談你對閉包的理解
使用閉包可以模仿塊級作用域
優點:可以避免全域性變數的汙染,實現封裝和快取;
缺點:閉包會常駐記憶體,增大記憶體使用量,使用不當很容易造成記憶體洩漏。解決方法:在退出函式之前,將不適用的區域性變數設為null。
閉包最大的兩個用處:1.可以讀取函式內部的變數;2.使這些變數始終儲存在記憶體中;3.封裝物件的私有屬性和私有方法
說說你對作用域鏈的理解
作用域鏈的作用是保證執行環境裡有權訪問的變數和函式是有序的,作用域鏈的變數只能享受訪問,訪問到window物件即被終止。
簡單來說,作用域就是變數和函式的可訪問範圍
如何建立塊級作用域
- ES6 使用let和const
- 閉包
BOM
BOM物件有哪些?
- window JS最頂層物件
- location 瀏覽器當前URL資訊
- navigator 瀏覽器本身資訊
- screen 客戶端螢幕資訊
- history 瀏覽器訪問歷史資訊
window物件的方法
alert(),prompt(),confirm(),open(),close(),print(),focus(),blur(),moveBy(),moveTo(),resizeBy(),resizeTo(),scrollBy(),scrollTo(),setInterval(),setTimeout(),clearInterval(),clearTimeout()
history物件
history.go(-1); // 後退一頁
history.go(1); // 前進一頁
history.back(); // 後退一頁
history.forward(); // 前進一頁
複製程式碼
視窗位置
- 跨瀏覽器取得視窗左邊和上邊的位置
var leftPos=(typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX;
var topPos=(typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
複製程式碼
- 將視窗移動到指定位置
moveTo():接收新位置的x,y座標值
moveBy():接收在水平垂直方向上移動的畫素數
視窗大小
outerWidth和outerHeight返回瀏覽器視窗本身的尺寸,innerWidth和innerHeight返回容器檢視區的大小
- 調整視窗大小 resizeTo()和resizeBy()
如何檢測瀏覽器的型別
使用navigator.userAgent
var ua = navigator.userAgent;
var isChrome = ua.indexOf('Chrome')
複製程式碼
拆解url的各部分
使用location的屬性
- href 完整url地址
- protocol 協議
- host 主機名+埠號
- hostname 主機名
- port 埠號
- pathname 相對路徑
- hash #錨點
- search ?查詢字串
history.go(1)
複製程式碼
DOM
為什麼操作DOM慢?
因為DOM屬於渲染引擎的東西,JS又是JS引擎的東西,當我們通過JS操作DOM的時候,涉及到兩個執行緒間的通訊,而且操作DOM可能會帶來重繪迴流的情況,所以就導致了效能問題。
插入幾萬個DOM,如何實現頁面不卡頓?
- 1.分批次部分渲染DOM,通過requestAnimationFrame去迴圈插入DOM
- 2.虛擬滾動,只渲染可視區域內的內容
什麼情況阻塞渲染
- 1.HTML和CSS都會阻塞渲染,如果想渲染快就應該降低一開始渲染的檔案大小,扁平層級,優化選擇器
- 2.瀏覽器解析到script標籤時會暫停構建DOM,
解決方法:
- ①將script標籤放到body底部
- ②給script標籤加上defer屬性,該JS檔案會並行下載但等到HTML解析完後順序執行
- ③script標籤加上async屬性,表示JS檔案下載和解析不會阻塞渲染
重繪Repaint和迴流Reflow
重繪是當節點改變樣式而不影響佈局,迴流是當佈局或幾何屬性需要改變
迴流必定會發生重繪,迴流的成本比重繪高
效能問題:1.改變window大小 2.改變字型 3.新增或刪除樣式 4.文字改變 5.定位或浮動 6.盒模型
減少重繪和迴流
- 1.使用transform替代top
- 2.使用visibility換成display:none,前者只引起重繪,後者引發迴流
- 3.不要把節點的屬性值放在一個迴圈裡當成迴圈裡的變數
- 4.不要使用table佈局
- 5.動畫實現的動畫速度越快,迴流次數越多,也可使用requestAnimationFrame
- 6.CSS選擇符從右往左匹配,避免節點層級過多
- 7.將頻繁重繪或迴流的節點設定為圖層 ①will-change屬性 ②video, iframe標籤
建立節點
- createElement 建立一個元素
- createTextNode 建立一個文字節點
- createDocumentFragment 建立一個文件片段
- cloneNode 返回撥用該方法的節點的一個副本
注意事項:
- 以上只是建立一個孤立的節點,需要通過appendChild新增到文件中
- cloneNode要注意被複制的節點是否包含子節點以及事件繫結等問題
- 使用createDocumentFragment來解決新增大量節點時的效能問題
頁面修改API
- appendChild() 新增子節點
- insertBefore() 新增節點到參考節點之前
parentNode.insertBefore(newNode, refNode)
parentNode表示父節點,newNode表示要新增的節點,refNode表示參照節點 - removeChild() 刪除子節點
- replaceChild() 替換
parent.replaceChild(newChild,oldChild)
節點查詢API
- document.getElementById
- document.getElementsByTagName
- document.getElementsByName
- document.getElementsByClassName
- document.querySelector // 返回第一個匹配的元素
- document.querySelectorAll
節點關係型API
- parentNode // 父節點
- parentElement // 父節點,必須是element
- childNodes // 子元素列表,包含文字,註釋節點
- children // 子元素列表
- firstChild // 第一個子節點
- lastChild // 最後一個子節點
- hasChildNodes // 判斷當前節點是否有子節點
- previousSibling // 前一個兄弟節點
- previousElementSibling // 前一個兄弟元素節點
- nextSibling // 後一個兄弟節點
- nextElementSibling // 後一個兄弟元素節點
元素屬性API
- setAttribute 設定屬性
element.setAttribute(name, value)
- getAttribute 返回屬性值
- removeAttribute 刪除屬性
元素樣式API
- window.getComputedStyle 返回元素計算後的樣式
- getBoundingClientRect 返回元素大小以及相對於瀏覽器可視視窗的位置
- 直接修改元素的樣式
ele.style.color = 'red'
ele.style.setProperty('font-size', '16px')
ele.style.removeProperty('color')
複製程式碼
- 動態新增樣式規則
var style = document.createElement('style');
style.innerHTML='body{color:red;} #top{color:white;}';
document.head.appendChild(style);
複製程式碼
attribute和property的區別是什麼?
attribute是dom元素在文件中作為HTML標籤擁有的屬性
prototype是dom元素在JS中作為物件擁有的屬性
JS如何設定獲取盒模型對於的寬和高?
- 獲取內聯元素的寬高
dom.style.width/height
- 只適用在IE中獲取元素寬高
dom.currentStyle.width/height
- 獲取元素寬高,相容性較好
window.getCompontedStyle(dom).width/height
- 根據元素在視窗中的絕對位置獲取寬高
dom.getBoundingClientRect().width/height
- 最常用,相容性最好
dom.offsetWidth/offsetHeight
offsetWidth/offsetHeight,clientWidth/clientHeight與srcollWidth/scrollHeight的區別
- offsetWidth/offsetHeight返回包含content+padding+border,效果與e.getBoundingClientRect()相同
- clientWidth/clientHeight返回包含content+padding,如果有滾動條,也不包含滾動條
- scrollWidth/scrollHeight返回包含content+paddin+溢位內容的尺寸
document.write和innerHTML的區別
- document.write只能重繪整個頁面
- innerHTML可以重繪頁面的一部分
DOM事件
DOM事件的級別
- DOM0
element.onclick=function(){}
- DOM2
element.addEventListener('click',function(){},false)
- DOM3
element.addEventListener('keyup',function(){},false)
DOM0級事件就是將一個函式賦值給一個事件處理屬性,缺點在於一個處理程式無法同時繫結多個處理函式。
DOM2級事件執行給一個程式新增多個處理函式,定義了addEventListener和removeEventListener兩個方法,分別用於繫結和解綁事件,方法包含三個引數分別是繫結的事件處理的屬性名稱,處理函式,是否在捕獲時執行事件
IE8以下使用attachEvent和detachEvent實現,不需要傳入第三個引數,因為IE8以下只支援冒泡型事件
btn.attachEvent('onclick', showFn);
btn.detachEvent('onclick', showFn);
複製程式碼
DOM3級事件是在DOM2級事件的基礎上新增很多事件型別如load,scroll,blur,focus,dbclick,mouseup,mousewheel,textInput,keydown,keypress,同時也允許使用者自定義一些事件。
如何使用事件?
- HTML事件處理程式
<div onclick="clicked()"></div>
複製程式碼
優缺點:簡單,但與HTML程式碼緊密耦合,更改不方便
- DOM0級處理程式
document.onclick = function(){}; // 指定事件
docuemtn.onclick = null; // 刪除事件
複製程式碼
優缺點:簡單且跨瀏覽器
- DOM2級處理程式
addEventListener('click', function(){},布林值) // 指定事件
removeListener('click', function(){}, 布林值) // 移除事件
複製程式碼
優缺點:可以新增多個監聽函式,如果指定處理函式是匿名函式,則無法刪除
- IE事件處理程式
attachEvent('onclick', function(){}) // 指定事件
detachEvent('click', function(){}) // 移除事件
複製程式碼
優缺點:可以新增多個監聽函式,如果指定處理函式是匿名函式,則無法刪除
IE與標準DOM事件模型之間存在的差別
- 引數的差別:
attachEvent()第一個引數比addEventListener()的事件名多一個“on”;且沒有第三個引數,因為IE事件模型只支援冒泡事件流
- 事件處理函式作用域:
IE中事件處理程式處於全域性作用域,其內的this會指向window,而DOM事件模型是作用於元素,其內的this執行所屬元素
- 事件物件event的屬性方法的差別
阻止冒泡
IE:cancelBubble=true
DOM: stopPropagation()
阻止元素預設事件
IE:returnValue=false
DOM:preventDefault()
事件目標
IE: srcElement DOM: target
IE與標準有哪些相容性寫法?
var ev = ev || window.event;
document.docuemntElement.clientWidth || document.body.clientWidth
var target = ev.srcElement || ev.target
複製程式碼
DOM事件模型(冒泡和捕獲)
- 事件冒泡
addEventListener第三個引數為false,事件在冒泡時候執行,事件目標target一級一級往上冒泡
如何阻止事件冒泡
child.addEventListener('click', function(e){
console.log('目標事件')
e.stopPropagation();
}, false)
複製程式碼
- 事件捕獲
事件捕獲是自上而下執行的,將addEventListener第三個引數為true
DOM事件流
DOM2級事件規定的事件流包括三個階段:事件捕獲階段,處於目標階段和事件冒泡階段。首先傳送的是事件捕獲,為截獲事件提供了機會,然後是實際的目標接收到事件,最後一個階段是冒泡階段,可以在這個階段對事件做出響應
捕獲DOM事件捕獲的具體流程
window->docuemnt->html->body->...
一開始接收事件的window,window接收完以後給到document,第三個才是html標籤,再就是body,然後在一級一級往下傳。與之相當的就是冒泡,從當前元素到window的過程
Event物件的常見應用
- event.preventDefault(): 阻止預設事件
- event.stopPropagation(): 阻止冒泡
- event.stopImmediatePropagatio(): 阻止剩餘事件處理函式的執行,並防止當前事件在DOM上冒泡
- event.currentTarget: 事件委託時,當前繫結的事件元素
- event.target: 事件委託時,區分當前哪個元素被點選
自定義事件
var event = new Event('custome');
ev.addEventListener('custome', function(){
console.log('custome');
});
ev.dispatchEvent(event);
複製程式碼
mouseover和mouseenter的區別
mouseover:當滑鼠移入元素或其子元素都會觸發事件,所以有一個重複觸發,冒泡的過程。對應的移除事件是mouseout
mouseenter: 當滑鼠移入元素本身(不包含子元素)會觸發事件,不會冒泡,對應的移除事件是mouseleave
請解釋什麼是事件代理?
事件代理(事件委託)把原本需要繫結的事件委託給父元素,讓父元素擔當事件監聽的職務。原理是DOM元素的事件冒泡。
好處:可以提高效能,大量節省記憶體佔用,減少事件註冊,可以實現新增子物件時無需再次對其繫結
document.load和document.ready的區別
document.onload是在樣式結構載入完才執行,window.onload()不僅要樣式結構載入完還要執行完所有樣式圖片資原始檔全部載入完後才會觸發
document.ready原生中無此方法,jQuery中有$().ready(),ready事件不要求頁面全載入完,只需要載入完DOM結構即可觸發。
JSON
如何理解JSON?
JSON是JS的一個內建物件,也是一種輕量級的資料交換格式。 常用兩個方法:
- parse // 字串轉物件
- stringify // 物件轉字串
XML和JSON的區別?
- 資料體積方面:JSON相對於XML來講,資料的體積小,傳遞的速度更快些。
- 資料互動方面:JSON與JavaScript的互動更加方便,更容易解析處理,更好的資料互動
- 資料描述方面:JSON對資料的描述性比XML較差
- 傳輸速度方面:JSON的速度要遠遠快於XML
Ajax
如何建立Ajax?
// 建立XMLHTTPRequest物件
var xhr = new XMLHttpRequest();
// 建立一個新的http請求
xhr.open("get", url, true)
// 設定響應HTTP請求狀態變化的函式
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
// 獲取非同步呼叫返回的資料
alert(xhr.responseText)
}
}
}
// 傳送HTTP請求
xhr.send(null);
複製程式碼
狀態碼readyState說明:0:未初始化,未呼叫send();1:已呼叫send(),正在發生請求;2:send()方法執行完畢,已經接收到全部響應內容;3:正在解析響應內容;4:解析完成,可以在客戶端呼叫了
Ajax有哪些優缺點?
優點:1.通過非同步模式,提升使用者體驗;2.優化了瀏覽器和伺服器之間的傳輸,減少不必要的資料往返,減少了頻寬佔用;3.Ajax在客戶端執行,承擔了一部分本來由伺服器承擔的工作,減少了大使用者量下的伺服器負載;4.Ajax可以實現區域性重新整理
缺點:1.Ajax暴露了與伺服器互動的的細節;2.對搜尋引擎的支援較弱;3.不容易除錯
GET和POST在瀏覽器實現的區別
- GET後退重新整理無害,可收藏為書籤,能被快取,引數會保留在瀏覽器歷史中,只允許ASCII字元,安全性較差,資料顯示在URL中
- POST後退重新整理會重新提交資料,不可被收藏為標籤,不能被快取,引數不會被儲存在瀏覽器歷史中,對資料型別無限制,安全性較好,資料不會顯示在URL中
GET請求傳參的誤區
誤區:我們常認為GET請求引數的大小存在限制,而POST無限制
實際上HTTP協議沒有限制GET/POST的請求長度限制,GET的最大長度限制是因為瀏覽器和WEB伺服器限制了URL的長度。
GET和POST的區別
GET和POST方法沒有實質區別,只是報文格式不同。
跨域
可以跨域的三個標籤:<img><link><script>
什麼是跨域?
跨域指通過JS在不同的域之間進行資料傳入或通訊。 協議,域名,埠有一個不同就是不同域
瀏覽器為什麼要使用同源策略?
同源策略是為了防止CSRF攻擊,它是利用使用者的登入態發起惡意請求。如果沒有同源策略,網站可以被任意來源的Ajax訪問到內容。
如何解決跨域問題?
- JSONP
原理是利用
<script>
標籤沒有跨域限制的漏洞
function jsonp(url, callback, success){
let script = docuemnt.createElement('scipt');
script.src = url;
script.async = true;
script.type = "text/javascript";
window[callback] = function(data){
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx.com', 'callback', function(value){
console.log(value);
})
複製程式碼
JSONP使用簡單,相容性不錯但僅限GET請求
- CORS CORS需要前後端同時支援,伺服器端設定Access-Control-Origin開啟CORS,該屬性表明哪些域名可以訪問資源。分為簡單請求和複雜請求,複雜請求多一個預檢請求
- document.domain 只適用於二級域名相同,例如
a.test.com
和b.test.com
。在頁面新增document.domain = 'test.com'
表示二級域名相同即可跨域 - postMessage 通常用於獲取嵌入頁面的第三方頁面資料,一個頁面傳送訊息,另一個頁面判斷來源並接收訊息
// 傳送訊息
window.parent.postMessage('message', 'http://www.test.com')
// 接收訊息
let mc=new MessageChannel()
mc.addEventListener('message', event => {
let origin = event.origin || event.originalEvent.origin;
if(origin === 'http://www.test.com'){
console.log('驗證通過')
}
})
複製程式碼
window.name
儲存
請描述cookie,sessionStorage和localStorage的區別
答:
- 容量:cookie只有4kb,localStorage和sessionStorage最大容量5M
- 伺服器通訊:cookie用於客戶端與伺服器端的通訊
- 有效時間:cookie在設定的有效時間過期前都存在,sessionStorage關閉當前頁面就清理,localStorage除非主動刪除否則一直存在
- api易用性:cookie的api需要自己封裝才能使用,localStorage.setItem(key,value);localStorage.getItem(key)
有幾種方式可以實現儲存功能?
cookie,sessionStorage,localStorage,indexDB。
indexDB不限儲存大小,不與伺服器端通訊,除非主動刪除否則一直存在。
什麼是Service Worker?
Service Worker是執行在瀏覽器背後的獨立執行緒,一般可以用來實現快取功能,傳輸協議必須為https
Server Worker的工作?
- 與快取進行互動,當使用者請求快取中的東西時,Service Worker能立刻從快取中獲取資料不通過外部http呼叫
- 傳送推送通知
- 執行背景同步:在離線的情況下用瀏覽器傳送一個檔案,Service Worker可以儲存此任務,等網路連線恢復後將檔案上傳至外部伺服器
ES6
箭頭函式
箭頭函式與普通函式的區別:
- 箭頭函式沒有arguments(用剩餘運算子替代)
- 箭頭函式沒有prototype,不能作為建構函式(不能用new關鍵字呼叫)
- 箭頭函式沒有自己的this,引用的是外層執行上下文的this
擴充套件運算子
- 可將類陣列轉為真正的陣列
let nodeList = document.querySelectorAll('div')
let arr=[...nodeList]
複製程式碼
- 合併多個陣列
let arr1=[1,2,3]
let arr2=[4,5,6]
let arr3=[...arr1, ...arr2]
複製程式碼
for...of迴圈
for-of與for-in的區別
- for-of遍歷獲取的是物件的鍵值,for-in獲取的是鍵名
- for-in會遍歷物件的整個原型鏈,效能差,for...of只遍歷當前物件不會遍歷原型鏈
- 對於陣列的遍歷,for-in會遍歷所有可列舉屬性(包括原型鏈),for...of只返回陣列下標所對應的屬性值
for...of的原理
利用了遍歷物件內部的iterator介面,將for...of迴圈分解為最原始的for迴圈
對promise的理解,分別有什麼優缺點?什麼是Promise鏈?Promise建構函式執行和then函式執行有什麼區別?
- Promise有三種狀態:pending等待中,resolved完成,rejected拒絕 且一旦從等待狀態變為其他狀態就不能再次改變
- 構造Promise時,建構函式內部的程式碼是立即執行的。
- Promise實現了鏈式呼叫即每次呼叫then之後返回的都是一個全新的Promise,如果在then中使用return,那麼return的值會被Promise.resolve()包裝
你理解的Generator是什麼?
Generator可以控制函式的執行
function *foo(x){
let y=2*(yield(x+1))
let z=yield(y/3)
return (x+y+z)
}
let it=foo(5)
it.next() // {value:6,done:false}
it.next(12)
it.next(13)
複製程式碼
Generator函式呼叫會返回一個迭代器,第一次next,函式停在yield(x+1)所以value=5+1=6; 第二次next,傳入引數相當於上一個yield的值,即let y=2*12=24; let z=24/3=8;第三次next,傳入引數相當於上一個yield的值,即let z=13,y=24,x=5,相加返回42
async,await相比於promise的優勢
- 優勢在於處理then的呼叫鏈,能夠更清晰準確的寫出程式碼
- 缺點:await將非同步程式碼改造成同步,如果多個非同步程式碼之間沒有依賴性卻使用了await會導致效能降低
對async,await的理解,內部原理
async就是將函式返回值使用Promise.resolve()包裹一下,和then中處理返回值一樣,並且async只能搭配await使用 await其實就是generator加上Promise的語法糖,且內部實現了自動執行generator
setTimeout,setInterval,requestAnimationFrame各有什麼特點?
setTimeout延後執行,setInterval每隔一段時間執行一次回撥函式,以上兩種都不能保證在預期時間執行任務 requestAnimationFrame自帶函式節流功能,且延時效果是精確的
自己實現一個Promise
const PENDGIN='pending'
const RESOLVED='resolved'
const REJECTED='rejected'
function myPromise(fn){
const that=this
that.state=PENDING
that.value=null
that.resolvedCallbacks=[]
that.rejectedCallbacks=[]
function resolve(value){
if(value instanceof myPromise){
return value.then(resolve, reject)
}
setTimeout(()=>{
if(that.state===PENDING){
that.state=RESOLVED;
that.value=value;
that.resolvedCallbacks.map(cb => cb(that.value))
}
},0)
}
function reject(error){
setTimeout(()=>{
if(that.state===PENDING){
that.state=REJECTED
that.value=value
that.rejectedCallbacks.map(cb => cb(that.value))
}
},0)
}
try{
fn(resolve, reject)
}catch(e){
reject(e)
}
}
myPromise.prototype.then=function(onFulfilled, onRejected){
const that=this
onFulfilled=typeof onFulfilled==='function'?onFulfilled: v=>v
onRejected=typeof onRejected==='function'?onRejected: r=>{throw r}
if(that.state===PENDING){
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if(that.state===RESOLVED){
onFulfilled(that.value)
}
if(that.state===REJECTED){
onRejected(that.value)
}
}
new myPromise((resolve, reject) => {
setTimeout(()=>{
resolve(1)
},0)
}).then(value=>{
console.log(value)
})
複製程式碼
執行緒和程式的區別
程式描述了CPU在執行指令及載入和儲存上下文所需的事件,執行緒是更小的單位,秒速了執行一段指令所需的時間
JS單執行緒帶來的好處
因為js可以修改DOM,如果JS執行的過程UI執行緒還在工作會導致不能不能安全的渲染UI。 JS單執行緒執行,可以達到節省記憶體,節約上下文切換時間,沒有鎖的問題
什麼是執行棧
執行棧存放函式呼叫的棧結構,遵循先進後出的原則。 從底部開始放進去執行棧,然後從棧頂取出執行
微任務與巨集任務
不同的任務源會被分配到不同task佇列中,任務源可以分為微任務(microtask)和巨集任務(macrotask) 微任務包括process.nextTick, promise, MutationObserver 巨集任務包括script,setTimeout,setInterval,setImmediate,I/O,UI rendering
EventLoop執行順序
1.首先執行同步程式碼,這屬於巨集任務 2.當執行完所有同步程式碼後,執行棧為空,查詢是否有非同步程式碼需要執行 3.執行所有微任務 4.當執行完所有微任務後,如有必要會渲染頁面 5.然後開始下一輪EventLoop,執行巨集任務中的非同步程式碼,也就是setTimeout中的回撥函式
Proxy
Vue3.0其中一個核心功能就是使用Proxy替代Object.defineProperty Proxy可以在目標物件前架設一個攔截器,一般Reflect搭配,前者攔截物件,後者返回攔截的結果
Object.defineProperty的不足
1.無法探測到物件根屬性的新增和刪除 2.無法探測到對陣列基於下標的修改 3.無法探測到.length修改的監測
參考文獻:
JavaScript高階程式設計(第3版)