一、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
解決辦法:增加驗證流程,如指紋、密碼、短信碼等。