1. JS為什么單線程
一個簡單的原因就是,js
在設計之初只是進行一些簡單的表單校驗,這完全不需要多線程,單線程完全可以勝任這項工作。即便后來前端發展迅速,承載的能力越來越多,也沒有發展到非多線程不可的程度。
而且還有一個主要的原因,設想一下,如果js
是多線程的,在運行時多個線程同時對DOM
元素進行操作,那具體以哪個線程為主就是個問題了,線程的調度問題是一個比較復雜的問題。
HTML5
新的標準中允許使用new Worker
的方式來開啟一個新的線程,去運行一段單獨的js
文件腳本,但是在這個新線程中嚴格的要求了可以使用的功能,比如說他只能使用ECMAScript
, 不能訪問DOM
和BOM
。這也就限制死了多個線程同時操作DOM
元素的可能。
2.使用css寫出一個三角形角標
元素寬高設置為0
,通過border
屬性來設置,讓其它三個方向的border
顏色為透明或者和背景色保持一致,剩余一條border
的顏色設置為需要的顏色。
div {
width: 0;
height: 0;
border: 5px solid #transparent;
border-top-color: red;
}
3.水平垂直居中
我一般只使用兩種方式定位
或者flex
,我覺得夠用了。
div {
width: 100px;
height: 100px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
父級控制子集居中
.parent {
display: flex;
justify-content: center;
align-items: center;
}
4. css一行文本超出...
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
5.多行文本超出顯示...
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
6.IOS手機容器滾動條滑動不流暢
overflow: auto;
-webkit-overflow-scrolling: touch;
7.修改滾動條樣式
隱藏div
元素的滾動條
div::-webkit-scrollbar {
display: none;
}
div::-webkit-scrollbar 滾動條整體部分
div::-webkit-scrollbar-thumb 滾動條里面的小方塊,能向上向下移動(或往左往右移動,取決于是垂直滾動條還是水平滾動條)
div::-webkit-scrollbar-track 滾動條的軌道
div::-webkit-scrollbar-button 滾動條的軌道的兩端按鈕,允許通過點擊微調小方塊的位置。
div::-webkit-scrollbar-track-piece 內層軌道,滾動條中間部分
div::-webkit-scrollbar-corner 邊角,即兩個滾動條的交匯處
div::-webkit-resizer 兩個滾動條的交匯處上用于通過拖動調整元素大小的小控件
注意此方案有兼容性問題,一般需要隱藏滾動條時我都是用一個色塊通過定位蓋上去,或者將子級元素調大,父級元素使用overflow-hidden截掉滾動條部分。暴力且直接。
8.解決ios audio無法自動播放、循環播放的問題
ios
手機在使用audio
或者video
播放的時候,個別機型無法實現自動播放,可使用下面的代碼hack
。
// 解決ios audio無法自動播放、循環播放的問題
var music = document.getElementById('video');
var state = 0;
document.addEventListener('touchstart', function(){
if(state==0){
music.play();
state=1;
}
}, false);
document.addEventListener("WeixinJSBridgeReady", function () {
music.play();
}, false);
//循環播放
music.onended = function () {
music.load();
music.play();
}
9.隱藏頁面元素
display-none: 元素不會占用空間,在頁面中不顯示,子元素也不會顯示。
opacity-0: 元素透明度將為0
,但元素仍然存在,綁定的事件仍舊有效仍可觸發執行。
visibility-hidden:元素隱藏,但元素仍舊存在,占用空間,頁面中無法觸發該元素的事件。
10.前端工程化
一提到前端工程化很多人想到的都是webpack
,這是不對的,webpack
僅僅是前端工程化中的一環。在整個工程化過程中他幫我們解決了絕大多數的問題,但并沒有解決所有問題。
前端工程化是通過工具提升效率,降低成本的一種手段。
近些年被廣泛的關注和探討,究其原因主要是因為現代化前端應用功能要求不斷提高,業務邏輯日益復雜,作為當下互聯網時代唯一不可或缺的技術,前端可以說是占據了整個開發行業的半壁江山。從傳統的網站,到現在的H5
,移動App
,桌面應用,以及小程序。前端技術幾乎是無所不能的全面覆蓋。
在這些表象的背后呢,實際上是行業對開發人員的要求發生了天翻地覆的變化,以往前端寫demo,套模板,調頁面這種刀耕火種的方式已經完全不符合當下對開發效率的要求,前端工程化就是在這樣一個背景下被提上臺面,成為前端工程師必備的手段之一。
一般來說前端工程包含,項目初始化,項目開發,提交,構建,部署,測試,監控等流程。工程化就是以工程的角度來解決這些問題。比如項目初始化我們一般使用npm init
, 創建頁面模板使用plop
,我們喜歡使用ES6+
開發,但是需要通過babel
編碼成ES5
,持續集成的時候我們使用git/ci cd
,但是為了保持開發規范我們引入了ESLint
,部署一般使用git/cd
或者jenkins
等等。這里都是前端寶藏
11.contenteditable
html
中大部分標簽都是不可以編輯的,但是添加了contenteditable
屬性之后,標簽會變成可編輯狀態。
<div contenteditable="true"></div>
不過通過這個屬性把標簽變為可編輯狀態后只有input
事件,沒有change
事件。也不能像表單一樣通過maxlength
控制最大長度。我也忘記我在什么情況下用到過了,后面想起來再補吧。
12.calc
這是一個css
屬性,我一般稱之為css
表達式。可以計算css
的值。最有趣的是他可以計算不同單位的差值。很好用的一個功能,缺點是不容易閱讀。接盤俠沒辦法一眼看出20px
是啥。
div {
width: calc(25% - 20px);
}
13.Date對象
獲取當前時間毫秒值
// 方式一
Date.now(); // 1606381881650
// 方式二
new Date() - 0; // 1606381881650
// 方式三
new Date().getTime() // 1606381881650
創建Date
對象的兼容性問題。
// window和安卓支持,ios和mac不支持
new Date('2020-11-26');
// window和安卓支持,ios和mac支持
new Date('2020/11/26');
14.Proxy和Object.defineProperty區別
Proxy
的意思是代理,我一般叫他攔截器,可以攔截對象上的一個操作。用法如下,通過new
的方式創建對象,第一個參數是被攔截的對象,第二個參數是對象操作的描述。實例化后返回一個新的對象,當我們對這個新的對象進行操作時就會調用我們描述中對應的方法。
new Proxy(target, {
get(target, property) {
},
set(target, property) {
},
deleteProperty(target, property) {
}
})
Proxy
區別于Object.definedProperty
。
Object.defineProperty
只能監聽到屬性的讀寫,而Proxy
除讀寫外還可以監聽屬性的刪除,方法的調用等。
通常情況下我們想要監視數組的變化,基本要依靠重寫數組方法的方式實現,這也是Vue
的實現方式,而Proxy
可以直接監視數組的變化。
const list = [1, 2, 3];
const listproxy = new Proxy(list, {
set(target, property, value) {
target[property] = value;
return true; // 標識設置成功
}
});
list.push(4);
Proxy
是以非入侵的方式監管了對象的讀寫,而defineProperty
需要按特定的方式定義對象的屬性。
15.Reflect
他是ES2015
新增的對象,純靜態對象也就是不能被實例畫,只能通過靜態方法的方式調用,和Math
對象類似,只能類似Math.random()
的方式調用。
Reflect
內部封裝了一系列對對象的底層操作,一共14
個,其中1
個被廢棄,還剩下13
個。
Reflect
的靜態方法和Proxy
描述中的方法完全一致。也就是說Reflect
成員方法就是Proxy
處理對象的默認實現。
Proxy
對象默認的方法就是調用了Reflect
內部的處理邏輯,也就是如果我們調用get
方法,那么在內部,Reflect
就是將get
原封不動的交給了Reflect
,如下。
const proxy = new Proxy(obj, {
get(target, property) {
return Reflect.get(target, property);
}
})
Reflect
和Proxy
沒有絕對的關系,我們一般將他們兩個放在一起講是為了方便對二者的理解。
那為什么會有Reflect
對象呢,其實他最大的用處就是提供了一套統一操作Object
的API
。
判斷對象是否存在某一個屬性,可以使用in
操作符,但是不夠優雅,還可以使用Reflect.has(obj, name)
; 刪除一個屬性可以使用delete
,也可以使用Reflect.deleteProperty(obj, name)
; 獲取所有屬性名可以使用Object.keys
, 也可以使用Reflect.ownKeys(obj)
; 我們更推薦使用Reflect
的API
來操作對象,因為他才是未來。
16.解析get參數
通過replace
方法獲取url
中的參數鍵值對,可以快速解析get
參數。
const q = {};
location.search.replace(/([^?&=]+)=([^&]+)/g,(_,k,v)=>q[k]=v);
console.log(q);
17.解析連接url
可以通過創建a
標簽,給a
標簽賦值href
屬性的方式,獲取到協議
,pathname
,origin
等location
對象上的屬性。
// 創建a標簽
const aEle = document.createElement('a');
// 給a標簽賦值href路徑
aEle.href = '/test.html';
// 訪問aEle中的屬性
aEle.protocol; // 獲取協議
aEle.pathname; // 獲取path
aEle.origin;
aEle.host;
aEle.search;
...
18.localStorage
localStorage
是H5
提供的永久存儲空間,一般最大可存儲5M
數據,并且支持跨域隔離,他的出現極大提高了前端開發的可能性。localStorage
的使用很多人都知道setItem
,getItem
,removeItem
, 但他也可以直接以成員的方式操作。
// 存儲
localStorage.name = 'yd';
// 獲取
localStorage.name; // yd
// 刪除
delete localStorage.name;
// 清除全部
localStorage.clear();
// 遍歷
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); // 獲取本地存儲的Key
localStorage[key]; // 獲取本地存儲的value
}
localStorage
滿了的情況下仍繼續存儲并不會覆蓋其他的值,而是直接報錯(QuotaExceededError
),并且當前存儲的值也會被清空。瀏覽器支持每個域名下存儲5M
數據。
19.sessionStorage
sessionStorage
和localStorage
的區別是,存在當前會話,很多人理解的是瀏覽器關閉,這是不對的,假設你在A
頁面存儲了sessionStorage
,新開選項卡將A
頁面的鏈接粘貼進去打開頁面,sessionStorage
也是不存在的。
所以sessionStorage
存在的條件是頁面間的跳轉,A
頁面存儲了sessionStorage
,他要通過超鏈接
或者location.href
或者window.open
來打開另一個同域頁面才能訪問sessionStorage
。
這一點在混合開發嵌套H5
的開發模式中尤為重要,如果以新開webview
的方式打開頁面,很可能sessionStorage
就沒有了。
20.會話cookie
cookie
在設置的時候如果不設置過期時間,就表示是個會話cookie
,以前我以為關閉瀏覽器會話cookie
就消失了,然而...喜提bug
一個。
在多數情況下windows
系統或者安卓系統確實是這樣的。但是在macOS
系統或者ios
系統中,關閉瀏覽器并不會清除掉會話cookie
,結束瀏覽器進程才行。
21.標簽模板字符串
模板字符串支持在前面添加一個函數,第一個參數是一個有固定內容組成的數組,后面參數依次為傳入的變量,函數返回值為模板字符串真正展示的值。不過這個功能個人感覺沒啥用。
const tag = (params, ...args) => {
return params[0] + args[0]; // 返回值為模板字符串的真實值。
}
const str = tag`hello ${'world'}`;
22.字符串常用的幾個方法
1. includes();
字符串中是否包含某個字符串,這個不說了,其實就是indexOf
的替代方案,用起來更優雅,
2. startsWith();
字符串是否為某個字符串開始,我一般用它判斷url
是否有http
3. endsWith();
字符串是否為某個字符串結尾。判斷后綴名的時候尤其有效。
4. repeat(number);
得到一個重復number
次的字符串。額...我也不知道什么時候有用,一般我用它造測試數據。
5. 'abc'.padEnd(5, '1'); // abc11;
用給定的字符串在尾部拼接到指定長度,第一個參數為長度,第二個參數為用于拼接的值。
6. 'abc'.padStart(5, '1'); // 11abc;
用給定的字符串在首部拼接到指定長度第一個參數為長度,第二個參數為用于拼接的值。首部補0?
23.數組快速去重
應該很多人都知道這個,數組轉換成Set
, 再轉換為數組,不過這種去重方式只能去除基本數據類型組成的數組。
const arr = [1, 2, 3, 4, 5, 6];
const arr2 = new Set(arr);
const arr3 = [...arr2];
24.Object.keys, values, entries
一般我們常用Object.keys
,返回一個對象的鍵組成的數組,其實還有Object.values
,返回對象值組成的數組,Object.entries
將對象轉成數組,每個元素是鍵值對組成的數組,可以使用此功能快速將對象轉為Map
。
const obj = {name: 'yd', age: 18};
Object.keys(obj); // ['name', 'age'];
Object.values(obj); // ['yd', 18];
const l = Object.entries(obj); // [['name', 'yd'], ['age': 18]];
const m = new Map(l);
25.Object.getOwnPropertyDescriptors
獲取對象的描述信息
Object.assign
復制時,將對象的屬性和方法當做普通屬性來復制,并不會復制完整的描述信息,比如this
。
const p1 = {
a: 'y',
b: 'd',
get name() {
return `${this.a} ${this.b}`;
}
}
const p2 = Object.assign({}, p1);
p2.a = 'z';
p2.name; // y d; 發現并沒有修改p2.a的值,是因為this仍舊指向p1
使用Object.getOwnPropertyDescriptors
獲取完整描述信息
const description = Object.getOwnPropertyDescriptors(p1);
const p2 = Object.defineProperties({}, description);
p2.a = 'z';
p2.name; // z d
26.BigInt
JavaScript
可以處理的最大數字是2
的53
次方 - 1
,這一點我們可以在Number.MAX_SAFE_INTEGER
中看到。
consoel.log(Number.MAX_SAFE_INTEGER); //9007199254740991
更大的數字則無法處理,ECMAScript2020
引入BigInt
數據類型來解決這個問題。通過把字母n
放在末尾, 可以運算大數據。
BigInt
可以使用算數運算符進行加、減、乘、除、余數及冪等運算。它可以由數字和十六進制或二進制字符串構造。此外它還支持AND
、OR
、NOT
和XOR
之類的按位運算。唯一無效的位運算是零填充右移運算符。
const bigNum = 100000000000000000000000000000n;
console.log(bigNum * 2n); // 200000000000000000000000000000n
const bigInt = BigInt(1);
console.log(bigInt); // 1n;
const bigInt2 = BigInt('2222222222222222222');
console.log(bigInt2); // 2222222222222222222n;
BigInt是一個大整數,所以他不能用來存儲小數。
27.??合并空運算符
假設變量a
不存在,我們希望給系統一個默認值,一般我們會使用||
運算符。但是在javascript
中空字符串,0
,false
都會執行||
運算符,所以ECMAScript2020
引入合并空運算符解決該問題,只允許在值為null
或未定義時使用默認值。
const name = '';
console.log(name || 'yd'); // yd;
console.log(name ?? 'yd'); // '';
28.?可選鏈運算符
業務代碼中經常會遇到這樣的情況,a
對象有個屬性b
,b
也是一個對象有個屬性c
,
我們需要訪問c
,經常會寫成a.b.c
,但是如果f
不存在時,就會出錯。
const a = {
b: {
c: 123,
}
}
console.log(a.b.c); // 123;
console.log(a.f.c); // f不存在所以會報錯
ECMAScript2020
定義可選鏈運算符解決該問題,通過在.
之前添加一個?
將鍵名變成可選
let person = {};
console.log(person?.profile?.age ?? 18); // 18
29.import
import
是ECMAScript2015
當中定義的一套ES Module
模塊系統,語法特性絕大多數瀏覽器已經支持了,通過給script
標簽添加type=module
的屬性就可以使用ES Module
的標準去執行javascript
代碼了。
<script type="module">
console.log('this is es module');
</script>
在ES Module
規范下,會采用嚴格模式(use strict
)運行javascript
代碼。每個ES Module
都運行在單獨的作用域中,也就意味著變量間不會互相干擾。外部js
文件是通過CORS
的方式請求的,所以要求我們外部的js
文件地址要支持跨域請求,也就是文件服務器要支持CORS
。我們可以在任意網站控制臺輸入下面代碼。都看到這里了不點開看下?
const script = document.createElement('script');
script.type = 'module';
script.innerHTML = `import React from 'https://cdn.bootcdn.net/ajax/libs/react/17.0.1/cjs/react-jsx-dev-runtime.development.js';`;
document.body.append(script);
可以發現在network
中請求了https://cdn.bootcdn.net/ajax/libs/react/17.0.1/cjs/react-jsx-dev-runtime.development.js
資源。
ES Module
的script
標簽會延遲腳本加載,等待網頁請求完資源之后才執行,和使用deffer
的方式加載資源相同。
需要注意的是,import {} from 'xx'
導入模塊的時候,并不是對象的解構,而是import
的固定語法,這一點很多人容易弄錯。
并且ECMAScript2020
中import
開始支持動態導入功能,在此之前import
只能寫在模塊代碼的頂部,一開始就要聲明模塊依賴的其它模塊。支持動態引入后就可以按需引入對應的模塊,這個功能我們早在SPA
中就已經用到了。動態導入返回的是一個Promise
。
a.js
const a = 123;
export { a };
b.js
import('./a.js').then(data => {
console.log(data.a); // 123;
})
30. 0.1 + 0.2 === 0.3 // false
console.log(0.1+0.2); // 0.30000000000000004
在JS
當中,Number
類型實際上是double
類型,運算小數時存在精度問題。因為計算機只認識二進制,在進行運算時,需要將其他進制的數值轉換成二進制,然后再進行計算
小數用二進制表達時是無窮的。
// 將0.1轉換成二進制
console.log(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101
// 將0.2轉換成二進制
console.log(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101
雙精度浮點數的小數部分最多支持53
位二進制位,所以兩者相加后,因浮點數小數位的限制而截斷的二進制數字,再轉換為十進制,就成了0.30000000000000004
,這樣在進行算術計算時會產生誤差。
ES6 在Number
對象上面,新增一個極小的常量Number.EPSILON
。根據規格,它表示1
與大于1
的最小浮點數之間的差。對于64
位浮點數來說,大于1
的最小浮點數相當于二進制的1.00..001
,小數點后面有連續51
個零。這個值減去1
之后,就等于2的-52次方
。
Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"
Number.EPSILON
實際上是JavaScript
能夠表示的最小精度。誤差如果小于這個值,就可以認為已經沒有意義了,即不存在誤差了。
引入一個這么小的量的目的,在于為浮點數計算,設置一個誤差范圍。我們知道浮點數計算是不精確的。
Number.EPSILON
可以用來設置能夠接受的誤差范圍
。比如,誤差范圍設為2
的-50
次方(即Number.EPSILON * Math.pow(2, 2)
),即如果兩個浮點數的差小于這個值,我們就認為這兩個浮點數相等。
(0.1 + 0.2 - 0.3) < Number.EPSILON // true
原文地址:https://juejin.cn/post/6908698827033837575
該文章在 2024/10/19 12:26:54 編輯過