發(fā)布訂閱模式(事件總線)
描述:實現(xiàn)一個發(fā)布訂閱模式,擁有 on, emit, once, off 方法
class EventEmitter {
constructor() {
// 包含所有監(jiān)聽器函數(shù)的容器對象
// 內(nèi)部結(jié)構(gòu): {msg1: [listener1, listener2], msg2: [listener3]}
this.cache = {};
}
// 實現(xiàn)訂閱
on(name, callback) {
if(this.cache[name]) {
this.cache[name].push(callback);
}
else {
this.cache[name] = [callback];
}
}
// 刪除訂閱
off(name, callback) {
if(this.cache[name]) {
this.cache[name] = this.cache[name].filter(item => item !== callback);
}
if(this.cache[name].length === 0) delete this.cache[name];
}
// 只執(zhí)行一次訂閱事件
once(name, callback) {
callback();
this.off(name, callback);
}
// 觸發(fā)事件
emit(name, ...data) {
if(this.cache[name]) {
// 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊相同事件,會造成死循環(huán)
let tasks = this.cache[name].slice();
for(let fn of tasks) {
fn(...data);
}
}
}
}
復(fù)制代碼
原型修改、重寫
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重寫原型
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
復(fù)制代碼
可以看到修改原型的時候p的構(gòu)造函數(shù)不是指向Person了,因為直接給Person的原型對象直接用對象賦值時,它的構(gòu)造函數(shù)指向的了根構(gòu)造函數(shù)Object,所以這時候p.constructor === Object ,而不是p.constructor === Person。要想成立,就要用constructor指回來:
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
復(fù)制代碼
為什么 0.1 + 0.2 != 0.3,請詳述理由
因為 JS 采用 IEEE 754 雙精度版本(64位),并且只要采用 IEEE 754 的語言都有該問題。
我們都知道計算機表示十進制是采用二進制表示的,所以 0.1 在二進制表示為
// (0011) 表示循環(huán)
0.1 = 2^-4 * 1.10011(0011)
復(fù)制代碼
那么如何得到這個二進制的呢,我們可以來演算下
小數(shù)算二進制和整數(shù)不同。乘法計算時,只計算小數(shù)位,整數(shù)位用作每一位的二進制,并且得到的第一位為最高位。所以我們得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)。
回來繼續(xù)說 IEEE 754 雙精度。六十四位中符號位占一位,整數(shù)位占十一位,其余五十二位都為小數(shù)位。因為 0.1 和 0.2 都是無限循環(huán)的二進制了,所以在小數(shù)位末尾處需要判斷是否進位(就和十進制的四舍五入一樣)。
所以 2^-4 * 1.10011...001 進位后就變成了 2^-4 * 1.10011(0011 * 12次)010 。那么把這兩個二進制加起來會得出 2^-2 * 1.0011(0011 * 11次)0100 , 這個值算成十進制就是 0.30000000000000004
下面說一下原生解決辦法,如下代碼所示
parseFloat((0.1 + 0.2).toFixed(10))
復(fù)制代碼
事件流
事件流是網(wǎng)頁元素接收事件的順序,"DOM2級事件"規(guī)定的事件流包括三個階段:事件捕獲階段、處于目標階段、事件冒泡階段。
首先發(fā)生的事件捕獲,為截獲事件提供機會。然后是實際的目標接受事件。最后一個階段是時間冒泡階段,可以在這個階段對事件做出響應(yīng)。
雖然捕獲階段在規(guī)范中規(guī)定不允許響應(yīng)事件,但是實際上還是會執(zhí)行,所以有兩次機會獲取到目標對象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件冒泡</title>
</head>
<body>
<div>
<p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p>
</div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () { alert('父級 冒泡');}, false);parEle.addEventListener('click', function () { alert('父級 捕獲');}, true);sonEle.addEventListener('click', function () { alert('子級冒泡');}, false);sonEle.addEventListener('click', function () { alert('子級捕獲');}, true);
</script>
復(fù)制代碼
當容器元素及嵌套元素,即在捕獲階段又在冒泡階段調(diào)用事件處理程序時:事件按DOM事件流的順序執(zhí)行事件處理程序:
- 父級捕獲
- 子級捕獲
- 子級冒泡
- 父級冒泡
且當事件處于目標階段時,事件調(diào)用順序決定于綁定事件的書寫順序,按上面的例子為,先調(diào)用冒泡階段的事件處理程序,再調(diào)用捕獲階段的事件處理程序。依次alert出“子集冒泡”,“子集捕獲”。
事件是如何實現(xiàn)的?
基于發(fā)布訂閱模式,就是在瀏覽器加載的時候會讀取事件相關(guān)的代碼,但是只有實際等到具體的事件觸發(fā)的時候才會執(zhí)行。
比如點擊按鈕,這是個事件(Event),而負責(zé)處理事件的代碼段通常被稱為事件處理程序(Event Handler),也就是「啟動對話框的顯示」這個動作。
在 Web 端,我們常見的就是 DOM 事件:
- DOM0 級事件,直接在 html 元素上綁定 on-event,比如 onclick,取消的話,dom.onclick = null,同一個事件只能有一個處理程序,后面的會覆蓋前面的。
- DOM2 級事件,通過 addEventListener 注冊事件,通過 removeEventListener 來刪除事件,一個事件可以有多個事件處理程序,按順序執(zhí)行,捕獲事件和冒泡事件
- DOM3級事件,增加了事件類型,比如 UI 事件,焦點事件,鼠標事件
JS 隱式轉(zhuǎn)換,顯示轉(zhuǎn)換
一般非基礎(chǔ)類型進行轉(zhuǎn)換時會先調(diào)用 valueOf,如果 valueOf 無法返回基本類型值,就會調(diào)用 toString
字符串和數(shù)字
- "+" 操作符,如果有一個為字符串,那么都轉(zhuǎn)化到字符串然后執(zhí)行字符串拼接
- "-" 操作符,轉(zhuǎn)換為數(shù)字,相減 (-a, a * 1 a/1) 都能進行隱式強制類型轉(zhuǎn)換
[] + {} 和 {} + []
復(fù)制代碼
布爾值到數(shù)字
- 1 + true = 2
- 1 + false = 1
轉(zhuǎn)換為布爾值
- for 中第二個
- while
- if
- 三元表達式
- || (邏輯或) && (邏輯與)左邊的操作數(shù)
符號
- 不能被轉(zhuǎn)換為數(shù)字
- 能被轉(zhuǎn)換為布爾值(都是 true)
- 可以被轉(zhuǎn)換成字符串 "Symbol(cool)"
寬松相等和嚴格相等
寬松相等允許進行強制類型轉(zhuǎn)換,而嚴格相等不允許
字符串與數(shù)字
轉(zhuǎn)換為數(shù)字然后比較
其他類型與布爾類型
- 先把布爾類型轉(zhuǎn)換為數(shù)字,然后繼續(xù)進行比較
對象與非對象
- 執(zhí)行對象的 ToPrimitive(對象)然后繼續(xù)進行比較
假值列表
- undefined
- null
- false
- +0, -0, NaN
- ""
IE 兼容
- attchEvent('on' + type, handler)
- detachEvent('on' + type, handler)
代碼輸出結(jié)果
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue());
復(fù)制代碼
輸出結(jié)果:true
實際上,這段代碼就是在實現(xiàn)原型鏈繼承,SubType繼承了SuperType,本質(zhì)是重寫了SubType的原型對象,代之以一個新類型的實例。SubType的原型被重寫了,所以instance.constructor指向的是SuperType。具體如下:
基于 Localstorage 設(shè)計一個 1M 的緩存系統(tǒng),需要實現(xiàn)緩存淘汰機制
設(shè)計思路如下:
- 存儲的每個對象需要添加兩個屬性:分別是過期時間和存儲時間。
- 利用一個屬性保存系統(tǒng)中目前所占空間大小,每次存儲都增加該屬性。當該屬性值大于 1M 時,需要按照時間排序系統(tǒng)中的數(shù)據(jù),刪除一定量的數(shù)據(jù)保證能夠存儲下目前需要存儲的數(shù)據(jù)。
- 每次取數(shù)據(jù)時,需要判斷該緩存數(shù)據(jù)是否過期,如果過期就刪除。
以下是代碼實現(xiàn),實現(xiàn)了思路,但是可能會存在 Bug,但是這種設(shè)計題一般是給出設(shè)計思路和部分代碼,不會需要寫出一個無問題的代碼
class Store {
constructor() {
let store = localStorage.getItem('cache')
if (!store) {
store = {
maxSize: 1024 * 1024,
size: 0
}
this.store = store
} else {
this.store = JSON.parse(store)
}
}
set(key, value, expire) {
this.store[key] = {
date: Date.now(),
expire,
value
}
let size = this.sizeOf(JSON.stringify(this.store[key]))
if (this.store.maxSize < size + this.store.size) {
console.log('超了-----------');
var keys = Object.keys(this.store);
// 時間排序
keys = keys.sort((a, b) => {
let item1 = this.store[a], item2 = this.store[b];
return item2.date - item1.date;
});
while (size + this.store.size > this.store.maxSize) {
let index = keys[keys.length - 1]
this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
delete this.store[index]
}
}
this.store.size += size
localStorage.setItem('cache', JSON.stringify(this.store))
}
get(key) {
let d = this.store[key]
if (!d) {
console.log('找不到該屬性');
return
}
if (d.expire > Date.now) {
console.log('過期刪除');
delete this.store[key]
localStorage.setItem('cache', JSON.stringify(this.store))
} else {
return d.value
}
}
sizeOf(str, charset) {
var total = 0,
charCode,
i,
len;
charset = charset ? charset.toLowerCase() : '';
if (charset === 'utf-16' || charset === 'utf16') {
for (i = 0, len = str.length; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode <= 0xffff) {
total += 2;
} else {
total += 4;
}
}
} else {
for (i = 0, len = str.length; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode <= 0x007f) {
total += 1;
} else if (charCode <= 0x07ff) {
total += 2;
} else if (charCode <= 0xffff) {
total += 3;
} else {
total += 4;
}
}
}
return total;
}
}
復(fù)制代碼
10 個 Ajax 同時發(fā)起請求,全部返回展示結(jié)果,并且至多允許三次失敗,說出設(shè)計思路
這個問題相信很多人會第一時間想到 Promise.all ,但是這個函數(shù)有一個局限在于如果失敗一次就返回了,直接這樣實現(xiàn)會有點問題,需要變通下。以下是兩種實現(xiàn)思路
// 以下是不完整代碼,著重于思路 非 Promise 寫法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {
if (success) {
success++
if (success + errorCount === 10) {
console.log(datas)
} else {
datas.push(res.data)
}
} else {
errorCount++
if (errorCount > 3) {
// 失敗次數(shù)大于3次就應(yīng)該報錯了
throw Error('失敗三次')
}
}
})
// Promise 寫法
let errorCount = 0
let p = new Promise((resolve, reject) => {
if (success) {
resolve(res.data)
} else {
errorCount++
if (errorCount > 3) {
// 失敗次數(shù)大于3次就應(yīng)該報錯了
reject(error)
} else {
resolve(error)
}
}
})
Promise.all([p]).then(v => {
console.log(v);
});
復(fù)制代碼
說一下原型鏈和原型鏈的繼承吧
- 所有普通的 [[Prototype]] 鏈最終都會指向內(nèi)置的 Object.prototype,其包含了 JavaScript 中許多通用的功能
- 為什么能創(chuàng)建 “類”,借助一種特殊的屬性:所有的函數(shù)默認都會擁有一個名為 prototype 的共有且不可枚舉的屬性,它會指向另外一個對象,這個對象通常被稱為函數(shù)的原型
function Person(name) {
this.name = name;
}
Person.prototype.constructor = Person
復(fù)制代碼
在發(fā)生 new 構(gòu)造函數(shù)調(diào)用時,會將創(chuàng)建的新對象的 [[Prototype]] 鏈接到 Person.prototype 指向的對象,這個機制就被稱為原型鏈繼承
方法定義在原型上,屬性定義在構(gòu)造函數(shù)上
首先要說一下 JS 原型和實例的關(guān)系:每個構(gòu)造函數(shù) (constructor)都有一個原型對象(prototype),這個原型對象包含一個指向此構(gòu)造函數(shù)的指針屬性,通過 new 進行構(gòu)造函數(shù)調(diào)用生成的實例,此實例包含一個指向原型對象的指針,也就是通過 [[Prototype]] 鏈接到了這個原型對象
然后說一下 JS 中屬性的查找:當我們試圖引用實例對象的某個屬性時,是按照這樣的方式去查找的,首先查找實例對象上是否有這個屬性,如果沒有找到,就去構(gòu)造這個實例對象的構(gòu)造函數(shù)的 prototype 所指向的對象上去查找,如果還找不到,就從這個 prototype 對象所指向的構(gòu)造函數(shù)的 prototype 原型對象上去查找
什么是原型鏈:這樣逐級查找形似一個鏈條,且通過 [[Prototype]] 屬性鏈接,所以被稱為原型鏈
什么是原型鏈繼承,類比類的繼承:當有兩個構(gòu)造函數(shù) A 和 B,將一個構(gòu)造函數(shù) A 的原型對象的,通過其 [[Prototype]] 屬性鏈接到另外一個 B 構(gòu)造函數(shù)的原型對象時,這個過程被稱之為原型繼承。
** 標準答案更正確的解釋**
什么是原型鏈?
當對象查找一個屬性的時候,如果沒有在自身找到,那么就會查找自身的原型,如果原型還沒有找到,那么會繼續(xù)查找原型的原型,直到找到 Object.prototype 的原型時,此時原型為 null,查找停止。
這種通過 通過原型鏈接的逐級向上的查找鏈被稱為原型鏈
什么是原型繼承?
一個對象可以使用另外一個對象的屬性或者方法,就稱之為繼承。具體是通過將這個對象的原型設(shè)置為另外一個對象,這樣根據(jù)原型鏈的規(guī)則,如果查找一個對象屬性且在自身不存在時,就會查找另外一個對象,相當于一個對象可以使用另外一個對象的屬性和方法了。
如果new一個箭頭函數(shù)的會怎么樣
箭頭函數(shù)是ES6中的提出來的,它沒有prototype,也沒有自己的this指向,更不可以使用arguments參數(shù),所以不能New一個箭頭函數(shù)。
new操作符的實現(xiàn)步驟如下:
- 創(chuàng)建一個對象
- 將構(gòu)造函數(shù)的作用域賦給新對象(也就是將對象的proto屬性指向構(gòu)造函數(shù)的prototype屬性)
- 指向構(gòu)造函數(shù)中的代碼,構(gòu)造函數(shù)中的this指向該對象(也就是為這個對象添加屬性和方法)
- 返回新的對象
所以,上面的第二、三步,箭頭函數(shù)都是沒有辦法執(zhí)行的。
實現(xiàn)數(shù)組原型方法
forEach
語法:
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])參數(shù):
callback:為數(shù)組中每個元素執(zhí)行的函數(shù),該函數(shù)接受1-3個參數(shù)currentValue: 數(shù)組中正在處理的當前元素index(可選): 數(shù)組中正在處理的當前元素的索引array(可選):forEach()方法正在操作的數(shù)組thisArg(可選): 當執(zhí)行回調(diào)函數(shù)callback時,用作this的值。返回值:
undefined
Array.prototype.forEach1 = function(callback, thisArg) {
if(this == null) {
throw new TypeError('this is null or not defined');
}
if(typeof callback !== "function") {
throw new TypeError(callback + 'is not a function');
}
// 創(chuàng)建一個新的 Object 對象。該對象將會包裹(wrapper)傳入的參數(shù) this(當前數(shù)組)。
const O = Object(this);
// O.length >>> 0 無符號右移 0 位
// 意義:為了保證轉(zhuǎn)換后的值為正整數(shù)。
// 其實底層做了 2 層轉(zhuǎn)換,第一是非 number 轉(zhuǎn)成 number 類型,第二是將 number 轉(zhuǎn)成 Uint32 類型
const len = O.length >>> 0;
let k = 0;
while(k < len) {
if(k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
復(fù)制代碼
map
語法:
arr.map(callback(currentValue [, index [, array]])[, thisArg])參數(shù):與
forEach()方法一樣返回值:一個由原數(shù)組每個元素執(zhí)行回調(diào)函數(shù)的結(jié)果組成的新數(shù)組。
Array.prototype.map1 = function(callback, thisArg) {
if(this == null) {
throw new TypeError('this is null or not defined');
}
if(typeof callback !== "function") {
throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let newArr = []; // 返回的新數(shù)組
let k = 0;
while(k < len) {
if(k in O) {
newArr[k] = callback.call(thisArg, O[k], k, O);
}
k++;
}
return newArr;
}
復(fù)制代碼
filter
語法:
arr.filter(callback(element [, index [, array]])[, thisArg])參數(shù):
callback: 用來測試數(shù)組的每個元素的函數(shù)。返回true表示該元素通過測試,保留該元素,false則不保留。它接受以下三個參數(shù):element、index、array,參數(shù)的意義與forEach一樣。
thisArg(可選): 執(zhí)行callback時,用于this的值。返回值:一個新的、由通過測試的元素組成的數(shù)組,如果沒有任何數(shù)組元素通過測試,則返回空數(shù)組。
Array.prototype.filter1 = function(callback, thisArg) {
if(this == null) {
throw new TypeError('this is null or not defined');
}
if(typeof callback !== "function") {
throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let newArr = []; // 返回的新數(shù)組
let k = 0;
while(k < len) {
if(k in O) {
if(callback.call(thisArg, O[k], k, O)) {
newArr.push(O[k]);
}
}
k++;
}
return newArr;
}
復(fù)制代碼
some
語法:
arr.some(callback(element [, index [, array]])[, thisArg])參數(shù):
callback: 用來測試數(shù)組的每個元素的函數(shù)。接受以下三個參數(shù):element、index、array,參數(shù)的意義與 forEach 一樣。
thisArg(可選): 執(zhí)行callback時,用于this的值。
返回值:數(shù)組中有至少一個元素通過回調(diào)函數(shù)的測試就會返回 true;所有元素都沒有通過回調(diào)函數(shù)的測試返回值才會為 false。
Array.prototype.some1 = function(callback, thisArg) {
if(this == null) {
throw new TypeError('this is null or not defined');
}
if(typeof callback !== "function") {
throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
while(k < len) {
if(k in O) {
if(callback.call(thisArg, O[k], k, O)) {
return true
}
}
k++;
}
return false;
}
復(fù)制代碼
reduce
語法:
arr.reduce(callback(preVal, curVal[, curIndex [, array]])[, initialValue])參數(shù):
callback: 一個 “reducer” 函數(shù),包含四個參數(shù):
preVal:上一次調(diào)用callback時的返回值。在第一次調(diào)用時,若指定了初始值initialValue,其值則為initialValue,否則為數(shù)組索引為 0 的元素array[0]。
curVal:數(shù)組中正在處理的元素。在第一次調(diào)用時,若指定了初始值initialValue,其值則為數(shù)組索引為 0 的元素array[0],否則為array[1]。
curIndex(可選):數(shù)組中正在處理的元素的索引。若指定了初始值initialValue,則起始索引號為 0,否則從索引 1 起始。
array(可選):用于遍歷的數(shù)組。
initialValue(可選): 作為第一次調(diào)用callback函數(shù)時參數(shù)preVal的值。若指定了初始值initialValue,則curVal則將使用數(shù)組第一個元素;否則preVal將使用數(shù)組第一個元素,而curVal將使用數(shù)組第二個元素。
返回值:使用 “reducer” 回調(diào)函數(shù)遍歷整個數(shù)組后的結(jié)果。
Array.prototype.reduce1 = function(callback, initialValue) {
if(this == null) {
throw new TypeError('this is null or not defined');
}
if(typeof callback !== "function") {
throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
let accumulator = initialValue;
// 如果第二個參數(shù)為undefined的情況下,則數(shù)組的第一個有效值(非empty)作為累加器的初始值
if(accumulator === undefined) {
while(k < len && !(k in O)) {
k++;
}
// 如果超出數(shù)組界限還沒有找到累加器的初始值,則TypeError
if(k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = O[k++];
}
while(k < len) {
if(k in O) {
accumulator = callback(accumulator, O[k], k, O);
}
k++;
}
return accumulator;
}
復(fù)制代碼
類數(shù)組轉(zhuǎn)化為數(shù)組的方法
題目描述:類數(shù)組擁有 length 屬性 可以使用下標來訪問元素 但是不能使用數(shù)組的方法 如何把類數(shù)組轉(zhuǎn)化為數(shù)組?
實現(xiàn)代碼如下:
const arrayLike=document.querySelectorAll('div')
// 1.擴展運算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)
復(fù)制代碼
new 操作符
題目描述:手寫 new 操作符實現(xiàn)
實現(xiàn)代碼如下:
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
if (res && (typeof res === "object" || typeof res === "function")) {
return res;
}
return obj;
}
用法如下:
// // function Person(name, age) {
// // this.name = name;
// // this.age = age;
// // }
// // Person.prototype.say = function() {
// // console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
復(fù)制代碼
寫代碼:實現(xiàn)函數(shù)能夠深度克隆基本類型
淺克?。?/p>
function shallowClone(obj) {
let cloneObj = {};
for (let i in obj) {
cloneObj[i] = obj[i];
}
return cloneObj;
}
復(fù)制代碼
深克?。?/p>
- 考慮基礎(chǔ)類型
- 引用類型
- RegExp、Date、函數(shù) 不是 JSON 安全的
- 會丟失 constructor,所有的構(gòu)造函數(shù)都指向 Object
- 破解循環(huán)引用
function deepCopy(obj) {
if (typeof obj === 'object') {
var result = obj.constructor === Array ? [] : {};
for (var i in obj) {
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
} else {
var result = obj;
}
return result;
}
復(fù)制代碼
ES6中模板語法與字符串處理
ES6 提出了“模板語法”的概念。在 ES6 以前,拼接字符串是很麻煩的事情:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]
復(fù)制代碼
僅僅幾個變量,寫了這么多加號,還要時刻小心里面的空格和標點符號有沒有跟錯地方。但是有了模板字符串,拼接難度直線下降:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
復(fù)制代碼
字符串不僅更容易拼了,也更易讀了,代碼整體的質(zhì)量都變高了。這就是模板字符串的第一個優(yōu)勢——允許用${}的方式嵌入變量。但這還不是問題的關(guān)鍵,模板字符串的關(guān)鍵優(yōu)勢有兩個:
- 在模板字符串中,空格、縮進、換行都會被保留
- 模板字符串完全支持“運算”式的表達式,可以在${}里完成一些計算
基于第一點,可以在模板字符串里無障礙地直接寫 html 代碼:
let list = ` <ul> <li>列表項1</li> <li>列表項2</li> </ul>`;
console.log(message); // 正確輸出,不存在報錯
復(fù)制代碼
基于第二點,可以把一些簡單的計算和調(diào)用丟進 ${} 來做:
function add(a, b) {
const finalString = `${a} + $ = ${a+b}`
console.log(finalString)
}
add(1, 2) // 輸出 '1 + 2 = 3'
復(fù)制代碼
除了模板語法外, ES6中還新增了一系列的字符串方法用于提升開發(fā)效率:
(1)存在性判定:在過去,當判斷一個字符/字符串是否在某字符串中時,只能用 indexOf > -1 來做?,F(xiàn)在 ES6 提供了三個方法:includes、startsWith、endsWith,它們都會返回一個布爾值來告訴你是否存在。
- includes:判斷字符串與子串的包含關(guān)系:
const son = 'haha'
const father = 'xixi haha hehe'
father.includes(son) // true
復(fù)制代碼
- startsWith:判斷字符串是否以某個/某串字符開頭:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
復(fù)制代碼
- endsWith:判斷字符串是否以某個/某串字符結(jié)尾:
const father = 'xixi haha hehe'
father.endsWith('hehe') // true
復(fù)制代碼
(2)自動重復(fù):可以使用 repeat 方法來使同一個字符串輸出多次(被連續(xù)復(fù)制多次):
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3)
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
復(fù)制代碼
iframe 有那些優(yōu)點和缺點?
iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)。
優(yōu)點:
- 用來加載速度較慢的內(nèi)容(如廣告)
- 可以使腳本可以并行下載
- 可以實現(xiàn)跨子域通信
缺點:
- iframe 會阻塞主頁面的 onload 事件
- 無法被一些搜索引擎索識別
- 會產(chǎn)生很多頁面,不容易管理
const對象的屬性可以修改嗎
const保證的并不是變量的值不能改動,而是變量指向的那個內(nèi)存地址不能改動。對于基本類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),其值就保存在變量指向的那個內(nèi)存地址,因此等同于常量。
但對于引用類型的數(shù)據(jù)(主要是對象和數(shù)組)來說,變量指向數(shù)據(jù)的內(nèi)存地址,保存的只是一個指針,const只能保證這個指針是固定不變的,至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就完全不能控制了。