前端JavaScript面試技巧

一、JS基礎知識

1.變量類型和計算

值類型

var a = 100 
var b = a
a = 200
console.log(b)  // 100

引用類型:(對象、組數、函數):

var a = {age: 20}
var b = a
b.age = 21
console.log(a.age)  // 21

特點:無限添加屬性。設計原因:該三種類型可能較大,比較佔用內存。

涉及問題:JS變量按照存儲方式區分爲哪些類型,並描述其特點

答案:值類型和引用類型;引用類型賦值會導致所有對該對象引用的值都發生變化。

 

typeof運算符

typeof undefined    // undefined
typeof 'abc'    // string
typeof 123    // number
typeof true    // boolean
typeof {}    // object
typeof []    // object
typeof null    // object
typeof console.log()    // function

注意“null”對象原型爲object。

“typeof”只能識別值類型,但無法判斷引用類型,但function例外。

 

字符串拼接

var a = 100 + 10    //110
var b = 100 + '10'  //10010

“==”運算符

100 == '100'    //true
0 == ''  //true
null == undefined    //true

若以上均換成“===”,結果均爲false。

涉及問題:何時使用“===”,何時使用“==”

除了以下用法,其他情況均使用“===”:

if (obj.a == null) {
    // 這裏相當於 obj.a === null || obj.a === undefined , 簡寫形式
    // 這是jQuery源碼中推薦的寫法
    // 大概原因是null是引用類型;undefined爲值類型
}

if語句:(會對判斷內容強轉換)

var a = true
if (a) {}   //執行
var b = 100
if (b) {}   //執行
var c = ''
if (c) {}   //不執行

邏輯運算符:

console.log(10 && 0)   // 0
console.log('' || 'abc')   // 'abc'
console.log(!window.adb)   // true

var a = 100
console.log(!!a)   // true (!!a轉換成Boolean類型)

 

問題:JS中有哪些內置函數 —— 數據封裝對象。(都是創建特定類型的函數)

Object
Array
Boolean
Number
String
Function
Date
RegExp
Error

問題:如何理解JSON

答案:只是JS的對象。也是一個數據格式。

// JSON兩個常用內置函數
JSON.stringifg({a:10, b:20})    //轉化成字符串
JSON.parse('{a:10, b:20}')  //解釋字符串爲對象

 

2.原型和原型鏈

構造函數

function Foo(name, age) {
    this.name = name
    this.age = age
    this.class = 'class-1'
    // return this //默認有這行(因爲創建對象的時候就是返回this)
}
var f = new Foo('zhangsan', 20)
// var f1 = new Foo('lisi', 21)

var a = {} 其實是 var a = new Object() 的語法糖。其他類型也是。

問題:如何判斷一個變量是否爲“數組”

答案:使用”a instanceof Object”得到布爾值。

 

關於 引用對象

1、所有的引用類型都具有對象特性,即可以自由擴展屬性(“null”除外

var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

2、所有的引用類型都有一個”__proto__屬性(隱式原型),屬性值是一個普通的對象

console.log(obj.__proto__);    // {}
console.log(arr.__proto__);    // []
console.log(fn.__proto__);    // [Function]

3、所有的函數都有一個”prototype屬性(顯示原型),屬性值也是一個普通的對象

console.log(fn.prototype);    // undefined

4、“__proto__”屬性值指向它的構造函數的“prototype”屬性值

console.log(obj.__proto__ === Object.prototype) // true

5、當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的__proto__(即它的構造函數的prototype)中尋找

// 構造函數
function Foo(name) {
    this.name = name
}
// 擴展 構造函數"FOO"的prototype的屬性
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 創建實例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 測試
f.printName()   // 執行console.log
f.alertName()   // 執行alert

(針對上實例)循環對象自身的屬性:

var item
for (item in f) {
    // 高級瀏覽器已經在 for in 中屏蔽了來自原型的屬性
    // 但爲了保證程序的健壯性,還是建議使用 .hasOwnPrototype() 判斷是否屬於自身屬性
    if (f.hasOwnPrototype(item)) {
        console.log(item)
    }
}

簡單的原型鏈例子:

// 構造函數
function Foo(name) {
    this.name = name
}
// 擴展構造函數"FOO"的prototype的屬性
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 創建實例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 測試
f.printName()   // 執行console.log
f.alertName()   // 執行alert
f.toString()    // 要去f.__proto__.__proto__ 中查找

上實例的邏輯如下圖:

即,子類的__proto__指向父類的prototype。

給子類的__proto__添加新屬性/方法,其他同屬兄弟子類均可用該屬性/方法;

給父類的prototype添加新屬性/方法,其子類均可用該屬性/方法。

 

問題:如何準確判斷一個變量時數組類型

var arr = []
arr instanceof Array    // true

問題:寫一個原型鏈繼承的例子

function Ele (id) {
    this.ele = document.getElementById(id)
}
Ele.prototype.html = function (val) {
    var ele = this.ele  // 獲取上級的ele
    if (val) {
        ele.innerHTML = val
    }
    return this     // 鏈式操作
}
Ele.prototype.on = function (e, fn) {
    var ele = this.ele
    if (!!e && !!fn) {
        ele.addEventListener(e, fn)
    }
    return this
}
var div1 = new Ele('test')
div1.html('這是修改後的內容').on('click', function () {
    alert('觸發單擊事件')
})

問題:描述new一個對象的過程

答案:  1、創建一個新對象

             2、this指向這個新對象

             3、執行代碼,即對this賦值

             4、返回this

 

問題:zepto(或其他框架)源碼中如何使用原型鏈

答案:

 

3.作用域和閉包

“this”

“this”要在執行時才能確認值,定義時無法確認。

var obj = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
obj.fn()    // this === obj
obj.fn.call({name:'B'}) // this === {name:'B'}
var fn1 = obj.fn()
fn1()   // this === window

“var”定義變量,無塊級作用域:

// 無塊級作用域
if (ture) {
    var name = 'zhangsan'
}
console.log(name)   // zhangsan

閉包:

閉包必要條件

1、內部函數應訪問其外部函數聲明的私有變量、參數或其他函數內部函數。

2、外部函數“return”該內部函數

3、定義與該外部函數同級的變量,把該外部函數賦值給它。

function fn (x) {
    var a = x
    var b = function() {
        return a    // 訪問外部變量a
    }
    a++
    return b	// 需要把閉包體拋出
}
var c = fn(10)
console.log(c)    // 11
// 無閉包的情況
function fn1 (x) {
    var a1 = x
    var b1 = a1
    a1++
    return b1
}
var c1 = fn1(10)
console.log(c1)  // 10

問題:創建5個<a>標籤,點擊彈出對應的序號

var fragment = document.createDocumentFragment()
// 若使用'let'定義i,可以不使用閉包
for (var i = 0; i < 10; i++) {
    (function (i) {
        var a = document.createElement('a')
        a.innerHTML = i + 1 + '<br>'
        a.addEventListener('click', function(e) {
            e.preventDefault()  // 取消事件的默認行爲
            alert(i + 1)
        })
        fragment .appendChild(a)
    }) (i)   // 接收 i 並把 i 作爲私有變量
}
document.body.appendChild(fragment)

問題:實際開發中閉包的應用。(封裝變量,收斂權限)

// 閉包實際應用中主要用於 封裝變量,收斂權限
function isFristLoad () {
    var _list = []
    return function (id) {
        if (_list.indexOf(id) > -1) {
            return false
        } else {
            _list.push(id)
            return true
        }
    }
}
// 使用
var fl = isFristLoad()
fl(10)  //true
fl(10)  //false
fl(20)  //true

 

4.異步和單線程

JS是 異步 單線程

前端使用異步的場景:

1、定時任務:setTimeout、setInterval

2、網絡請求:Ajax請求、動態<img>加載

3、事件綁定

 

問題:同步和異步的區別是什麼

答案:同步會阻塞代碼執行,而異步不會。(alert和setTimeout)

 

問題:一個關於setTimeout的筆試題

console.log(1)
setTimeout(() => {
    console.log(2)
}, 0);
setTimeout(() => {
    console.log(3)
}, 1000);
console.log(4)

答案:1 → 4 → 2 → 3 。

 

5.其他知識點

日期

var dt = new Date()
dt.getTime()    // 獲取毫秒數,與Date.now()一樣
dt.getFullYear()    //年
dt.getMonth() + 1   // 月
dt.getDate()    // 日
dt.getHours()   // 小時
dt.getMinutes() // 分
dt.getSeconds() // 秒

涉及 問題:獲取 2017-06-10格式的日期

思路:主要涉及判斷“月”和“日”小於10需要拼接“0”。

 

“Math”方法:

// 獲取隨機數 (前端多用於判斷清除緩存)
Math.random()   // 0到1之間的隨機數

涉及問題:獲取隨機數,要求是長度一致的字符串格式

思路:拼接數個“0”,再用” .slice(0, n) ”得到指定長度。

 

數組API

var arr = [1, 4, 2, 3, 5]

forEach 遍歷所有元素

arr.forEach((item, index) => {
    console.log(index, item)
})

every 判斷所有元素是否都符合條件

var result = arr.every((item, index) => {
    if (item < 5) {
        return ture
    }
})
console.log(result) // false

some 判斷是否至少一個元素符合條件

var result1 = arr.some((item, index) => {
    if (item < 5) {
        return true
    }
}) 
console.log(result1)    // true

sort 排序

var arr2 = arr.sort((a, b) => {
    return a-b  // 升序
    // return b-a  // 降序
})
console.log(arr2)   // [1, 2, 3, 4, 5]

map 對元素重新組裝,生成新數組

var arr3 = arr.map((item, indxe) => {
    return '<b>' + item + '</b>'
})
console.log(arr3)   // 每個元素都包裹了b標籤

filter 過濾符合條件的元素,生成新數組

var arr4 = arr.filter((item, index) => {
    if (item > 2) {
        return true
    }
})
console.log(arr4)   // [4, 3, 5]

 

對象API

只有一個for…in循環:

var obj = {a:100, b:200, c:300}
for (var key in obj) {
    if (obj.hasOwnPrototype(key)) {
        console.log(key, obj[key])
    }
}

涉及問題:寫一個能遍歷對象和數組的通用forEach函數

思路:自定義函數內先用 instanceof 判斷類型,入口參數爲對象+回調函數,再用forEach/for遍歷。

 

 

二、JS-Web-API

常說的JS(瀏覽器執行的JS)包含兩部分:

       1、JS基礎知識(ECMA262標準

       2、JS-Web-API(W3C標準

1.DOM

數據結構:樹狀結構。       HTML代碼本質是字符串。

DOM可以理解爲:

瀏覽器把拿到的HTML代碼結構化一個瀏覽器能識別並且JS可操作的一個模型。

獲取DOM

var ele1 = document.getElementById('id')  // 元素
var ele2 = document.getElementsByTagName('div')  // 集合
var ele3 = document.getElementsByClassName('class')  // 集合
// .querySelectorAll入口參數爲CSS選擇器的字符串
var ele4 = document.querySelectorAll('div.note, div.alert')  // 集合

property:(JS對象的屬性)

ele1.style.width = '100px'  // 修改樣式
ele1.classNmae = 'ele1'     // 設置自定義屬性

attribute:(DOM元素的屬性)

ele1.getAttribute('style')
ele1.setAttribute('style', 'font-size:12px')

涉及問題:DOM節點的attribute和property有何區別

答案:property只是一個JS對象的屬性修改/刪除;attribute是對html標籤屬性的修改/刪除。

 

新增節點

var div1 = document.getElementById('div1')
// 添加新節點
var p1 = document.createElement('p')
p1.innerHTML = 'this is new p'
div1.appendChild(p1)    // 添加新創建的元素
// 移動已有節點(原節點被刪除)
var p2 = document.getElementById('p2')
div1.appendChild(p2)

獲取父元素/子節點:(.parentElement 和 .childNodes)

var div2 = document.getElementById('div2')
var parent = div2.parentElement
var child = div2.childNodes     // 集合(nodeList)
/// 加入子元素中含有空格/回車之類的字符,得到的集合會多若干個text的子元素
// 解決辦法:使用nodeType判斷子節點的類型
console.log(child[0].nodeType)  // text或其他元素類型

刪除節點

div2.removeChild(child[0])      // 刪除第一個子元素

問題:DOM操作的常用API有哪些

答案:  1、獲取DOM節點/屬性:.getElementById(還有class和tag)、.querySelectorAll、.getAttribute

              2、獲取父元素/子節點:.parentElements、.childNodes

              3、新增/刪除節點:.appendChild()、.removeChild()

 

2.BOM

// navigator (用於判斷瀏覽器)
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
// screen
console.log(screen.width)
console.log(screen.height)
// location
console.log(location.protocol)  // "https:"
console.log(location.host)  // "www.baidu.com"
console.log(location.pathname)  // "/s"
console.log(location.search)  // "?wd=abc&..."
console.log(location.hash)  // "#mid=100" 用於定位某元素位置
// location.href 可以賦值新地址
console.log(location.href)  // "https://www.baidu.com/s?wd=abc...#mid=100"
// history
history.back()  // 返回
history.forward()   // 前進

 

3.事件

自定義通用事件綁定的函數

var elem = document.getElementById('id')
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}

事件冒泡

<body>
    <div id="div1">
        <p id="p1">激活</p>
        <p id="p2">取消</p>
    </div>
    <div id="div2">
        <p id="p3">激活</p>
        <p id="p4">取消</p>
    </div>
</body>
<script>
    var p1 = document.getElementById('p1')
    var body = document.body
    bindEvent(p1, 'click', function(e) {
        // 阻止事件冒泡,否則會執行body的click
        e.stopPropagation()
        alert('激活')
    })
    bindEvent(body, 'click', function(e) {
        alert('取消')
    })
</script>

代理:(好處:代碼簡潔;減少瀏覽器內存佔用)

<div id="div3">
    <!-- 每個<a>都帶有指定click -->
    <a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
<a href="#">a5</a>
<a href="#">a6</a>
    <!-- 會隨時增加更多<a> -->
</div>
<script>
    var div3 = document.getElementById('div3')
    div3.addEventListener('click', function(e) {
        // 阻止click事件的默認行爲
        e.preventDefault()
        // .target獲取觸發事件的元素
        // .nodeType獲取元素的類型
        if (e.target.nodeType === 'a') {
            alert(e.target.innerHTML)
        }
    })
</script>

問題:簡述事件冒泡流程

答案:沿着DOM樹往上依次觸發該事件。

 

4.Ajax

XMLHtmlRequest:(必須能默寫

// 創建XMLHttpRequest對象
var xhr = new XMLHttpRequest()
// 使用xhr的open方法,第三個參數的(默認值)true表示使用 異步
// 只有異步纔會調用onreadystatechange指定的函數
xhr.open('GET',  '/api', true)
// 使用xhr的監聽事件onreadystatechange
xhr.onreadystatechange = function () {
    // readyState每次改變都會調用onreadystatechange的方法
    // "readyState = 4"表示內容解析完成,可在客戶端調用
    if (xhr.readyState = 4) {
        // responseText表示響應內容,2xx:成功;3xx:重定向;4xx:請求錯誤;5xx:服務器錯誤
        if (xhr.responseText = 200) {
            alert(xhr.responseText)
        }
    }
}
// xhr發送的內容,沒有可寫null
xhr.send(null)

 

跨域

瀏覽器有同源策略,不允許Ajax訪問其他域接口。

跨域條件:協議、域名、端口、有一個不同算跨域。

可跨域的三個標籤:<img src=””>、<link href=””>、<script src=”” >。

 

JSONP

定義:利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。

<script>
window.callback = function (data) {
    console.log(data)
}
</script>
<!-- 以下將返回callback({x:100; y:200}) -->
<script src="http://coding.test.com.api.js"></script>

 

5.存儲

cookie

本身用於客戶端和服務端通信。但它有本地存儲的功能,於是就被“借用”。

使用document.cookie = ...... 獲取和修改。

缺點:

1、存儲量太少,只有4KB;

2、所有http請求都帶着,會影響獲取資源的效率;

3、API簡單,需要封裝太能用document.cookie = ...... 。

 

localStorage和sessionStorage

優點:

       1、HTML5專門爲存儲而涉及,最大容量5M;

       2、API簡單易用:localStorage.getItem(key, value);localStorage.setItem(key)

sessionStorage與localStorage使用方法一樣,唯一區別在於重啓瀏覽器後會刪除sessionStorage

注意事項

iOS safari的隱藏模式下使用localStorage.getItem會報錯,建議統一使用try-catch封裝。

 

問題:請描述以下cookie、sesionStorage、localStorage的區別

答案:  1、容量:cookie只有4k;其餘兩者有5M。

              2、是否攜帶到Ajax中:cookie每次都會;其餘兩者不會。

              3、API易用性:cookie需要自己封裝數據成字符串;其餘兩者有.getItem和.setItem。

 

 

三、關於開發環境

1.IDE

webstorm(收費)
sublime
VScode
atom(git hub的開源軟件)
基本都依賴插件。

 

2.Git

常用git命令

git status                      // 獲取當前git的狀態(可看到處於什麼分支,哪些文件被修改)
git add .                       // 把所有修改的文件上傳(“.”改爲文件名可單獨上傳,多個文件用空格隔開)
git checkout xxx           // 以線上文件還原“xxx”文件
git commit -m “xxx”     // 把需要上傳的文件提交到本地緩存,-m “xxx”表示上傳時的預留信息
git push origin master  // 把緩存在本地的文件推到git服務器(忽略origin master可以操作當前分支)
git pull origin master    // 把git服務器的文件拉到本地(忽略origin master可以操作當前分支)
git branch                    // 獲取所有分支名
git checkout -b xxx      // 切換到“xxx”分支(“-b”可以省略),不存在則自動創建
git merge xxx               // 把”xxx”分支內容合併到本分支
git clone                      // 用於無項目代碼的電腦下載代碼

 

3.模塊化

① AMD(規範)

定義:全稱是Asynchronous Module Definition,即異步模塊加載機制。

工具:require.js

僅有2個API:define()require()。第一個參數爲依賴文件(無則不填);第二個參數模塊的實現函數。

// 【index.html】 require.js爲插件,data-main爲引入模塊主入口文件
</script src="./require.js" data-main="./main.js"></script>

// 【unit.js】
define(function () {
    // 定義模塊,格式:model = { function(){} }
    var getFormaDate = {
        function (date, type) {
            if (type === 1) {
                return '2018-10-03' // 實際需要處理date
            } else {
                return '2018年10月03日'
            }
        }
    }
    return getFormatDate    // 拋出模塊對象
})
// 【a-unit.js】
define(['./unit.js'], function (unit) {
    var aGetFormatdate = {
        function (date) {
            return unit.getFormatDate(date, 1)
        }
    }
    return aGetFormatdate
})
// 【main.js】
var dt = new Date()
require(['./aUnit.js'], function (aUnit) {
    console.log(aUnit.aGetFormatdate(dt))
})

② CommonJS

Nodejs模塊化的規範,一般和npm一起使用,但不會異步加載JS。

與AMD類似,只有兩個API:exports和require。

// 【unit.js】
module.exprots = {
    getFormatDate: function (date, type) {
        if (type === 1) {
            return '2018-10-03' // 實際需要處理date
        } else {
            return '2018年10月03日'
        }
    }
}
// 【a-unit.js】
var unit = require('./unit.js')
module.exprots = {
    aGetFormatdate: function (date) {
        return unit.getFormatDate(date, 2)
    }
}

③ 回滾流程要點

1、將當前服務器的代碼打包並記錄版本號,備份
2、將備份的上一個版本號解壓,覆蓋到線上服務器,並生成新的版本號

 

④ Linux基本命令

ssh name@server         //“name”爲登陸賬號名,“server”爲服務地址,回車後會提示輸入密碼
mkdir xxx                     // 創建文件夾xxx
ls/ll                              // 打印當前目錄下的所有文件,“ll“以列形式顯示
cd xxx                          // 定位路徑到xxx
rm -rf xxx                     // 刪除xxx文件
vi xxx                           // 進入vi編輯器。“i“插入模式;”esc“命令模式;”:“底行模式。 “:wq“保存並退出。
mv xxx newPath/xxx     // 移動xxx文件到newPath下並命名爲xxx
cat xxx                         // 查看xxx文件的內容
head -n 2 xxx               // 查看xxx文件的頭2行,“-n 2“可省略
tail -n 2 xxx                  // 查看xxx文件的尾2行,“-n 2“可省略
grep ‘a’ xxx                 // 搜索xxx文件內包含“a“的內容

 

四、運行環境

1.頁面加載

加載資源的形式

輸入url:http://www.baidu.com

加載html中的靜態資源:<script src=”./static/js/jquery.js”><script>

 

瀏覽器渲染頁面的過程

1、根據HTML結構生成DOM Tree

2、根據CSS生成CSSOM

3、將DOM和CSSOM整合成RenderTree

4、根據RenderTree開始渲染和展示

 

問題:從輸入url到得到html的詳細過程

答案:

1、瀏覽器根據DNS服務器得到域名的IP地址

2、向這個IP的機器發送http請求

3、服務器收到、處理並返回http請求

4、瀏覽器得到返回內容

 

問題:window.onload和DOMContentLoaded的區別

答案:當DOM樹構建完成的時候就會執行DOMContentLoaded事件。當頁面上所有資源(DOM,樣式表,腳本,圖片,flash等)都已經加載完成後,執行window.onload事件

window.addEventListener('load', function() {
    // 頁面所有資源加載完成後執行
})
window.addEventListener('DOMContentLoaded', function() {
    // DOM樹構建完成後執行
})

 

2.性能優化

原則

1、多使用內存、緩存或者其他方法

2、減少CPU計算、減少網絡請求

 

加載資源優化

1、靜態資源的壓縮合並

2、靜態資源緩存使用CDN讓資源加載更快

3、使用SSR(後端渲染),數據直接輸出HTML中

 

渲染優化

1、CSS放前面,JS放後面

2、懶加載(圖片懶加載、下拉加載更多)

3、減少DOM查詢,對DOM查詢做緩存

4、減少DOM操作,多個操作儘量合併在一起執行

5、事件節流

6、儘早執行操作(如DOMContentLoaded)

 

懶加載

<img id="img1" src="./preview.png" data-big="./phtot.jpg">
<script>
    var img1 = document.getElementById('img1')
    img1.addEventListener('click', function () {
        img1.src = img1.getAttribute('data-big')
    })
</script>

緩存DOM查詢:( 用變量存放常用DOM對象 )

var i
// 未緩存 DOM 查詢
for (i=0; i < document.getElementById('id').length; i++) {
    // todo
}
// 緩存了 DOM 查詢
var id = document.getElementById('id')
for (i = 0; i < id.length; i++) {
    // todo
}

合併DOM插入:( 利用document.createDocumentFragment()

var listNode = document.getElementById('list')
// 存放 待插入的10個li標籤
var fragment = document.createDocumentFragment()
var i, li
for (i = 0; i < 10; i++) {
    li = document.createElement('li')
    li.innerHTML = 'num:' + i
    fragment.appendChild(li)
}
// 一次性插入所有li標籤
listNode.appendChild(fragment)

事件節流:( 利用setTimeout() )

var textarea = document.getElementById('text')
var timer   // 存放定時器對象
textarea.addEventListener('keyup', function () {
    if (timer) {
        clearTimeout(timer)
    }
    timer = setTimeout(() => {
        // 觸發 change 事件
    }, 100)
})

 

3.安全性

XSS

解決辦法:用轉義字符替換關鍵字。

 

XSRF

解決辦法:增加驗證流程,如指紋、密碼、短信碼等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章