Hi~ 很久不見。javascript
上一次整理的100道前端面試題在掘金火了以後,不少小夥伴反饋看答案不方便,其實主要是由於我整理的答案和解析內容很是全面,致使每一道題的篇幅都很長,閱讀體驗不太好,因此纔給你們把答案放到github上。css
最近解鎖了掘金的新功能——摺疊內容,我將在這篇文章中儘量多的的放置答案和解析。html
公司:京東前端
分類:JavaScriptvue
實際的樣式值 能夠理解爲 瀏覽器的計算樣式java
style 對象中包含支持 style 屬性的元素爲這個屬性設置的樣式信息,但不包含從其餘樣式表層疊繼承的一樣影響該元素的樣式信息。node
DOM2 Style 在 document.defaultView 上增長了 getComputedStyle() 方法。這個方法接收兩個參數:要取得計算樣式的元素和僞元素字符串(如":after")。若是不須要查詢僞元素,則第二個參數能夠傳 null。getComputedStyle()方法返回一個 CSSStyleDeclaration 對象(與 style 屬性的類型同樣),包含元素的計算樣式。react
<!DOCTYPE html>
<html>
<head>
<title>Computed Styles Example</title>
<style type="text/css"> #myDiv { background-color: blue; width: 100px; height: 200px; } </style>
</head>
<body>
<div id="myDiv" style="background-color: red; border: 1px solid black" ></div>
</body>
<script>
let myDiv = document.getElementById("myDiv");
let computedStyle = document.defaultView.getComputedStyle(myDiv, null);
console.log(computedStyle.backgroundColor); // "red"
console.log(computedStyle.width); // "100px"
console.log(computedStyle.height); // "200px"
console.log(computedStyle.border); // "1px solid black"(在某些瀏覽器中)
/* 兼容寫法 */
function getStyleByAttr(obj, name) {
return window.getComputedStyle
? window.getComputedStyle(obj, null)[name]
: obj.currentStyle[name];
}
let node = document.getElementById("myDiv");
console.log(getStyleByAttr(node, "backgroundColor"));
console.log(getStyleByAttr(node, "width"));
console.log(getStyleByAttr(node, "height"));
console.log(getStyleByAttr(node, "border
</script>
</html>
複製代碼
公司:高德、頭條nginx
分類:Reactgit
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
從官網的這句話中,咱們能夠明確的知道,Hook
增長了函數式組件中state
的使用,在以前函數式組件是沒法擁有本身的狀態,只能經過props
以及context
來渲染本身的UI
,而在業務邏輯中,有些場景必需要使用到state
,那麼咱們就只能將函數式組件定義爲class
組件。而如今經過Hook
,咱們能夠輕鬆的在函數式組件中維護咱們的狀態,不須要更改成class
組件。
React16.8 加入 hooks,讓 React 函數式組件更加靈活
hooks 以前,React 存在不少問題
hooks 很好的解決了上述問題,hooks 提供了不少方法
React Hooks
要解決的問題是狀態共享,這裏的狀態共享是指只共享狀態邏輯複用,並非指數據之間的共享。咱們知道在React Hooks
以前,解決狀態邏輯複用問題,咱們一般使用higher-order components
和render-props
。
既然已經有了這兩種解決方案,爲何React
開發者還要引入React Hook
?對於higher-order components
和render-props
,React Hook
的優點在哪?
PS:Hook 最大的優點其實仍是對於狀態邏輯的複用便捷,還有代碼的簡潔,以及幫助函數組件加強功能,
咱們先來看一下React
官方給出的React Hook
的demo
import { useState } from "React";
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
);
}
複製代碼
咱們再來看看不用React Hook
的話,如何實現
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
);
}
}
複製代碼
能夠看到,在React Hook
中,class Example
組件變成了函數式組件,可是這個函數式組件卻擁有的本身的狀態,同時還能夠更新自身的狀態。這一切都得益於useState
這個Hook
,useState
會返回一對值:當前狀態和一個讓你更新它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似 class
組件的 this.setState
,可是它不會把新的 state
和舊的 state
進行合併
Hooks 的基本類型:
type Hooks = {
memoizedState: any, // 指向當前渲染節點 Fiber
baseState: any, // 初始化 initialState, 已經每次 dispatch 以後 newState
baseUpdate: Update<any> | null, // 當前須要更新的 Update ,每次更新完以後,會賦值上一個 update,方便 react 在渲染錯誤的邊緣,數據回溯
queue: UpdateQueue<any> | null, // UpdateQueue 經過
next: Hook | null, // link 到下一個 hooks,經過 next 串聯每一 hooks
};
type Effect = {
tag: HookEffectTag, // effectTag 標記當前 hook 做用在 life-cycles 的哪個階段
create: () => mixed, // 初始化 callback
destroy: (() => mixed) | null, // 卸載 callback
deps: Array<mixed> | null,
next: Effect, // 同上
};
複製代碼
React Hooks 全局維護了一個 workInProgressHook 變量,每一次調取 Hooks API 都會首先調取 createWorkInProgressHooks 函數。
function createWorkInProgressHook() {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
currentHook = firstCurrentHook;
if (currentHook === null) {
// This is a newly mounted hook
workInProgressHook = createHook();
} else {
// Clone the current hook.
workInProgressHook = cloneHook(currentHook);
}
firstWorkInProgressHook = workInProgressHook;
} else {
// There's already a work-in-progress. Reuse it.
currentHook = firstCurrentHook;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
let hook;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
currentHook = currentHook.next;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
// Clone the current hook.
hook = cloneHook(currentHook);
}
}
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
} else {
// There's already a work-in-progress. Reuse it.
workInProgressHook = workInProgressHook.next;
currentHook = currentHook !== null ? currentHook.next : null;
}
}
return workInProgressHook;
}
複製代碼
假設咱們須要執行如下 hooks 代碼:
function FunctionComponet() {
const [ state0, setState0 ] = useState(0);
const [ state1, setState1 ] = useState(1);
useEffect(() => {
document.addEventListener('mousemove', handlerMouseMove, false);
...
...
...
return () => {
...
...
...
document.removeEventListener('mousemove', handlerMouseMove, false);
}
})
const [ satte3, setState3 ] = useState(3);
return [state0, state1, state3];
}
複製代碼
當咱們瞭解 React Hooks 的簡單原理,獲得 Hooks 的串聯不是一個數組,可是是一個鏈式的數據結構,從根節點 workInProgressHook 向下經過 next 進行串聯。這也就是爲何 Hooks 不能嵌套使用,不能在條件判斷中使用,不能在循環中使用。不然會破壞鏈式結構。
函數組件 的本質是函數,沒有 state 的概念的,所以不存在生命週期一說,僅僅是一個 render 函數而已。
可是引入 Hooks 以後就變得不一樣了,它能讓組件在不使用 class 的狀況下擁有 state,因此就有了生命週期的概念,所謂的生命週期其實就是 useState、 useEffect() 和 useLayoutEffect() 。
即:Hooks 組件(使用了 Hooks 的函數組件)有生命週期,而函數組件(未使用 Hooks 的函數組件)是沒有生命週期的。
下面,是具體的 class 與 Hooks 的生命週期對應關係:
公司:頭條
分類:Css
meida queries 的方式能夠說是我早期採用的佈局方式,它主要是經過查詢設備的寬度來執行不一樣的 css 代碼,最終達到界面的配置。
核心語法:
@media only screen and (max-width: 374px) {
/* iphone5 或者更小的尺寸,以 iphone5 的寬度(320px)比例設置樣式*/
}
@media only screen and (min-width: 375px) and (max-width: 413px) {
/* iphone6/7/8 和 iphone x */
}
@media only screen and (min-width: 414px) {
/* iphone6p 或者更大的尺寸,以 iphone6p 的寬度(414px)比例設置樣式 */
}
複製代碼
優勢:
缺點:
以天貓的實現方式進行說明:
它的 viewport 是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
高度定死,寬度自適應,元素都採用 px 作單位。
隨着屏幕寬度變化,頁面也會跟着變化,效果就和 PC 頁面的流體佈局差很少,在哪一個寬度須要調整的時候使用響應式佈局調調就行(好比網易新聞),這樣就實現了『適配』。
實現原理:
根據 rem 將頁面放大 dpr 倍, 而後 viewport 設置爲 1/dpr.
這樣整個網頁在設備內顯示時的頁面寬度就會等於設備邏輯像素大小,也就是 device-width。這個 device-width 的計算公式爲:
設備的物理分辨率/(devicePixelRatio * scale)
,在 scale 爲 1 的狀況下,device-width = 設備的物理分辨率/devicePixelRatio
。
rem
是相對長度單位,rem
方案中的樣式設計爲相對於根元素font-size
計算值的倍數。根據屏幕寬度設置html
標籤的font-size
,在佈局時使用 rem 單位佈局,達到自適應的目的。
viewport 是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
。
經過如下代碼來控制 rem 基準值(設計稿以 720px 寬度量取實際尺寸)
!(function (d) {
var c = d.document;
var a = c.documentElement;
var b = d.devicePixelRatio;
var f;
function e() {
var h = a.getBoundingClientRect().width,
g;
if (b === 1) {
h = 720;
}
if (h > 720) h = 720; //設置基準值的極限值
g = h / 7.2;
a.style.fontSize = g + "px";
}
if (b > 2) {
b = 3;
} else {
if (b > 1) {
b = 2;
} else {
b = 1;
}
}
a.setAttribute("data-dpr", b);
d.addEventListener(
"resize",
function () {
clearTimeout(f);
f = setTimeout(e, 200);
},
false
);
e();
})(window);
複製代碼
css 經過 sass 預編譯,設置量取的 px 值轉化 rem 的變量$px: (1/100)+rem;
優勢:
缺點:
js
腳本監聽分辨率的變化來動態改變根元素的字體大小,css
樣式和 js
代碼有必定耦合性,而且必須將改變font-size
的代碼放在 css
樣式以前。rem
計算後可能會出現小數像素,瀏覽器會對這部分小數四捨五入,按照整數渲染,有可能沒那麼準確。視口是瀏覽器中用於呈現網頁的區域。
雖然 vw 能更優雅的適配,可是仍是有點小問題,就是寬,高無法限制。
$base_vw = 375;
@function vw ($px) {
return ($px/$base_vw) * 100vw
};
複製代碼
優勢:
css
移動端適配方案,不存在腳本依賴問題。rem
以根元素字體大小的倍數定義元素大小,邏輯清晰簡單。缺點:
// scss 語法
// 設置html根元素的大小 750px->75 640px->64
// 將屏幕分紅10份,每份做爲根元素的大小。
$vw_fontsize: 75
@function rem($px) {
// 例如:一個div的寬度爲100px,那麼它對應的rem單位就是(100/根元素的大小)* 1rem
@return ($px / $vw_fontsize) * 1rem;
}
$base_design: 750
html {
// rem與vw相關聯
font-size: ($vw_fontsize / ($base_design / 2)) * 100vw;
// 同時,經過Media Queries 限制根元素最大最小值
@media screen and (max-width: 320px) {
font-size: 64px;
}
@media screen and (min-width: 540px) {
font-size: 108px;
}
}
// body 也增長最大最小寬度限制,避免默認100%寬度的 block 元素跟隨 body 而過大太小
body {
max-width: 540px;
min-width: 320px;
}
複製代碼
使用百分比%定義寬度,高度用px
固定,根據可視區域實時尺寸進行調整,儘量適應各類分辨率,一般使用max-width
/min-width
控制尺寸範圍過大或者太小。
優勢:
缺點:
var arr =[[‘A’,’B’],[‘a’,’b’],[1,2]]
求二維數組的全排列組合 結果:Aa1,Aa2,Ab1,Ab2,Ba1,Ba2,Bb1,Bb2公司:美團
分類:算法
function foo(arr) {
// 用於記錄初始數組長度, 用於將數組前兩組已經獲取到全排列的數組進行截取標識
var len = arr.length;
// 當遞歸操做後, 數組長度爲1時, 直接返回arr[0], 只有大於1繼續處理
if (len >= 2) {
// 每次只作傳入數組的前面兩個數組進行全排列組合, 即arr[0]和arr[1]的全排列組合
var len1 = arr[0].length;
var len2 = arr[1].length;
var items = new Array(len1 * len2); // 建立全排列組合有可能次數的數組
var index = 0; // 記錄每次全排列組合後的數組下標
for (var i = 0; i < len1; i++) {
for (var j = 0; j < len2; j++) {
if (Array.isArray(arr[0])) {
// 當第二次進來後, 數組第一個元素一定是數組包着數組
items[index] = arr[0][i].concat(arr[1][j]); // 對於已是第二次遞歸進來的全排列直接追加便可
} else {
items[index] = [arr[0][i]].concat(arr[1][j]); // 這裏由於只須要去arr[0]和arr[1]的全排列, 因此這裏就直接使用concat便可
}
index++; // 更新全排列組合的下標
}
}
// 若是數組大於2, 這裏新的newArr作一個遞歸操做
var newArr = new Array(len - 1); // 遞歸的數組比傳進來的數組長度少一, 由於上面已經將傳進來的數組的arr[0]和arr[1]進行全排列組合, 因此這裏的newArr[0]就是上面已經全排列好的數組item
for (var i = 2; i < arr.length; i++) {
// 這裏的for循環是爲了截取下標1項後的數組進行賦值給newArr
newArr[i - 1] = arr[i];
}
newArr[0] = items; // 由於上面已經將傳進來的數組的arr[0]和arr[1]進行全排列組合, 因此這裏的newArr[0]就是上面已經全排列好的數組item
// 從新組合後的數組進行遞歸操做
return foo(newArr);
} else {
// 當遞歸操做後, 數組長度爲1時, 直接返回arr[0],
return arr[0];
}
}
var arr = [
["A", "B"],
["a", "b"],
[1, 2],
];
console.log(foo(arr));
複製代碼
const getResult = (arr1, arr2) => {
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
return;
}
if (!arr1.length) {
return arr2;
}
if (!arr2.length) {
return arr1;
}
let result = [];
for (let i = 0; i < arr1.length; i++) {
for (let j = 0; j < arr2.length; j++) {
result.push(String(arr1[i]) + String(arr2[j]));
}
}
return result;
};
const findAll = (arr) =>
arr.reduce((total, current) => {
return getResult(total, current);
}, []);
var arr = [
["A", "B"],
["a", "b"],
[1, 2],
];
console.log(findAll(arr));
複製代碼
var arr = [
["A", "B"],
["a", "b"],
[1, 2],
];
let res = [],
lengthArr = arr.map((d) => d.length);
let indexArr = new Array(arr.length).fill(0);
function addIndexArr() {
indexArr[0] = indexArr[0] + 1;
let i = 0;
let overflow = false;
while (i <= indexArr.length - 1) {
if (indexArr[i] >= lengthArr[i]) {
if (i < indexArr.length - 1) {
indexArr[i] = 0;
indexArr[i + 1] = indexArr[i + 1] + 1;
} else {
overflow = true;
}
}
i++;
}
return overflow;
}
function getAll(arr, indexArr) {
let str = "";
arr.forEach((item, index) => {
str += item[indexArr[index]];
});
res.push(str);
let overflow = addIndexArr();
if (overflow) {
return;
} else {
return getAll(arr, indexArr);
}
}
getAll(arr, indexArr);
console.log(res);
複製代碼
公司:騰訊微視
分類:工程化
[改動文件類型]:[改動說明]
.postcssrc.js
.babelrc
.prettierrc
(vscode 插件 prettier-code fomatter)— 注意與 eslint 要保持一致.editorconfig
.eslintrc.js
(強制開啓驗證模式)上線準備 a. 域名申請 b. 備案申請 c. 服務器申請 d. 部署
測試線上環境
日誌監控
公司:水滴籌
分類:Vue
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
複製代碼
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父組件監聽到 mounted 鉤子函數 ...');
},
// Child.vue
mounted(){
console.log('子組件觸發 mounted 鉤子函數 ...');
},
// 以上輸出順序爲:
// 子組件觸發 mounted 鉤子函數 ...
// 父組件監聽到 mounted 鉤子函數 ...
複製代碼
分類:Vue
缺點:調用這個方法的時候沒有提示
// global.js
const RandomString = (encode = 36, number = -8) => {
return Math.random() // 生成隨機數字, eg: 0.123456
.toString(encode) // 轉化成36進制 : "0.4fzyo82mvyr"
.slice(number);
},
export default {
RandomString,
...
}
複製代碼
// 在項目入口的main.js裏配置
import Vue from "vue";
import global from "@/global";
Object.keys(global).forEach((key) => {
Vue.prototype["$global" + key] = global[key];
});
複製代碼
// 掛載以後,在須要引用全局變量的模塊處(App.vue),不需再導入全局變量模塊,而是直接用this就能夠引用了,以下:
export default {
mounted() {
this.$globalRandomString();
},
};
複製代碼
優勢:由於mixin裏面的methods會和建立的每一個單文件組件合併。這樣作的優勢是調用這個方法的時候有提示
// mixin.js
import moment from 'moment'
const mixin = {
methods: {
minRandomString(encode = 36, number = -8) {
return Math.random() // 生成隨機數字, eg: 0.123456
.toString(encode) // 轉化成36進制 : "0.4fzyo82mvyr"
.slice(number);
},
...
}
}
export default mixin
複製代碼
// 在項目入口的main.js裏配置
import Vue from 'vue'
import mixin from '@/mixin'
Vue.mixin(mixin)
複製代碼
export default {
mounted() {
this.minRandomString()
}
}
複製代碼
Vue.use的實現沒有掛載的功能,只是觸發了插件的install方法,本質仍是使用了Vue.prototype。
// plugin.js
function randomString(encode = 36, number = -8) {
return Math.random() // 生成隨機數字, eg: 0.123456
.toString(encode) // 轉化成36進制 : "0.4fzyo82mvyr"
.slice(number);
}
const plugin = {
// install 是默認的方法。
// 當外界在 use 這個組件或函數的時候,就會調用自己的 install 方法,同時傳一個 Vue 這個類的參數。
install: function(Vue){
Vue.prototype.$pluginRandomString = randomString
...
},
}
export default plugin
複製代碼
// 在項目入口的main.js裏配置
import Vue from 'vue'
import plugin from '@/plugin'
Vue.use(plugin)
複製代碼
export default {
mounted() {
this.$pluginRandomString()
}
}
複製代碼
// 建立全局方法
this.$root.$on("test", function () {
console.log("test");
});
// 銷燬全局方法
this.$root.$off("test");
// 調用全局方法
this.$root.$emit("test");
複製代碼
公司:極光推送
分類:Vue
vm.$set()
解決了什麼問題在 Vue.js 裏面只有 data 中已經存在的屬性纔會被 Observe 爲響應式數據,若是你是新增的屬性是不會成爲響應式數據,所以 Vue 提供了一個 api(vm.$set)來解決這個問題。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue Demo</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
{{user.name}} {{user.age}}
<button @click="addUserAgeField">增長一個年紀字段</button>
</div>
<script> const app = new Vue({ el: "#app", data: { user: { name: "test", }, }, mounted() {}, methods: { addUserAgeField() { // this.user.age = 20 這樣是不起做用, 不會被Observer this.$set(this.user, "age", 20); // 應該使用 }, }, }); </script>
</body>
</html>
複製代碼
vm.$set()在 new Vue()時候就被注入到 Vue 的原型上。
vue/src/core/instance/index.js
import { initMixin } from "./init";
import { stateMixin } from "./state";
import { renderMixin } from "./render";
import { eventsMixin } from "./events";
import { lifecycleMixin } from "./lifecycle";
import { warn } from "../util/index";
function Vue(options) {
if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
warn("Vue is a constructor and should be called with the `new` keyword");
}
this._init(options);
}
initMixin(Vue);
// 給原型綁定代理屬性$props, $data
// 給Vue原型綁定三個實例方法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue);
// 給Vue原型綁定事件相關的實例方法: vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue);
// 給Vue原型綁定生命週期相關的實例方法: vm.$forceUpdate, vm.destroy, 以及私有方法_update
lifecycleMixin(Vue);
// 給Vue原型綁定生命週期相關的實例方法: vm.$nextTick, 以及私有方法_render, 以及一堆工具方法
renderMixin(Vue);
export default Vue;
複製代碼
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
複製代碼
源碼位置: vue/src/core/observer/index.js
export function set(target: Array<any> | Object, key: any, val: any): any {
// 1.類型判斷
// 若是 set 函數的第一個參數是 undefined 或 null 或者是原始類型值,那麼在非生產環境下會打印警告信息
// 這個api原本就是給對象與數組使用的
if (
process.env.NODE_ENV !== "production" &&
(isUndef(target) || isPrimitive(target))
) {
warn(
`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
);
}
// 2.數組處理
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 相似$vm.set(vm.$data.arr, 0, 3)
// 修改數組的長度, 避免索引>數組長度致使splcie()執行有誤
//若是不設置length,splice時,超過本來數量的index則不會添加空白項
target.length = Math.max(target.length, key);
// 利用數組的splice變異方法觸發響應式, 這個前面講過
target.splice(key, 1, val);
return val;
}
//3.對象,且key不是原型上的屬性處理
// target爲對象, key在target或者target.prototype上。
// 同時必須不能在 Object.prototype 上
// 直接修改便可, 有興趣能夠看issue: https://github.com/vuejs/vue/issues/6845
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
// 以上都不成立, 即開始給target建立一個全新的屬性
// 獲取Observer實例
const ob = (target: any).__ob__;
// Vue 實例對象擁有 _isVue 屬性, 即不容許給Vue 實例對象添加屬性
// 也不容許Vue.set/$set 函數爲根數據對象(vm.$data)添加屬性
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== "production" &&
warn(
"Avoid adding reactive properties to a Vue instance or its root $data " +
"at runtime - declare it upfront in the data option."
);
return val;
}
//5.target是非響應式數據時
// target自己就不是響應式數據, 直接賦值
if (!ob) {
target[key] = val;
return val;
}
//6.target對象是響應式數據時
//定義響應式對象
defineReactive(ob.value, key, val);
//watcher執行
ob.dep.notify();
return val;
}
複製代碼
// 判斷給定變量是不是未定義,當變量值爲 null時,也會認爲其是未定義
export function isUndef(v: any): boolean %checks {
return v === undefined || v === null;
}
// 判斷給定變量是不是原始類型值
export function isPrimitive(value: any): boolean %checks {
return (
typeof value === "string" ||
typeof value === "number" ||
// $flow-disable-line
typeof value === "symbol" ||
typeof value === "boolean"
);
}
// 判斷給定變量的值是不是有效的數組索引
export function isValidArrayIndex(val: any): boolean {
const n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val);
}
複製代碼
export function observe(value: any, asRootData: ?boolean): Observer | void {
// 省略...
if (asRootData && ob) {
// vue已經被Observer了,而且是根數據對象, vmCount纔會++
ob.vmCount++;
}
return ob;
}
複製代碼
export function initState(vm: Component) {
vm._watchers = [];
const opts = vm.$options;
if (opts.props) initProps(vm, opts.props);
if (opts.methods) initMethods(vm, opts.methods);
if (opts.data) {
//opts.data爲對象屬性
initData(vm);
} else {
observe((vm._data = {}), true /* asRootData */);
}
if (opts.computed) initComputed(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
複製代碼
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
// 省略...
// observe data
observe(data, true /* asRootData */);
}
複製代碼
從源碼能夠看出 set 主要邏輯以下:
vm.$set(target、key、value)
公司:極光推送
分類:JavaScript
看個例子
function deepCopy(obj){
const res = Array.isArray(obj) ? [] : {};
for(let key in obj){
if(typeof obj[key] === 'object'){
res[key] = deepCopy(obj[key]);
}else{
res[key] = obj[key];
}
}
return res
}
var obj = {
a:1,
b:2,
c:[1,2,3],
d:{aa:1,bb:2},
};
obj.e = obj;
console.log('obj',obj); // 不會報錯
const objCopy = deepCopy(obj);
console.log(objCopy); //Uncaught RangeError: Maximum call stack size exceeded
複製代碼
從例子能夠看到,當存在循環引用的時候,deepCopy會報錯,棧溢出。
你們都知道,對象的key是不能是對象的。
{{a:1}:2}
// Uncaught SyntaxError: Unexpected token ':'
複製代碼
參考解決方式一:使用weekmap:
解決循環引用問題,咱們能夠額外開闢一個存儲空間,來存儲當前對象和拷貝對象的對應關係
這個存儲空間,須要能夠存儲key-value
形式的數據,且key
能夠是一個引用類型,
咱們能夠選擇 WeakMap
這種數據結構:
WeakMap
中有無克隆過的對象key
,克隆對象做爲value
進行存儲function isObject(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function cloneDeep(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source); // 新增代碼,查哈希表
var target = Array.isArray(source) ? [] : {};
hash.set(source, target); // 新增代碼,哈希表設值
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep(source[key], hash); // 新增代碼,傳入哈希表
} else {
target[key] = source[key];
}
}
}
return target;
}
複製代碼
參考解決方式二:
能夠用 Set,發現相同的對象直接賦值,也可用 Map
const o = { a: 1, b: 2 };
o.c = o;
function isPrimitive(val) {
return Object(val) !== val;
}
const set = new Set();
function clone(obj) {
const copied = {};
for (const [key, value] of Object.entries(obj)) {
if (isPrimitive(value)) {
copied[key] = value;
} else {
if (set.has(value)) {
copied[key] = { ...value };
} else {
set.add(value);
copied[key] = clone(value);
}
}
}
return copied;
}
複製代碼
公司:編程貓
分類:工程化
有單元測試加持能夠保障交付代碼質量,加強本身和他人的信心。咱們選擇第三方庫的時候不也是會優先選擇有測試保障的嗎?將來對代碼進行改動時也能夠節省迴歸測試的時間。
在作單元測試時儘可能以集成測試爲主,對少許難以被集成測試覆蓋或須要分發的代碼作單元測試,同時也能夠有少許的端到端測試輔助。
儘可能不測試代碼實現,測試代碼實現可能會讓測試用例很快就失效。好比斷言變量,當變量名發生變動時會致使測試不經過,可是可能功能並無發生改變。
以用戶視角測試程序的功能,而非上帝視角。
對一個組件,傳入不一樣參數渲染 dom ,對用戶而言可能能夠看到某些特定文字或能夠作某些操做。此時能夠斷言 dom 是否出現了某些字,作動做(如點擊、輸入 、提交表單等)是否有正確的響應。
不要測試實現細節。好比以上帝視角檢查 redux store
上的數據、state
的數據等,而這些在最終用戶眼裏是不存在的,用戶能感知的只是所表現的功能。
Jest
是 facebook 出品的測試框架。開箱即用,自帶斷言庫、mock、覆蓋率報告等功能。
因爲前端要測試 UI,須要在模擬瀏覽器環境中渲染出 dom,因此須要一個這樣的庫。存在不少這樣的庫,經常使用的有 Enzyme
、@testing-library/react
。
Jest
自帶測試報告,可是衆多的項目分散在 gitlab 中給查看報告帶來了麻煩。須要考慮有一個集中的地方查看測試報告。這裏結合了 sonar
和 reportportal
歸集測試報告,能夠經過一個集中的地方查看全部項目的測試報告。
其中結合 sonar
的代碼掃描功能能夠查看測試覆蓋率等報告信息。reportportal
能夠查看測試執行率,另外官方宣稱自帶 AI 分析報告,能夠得出多維度的統計信息。
公司:編程貓
分類:React
雖然 React 自己有些函數式味道,但爲了迎合用戶習慣,早期只提供了 React.createClass() API 來定義組件: 天然而然地,(類)繼承就成了一種直覺性的嘗試。而在 JavaScript 基於原型的擴展模式下,相似於繼承的 Mixin 方案就成了首選:
// 定義Mixin
var Mixin1 = {
getMessage: function () {
return "hello world";
},
};
var Mixin2 = {
componentDidMount: function () {
console.log("Mixin2.componentDidMount()");
},
};
// 用Mixin來加強現有組件
var MyComponent = React.createClass({
mixins: [Mixin1, Mixin2],
render: function () {
return <div>{this.getMessage()}</div>;
},
});
複製代碼
但存在諸多缺陷
組件與 Mixin 之間存在隱式依賴(Mixin 常常依賴組件的特定方法,但在定義組件時並不知道這種依賴關係)多個 Mixin 之間可能產生衝突(好比定義了相同的 state 字段)Mixin 傾向於增長更多狀態,這下降了應用的可預測性(The more state in your application, the harder it is to reason about it.),致使複雜度劇增。
隱式依賴致使依賴關係不透明,維護成本和理解成本迅速攀升:難以快速理解組件行爲,須要全盤瞭解全部依賴 Mixin 的擴展行爲,及其之間的相互影響。組件自身的方法和 state 字段不敢輕易刪改,由於難以肯定有沒有 Mixin 依賴它 Mixin 也難以維護,由於 Mixin 邏輯最後會被打平合併到一塊兒,很難搞清楚一個 Mixin 的輸入輸出。
毫無疑問,這些問題是致命的 因此,React v0.13.0 放棄了 Mixin(繼承),轉而走向 HOC(組合)。
// 定義高階組件
var Enhance = (ComposedComponent) =>
class extends Component {
constructor() {
this.state = { data: null };
}
componentDidMount() {
this.setState({ data: "Hello" });
}
render() {
return <ComposedComponent {...this.props} data={this.state.data} />;
}
};
class MyComponent {
render() {
if (!this.data) return <div>Waiting...</div>;
return <div>{this.data}</div>;
}
}
// 用高階組件來加強普通組件,進而實現邏輯複用
export default Enhance(MyComponent);
複製代碼
理論上,只要接受組件類型參數並返回一個組件的函數都是高階組件((Component, ...args) => Component
),但爲了方便組合,推薦Component => Component
形式的 HOC,經過偏函數應用來傳入其它參數,例如:React Redux's connect
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
優勢:
缺點:HOC 雖然沒有那麼多致命問題,但也存在一些小缺陷:
「render prop」 是指⼀種在 React 組件之間使⽤⼀個值爲函數的 prop 共享代碼的簡單技術;
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY,
});
}
render() {
return (
<div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } } 複製代碼
優勢:數據共享、代碼復⽤,將組件內的 state 做爲 props 傳遞給調⽤者,將渲染邏輯交給調⽤者
缺點:⽆法在 return 語句外訪問數據、嵌套寫法不夠優雅;
function MyResponsiveComponent() {
const width = useWindowWidth();
// Our custom Hook
return <p>Window width is {width}</p>;
}
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
});
return width;
}
複製代碼
比起上面提到的其它方案,Hooks 讓組件內邏輯複用再也不與組件複用捆綁在一塊兒,是真正在從下層去嘗試解決(組件間)細粒度邏輯的複用問題。
此外,這種聲明式邏輯複用方案將組件間的顯式數據流與組合思想進一步延伸到了組件內,契合 React 理念。
優勢以下:
Hooks 也並不是完美,只是就目前而言,其缺點以下:
let [nums, setNums] = useState([0,1,2]); nums.push(1) 無效,必須使用 nums=[...nums, 1]
,再 setNums(nums);類組件中直接 push 是沒問題的公司:編程貓
分類:JavaScript
任何一個組件都應該遵照一套標準,能夠使得不一樣區域的開發人員據此標準開發出一套標準統一的組件
Single Point Of Truth,就是儘可能不要重複代碼,出自《The Art of Unix Programming》
使用父組件的 state 控制子組件的狀態而不是直接經過 ref 操做子組件
父組件不依賴子組件,刪除某個子組件不會形成功能異常
除了數據,避免複雜的對象,儘可能只接收原始類型的值
function F() {
this.a = 1;
}
var obj = new F();
console.log(obj.prototype);
複製代碼
公司:富途
分類:JavaScript
undefined
複製代碼
構造函數實例通常沒有prototype屬性。除了Function構造函數
只有函數纔有prototype屬性,這個屬性值爲一個object對象 實例對象時沒有這個屬性
實例對象經過__proto__這個內部屬性([[prototype]]
)來串起一個原型鏈的,經過這個原型鏈能夠查找屬性,
方法 經過new操做符初始化一個函數對象的時候就會構建出一個實例對象,
函數對象的prototype屬性指向的對象就是這個實例對象的原型對象,也就是__proto__指向的對象
經典與原型鏈圖
公司:富途
分類:JavaScript
十萬次循環代碼插入 body 中,頁面會出現卡頓,代碼後的 DOM 節點加載不出來
設置 script 標籤 defer 屬性,瀏覽器其它線程將下載腳本,待到文檔解析完成腳本纔會執行。
若 button 中的點擊事件在 defer 腳本前定義,則在 defer 腳本加載完後,響應點擊事件。
若 button 中的點擊事件在 defer 腳本後定義,則用戶點擊 button 無反應,待腳本加載完後,再次點擊有響應。
代碼示例
<!-- test.html -->
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div class="test1">test1</div>
<div id="hello"></div>
<script> // 待defer腳本下載完成後響應 function alertMsg() { alert("123"); } </script>
<input type="button" id="button1" onclick="alertMsg()" />
<script src="./test.js" defer></script>
<div class="test2">test2</div>
</body>
<style> .test1 { color: red; font-size: 50px; } .test2 { color: yellow; font-size: 50px; } </style>
</html>
複製代碼
// test.js
for (let i = 0; i < 100000; i++) {
console.log(i);
}
document.getElementById("hello").innerHTML = "hello world";
複製代碼
Concurrent.Thread.create(function () {
$("#test").click(function () {
alert(1);
});
for (var i = 0; i < 100000; i++) {
console.log(i);
}
});
複製代碼
若該情形是渲染十萬條數據的狀況下,則能夠使用虛擬列表。虛擬列表即只渲染可視區域的數據,使得在數據量龐大的狀況下,減小 DOM 的渲染,使得列表流暢地無限滾動。
實現方案:
基於虛擬列表是渲染可視區域的特性,咱們須要作到如下三點
topHeight 的計算比較簡單,就是滾動了多少高度,topHeight=scrollTop。
start 的計算依賴於 topHeight 和每項元素的高度 itemHeight,假設咱們向上移動了兩個列表項,則 start 爲 2,如此,咱們有 start = Math.floor(topHeight / itemHeight)
。
end 的計算依賴於屏幕的高度能顯示多少個列表項,咱們稱之爲 visibleCount,則有 visibleCount = Math.ceil(clientHeight / itemHeight)
,向上取整是爲了不計算偏小致使屏幕沒有顯示足夠的內容,則 end = start + visibleCount
。 bottomHeight 須要咱們知道整個列表沒有被截斷前的高度,減去其頂部的高度,計算頂部的高度有了 end 就很簡單了,假設咱們的整個列表項的數量爲 totalItem,則 bottomHeight = (totalItem - end - 1) \* itemHeight
。
會出現的問題:
可是當這樣實現的時候,會發現有兩個問題:
來分析下,對於第一個問題,會出現留白的狀況,那麼咱們能夠在頂部或者底部預留必定的位置,而第二個問題,也是能夠經過在頂部和底部預留必定的空間,因此解決這個問題只要一個方案就能夠解決了,那就是頂部和底部都預留必定的位置。
假設 reserveTop 爲頂部預留的位置數,reserveBottom 爲底部預留的位置數,那麼咱們上面的數據的計算就要從新定義了,具體如何計算,請看下圖。
reserveTop 和 reserveBottom 儘可能大點(固然也不要太大),或者知道列表項的最高高度爲多少,就按這個最高高度來。當你發現你滾動的時候頂部有留白,就調大 reserveTop 的數值,當你發現滾動的時候底部有留白,那就調大 reserveBottom 的數值。
公司:富途
分類:其它
7 只
複製代碼
每一個老鼠只有死或活 2 種狀態,所以每一個老鼠能夠看做一個 bit,取 0 或 1N 個老鼠能夠看做 N 個 bit,能夠表達 2^N 種狀態(其中第 n 個狀態表明第 n 個瓶子有毒)所以全部老鼠能表示的狀態數能大於等於 100 便可。
代碼實現
let n = 1;
while (Math.pow(2, n) < 100) {
n++;
}
console.log(n);
複製代碼
通俗點的理解:
給 100 個瓶分別標上以下標籤(7 位長度): 0000001 (第 1 瓶) 0000010 (第 2 瓶) 0000011 (第 3 瓶) ...... 1100100 (第 100 瓶)
從編號最後 1 位是 1 的全部的瓶子裏面取出 1 滴混在一塊兒(好比從第一瓶,第三瓶,。。。裏分別取出一滴混在一塊兒)並標上記號爲 1。以此類推,從編號第一位是 1 的全部的瓶子裏面取出 1 滴混在一塊兒並標上記號爲 7。如今獲得有 7 個編號的混合液,小白鼠排排站,分別標上 7,6,。。。1 號,並分別給它們灌上對應號碼的混合液。三天過去了,過來驗屍吧:
從左到右,死了的小白鼠貼上標籤 1,沒死的貼上 0,最後獲得一個序號,把這個序號換成 10 進制的數字,就是有毒的那瓶水的編號。
檢驗一下:假如第一瓶有毒,按照 0000001 (第 1 瓶),說明第 1 號混合液有毒,所以小白鼠的生死符爲 0000001(編號爲 1 的小白鼠掛了),0000001 二進制標籤轉換成十進制=1 號瓶有毒;假如第三瓶有毒,0000011 (第 3 瓶),第 1 號和第 2 號混合液有毒,所以小白鼠的生死符爲 0000011(編號爲 1,2 的鼠兄弟掛了),0000011 二進制標籤轉換成十進制=3 號瓶有毒。
因此結果就是 2^7 = 128 >= 100,至少須要 7 只小白鼠。
公司:快手
分類:JavaScript
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
複製代碼
Promise.allSettled()
方法接受一組 Promise
實例做爲參數,返回一個新的 Promise 實例。fulfilled
仍是 rejected
,包裝實例纔會結束。Promise
實例,一旦結束,狀態老是 fulfilled
,不會變成 rejected
。Promise
實例給監聽函數傳遞一個數組 results
。該數組的每一個成員都是一個對象,對應傳入 Promise.allSettled
的 Promise 實例。每一個對象都有 status 屬性,對應着 fulfilled
和 rejected
。 fulfilled
時,對象有 value
屬性, rejected
時有 reason
屬性,對應兩種狀態的返回值。const formatSettledResult = (success, value) =>
success
? { status: "fulfilled", value }
: { status: "rejected", reason: value };
Promise.all_settled = function (iterators) {
const promises = Array.from(iterators);
const num = promises.length;
const resultList = new Array(num);
let resultNum = 0;
return new Promise((resolve) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
resultList[index] = formatSettledResult(true, value);
if (++resultNum === num) {
resolve(resultList);
}
})
.catch((error) => {
resultList[index] = formatSettledResult(false, error);
if (++resultNum === num) {
resolve(resultList);
}
});
});
});
};
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
Promise.all_settled([resolved, rejected]).then((results) => {
console.log(results);
});
複製代碼
公司:快手
分類:JavaScript
跨域是針對瀏覽器的「同源策略」提出的說法。之因此有「同源策略」這種模式是基於網絡安全方面的考慮。所謂的同源策略關注三點:
http:www.baidu.com & https.www.baidu.com http
協議不一樣,跨域)https://www.aliyun.com & https://developer.aliyun.com
域名不一樣,跨域)http://localhost:8080 & http://localhost:8000
端口號不一樣,跨域)「同源策略」對於跨域網絡資源的設定很是的清晰。
這些場景涉及到跨域禁止操做:
爲何要阻止跨域呢?上文咱們說過是基於安全策略:好比一個惡意網站的頁面經過 iframe 嵌入了銀行的登陸頁面(兩者不一樣源),若是沒有同源限制,惡意網頁上的 javascript 腳本就能夠在用戶登陸銀行的時候獲取用戶名和密碼。
針對跨越問題咱們該如何解決,主流的方案有如下:
一、 經過 jsonp 跨域 二、 document.domain + iframe 跨域 三、 location.hash + iframe 四、 window.name + iframe 跨域 五、 postMessage 跨域 六、 跨域資源共享(CORS) 七、 nginx 代理跨域 八、 nodejs 中間件代理跨域 九、 WebSocket 協議跨域
跨域並不是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。
每次需求都會發出,服務器端也會作出響應,只是瀏覽器端在接受響應的時候會基於同源策略進行攔截。
注意:有些瀏覽器不容許從 HTTPS 的域跨域訪問 HTTP,好比 Chrome 和 Firefox,這些瀏覽器在請求還未發出的時候就會攔截請求,這是一個特例。
公司:快手
分類:網絡&安全
瀏覽器緩存主要分爲四個階段:
強緩存通常存放於 Memory Cache 或者 Disk Cache。
etag 能夠經過文件的 Last-Modified 和 content-length 計算。
Nginx官方默認的ETag計算方式是爲"文件最後修改時間16進制-文件長度16進制"。例:ETag: 「59e72c84-2404」
注意:
無論怎麼樣的算法,在服務器端都要進行計算,計算就有開銷,會帶來性能損失。所以爲了榨乾這一點點性能,很多網站徹底把Etag禁用了(好比Yahoo!),這其實不符合HTTP/1.1的規定,由於HTTP/1.1老是鼓勵服務器儘量的開啓Etag。
不使用緩存常見的方法是經過 url 拼接 random 的方式或者設置 Cache-Control 設置 no-cache。
公司:快手
分類:其它
網頁從加載到呈現會經歷一系列過程,針對每一個過程進行優化
經過 performance.timing
API,能夠獲取各個階段的執行時間:
{
navigationStart: 1578537857229; //上一個文檔卸載(unload)結束時的時間戳
unloadEventStart: 1578537857497; //表徵了unload事件拋出時的時間戳
unloadEventEnd: 1578537857497; //表徵了unload事件處理完成時的時間戳
redirectStart: 0; // 重定向開始時的時間戳
redirectEnd: 0; //重定向完成時的時間戳
fetchStart: 1578537857232; //準備好HTTP請求來獲取(fetch)文檔的時間戳
domainLookupStart: 1578537857232; //域名查詢開始的時間戳
domainLookupEnd: 1578537857232; //域名查詢結束的時間戳
connectStart: 1578537857232; //HTTP請求開始向服務器發送時的時間戳
connectEnd: 1578537857232; //瀏覽器與服務器之間的鏈接創建時的時間戳
secureConnectionStart: 0; //安全連接的握手時的U時間戳
requestStart: 1578537857253; //HTTP請求(或從本地緩存讀取)時的時間戳
responseStart: 1578537857491; //服務器收到(或從本地緩存讀取)第一個字節時的時間戳。
responseEnd: 1578537857493; //響應結束
domLoading: 1578537857504; //DOM結構開始解析時的時間戳
domInteractive: 1578537858118; //DOM結構結束解析、開始加載內嵌資源時的時間戳
domContentLoadedEventStart: 1578537858118; //DOMContentLoaded 事件開始時間戳
domContentLoadedEventEnd: 1578537858118; //當全部須要當即執行的腳本已經被執行(不論執行順序)時的時間戳
domComplete: 1578537858492; //當前文檔解析完成的時間戳
loadEventStart: 1578537858492; //load事件被髮送時的時間戳
loadEventEnd: 1578537858494; //當load事件結束時的時間戳
}
複製代碼
主要是針對重定向、DNS、TCP 鏈接進行優化
dns-prefetch
,同時將同類型的資源放到一塊兒,減小 domain
數量也是能夠減小 DNS 查找 Keep-Alive
選項和服務器創建長鏈接,讓多個資源經過一個 TCP 鏈接傳輸。減小瀏覽器向瀏覽器發送的請求數目以及請求資源的大小是請求優化的核心思想
1.3.1 頁面加載的核心指標
>=50ms 的任務
1.4.1 首屏時間:
指用戶打開網站開始,到瀏覽器首屏內容渲染完成的時間。對於用戶體驗來講,首屏時間是用戶對一個網站的重要體驗因素。一般一個網站,若是首屏時間在 5 秒之內是比較優秀的,10 秒之內是能夠接受的,10 秒以上就不可容忍了。超過 10 秒的首屏時間用戶會選擇刷新頁面或馬上離開。
1.4.2首屏時間計算:
一般適用於首屏內容不須要經過拉取數據才能生存以及頁面不考慮圖片等資源加載的狀況,咱們會在 HTML 文檔中對應首屏內容的標籤結束位置,使用內聯的 JavaScript 代碼記錄當前時間戳。以下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>首屏</title>
<script type="text/javascript"> window.pageStartTime = Date.now(); </script>
<link rel="stylesheet" href="common.css" />
<link rel="stylesheet" href="page.css" />
</head>
<body>
<!-- 首屏可見模塊1 -->
<div class="module-1"></div>
<!-- 首屏可見模塊2 -->
<div class="module-2"></div>
<script type="text/javascript"> window.firstScreen = Date.now(); </script>
<!-- 首屏不可見模塊3 -->
<div class="module-3"></div>
<!-- 首屏不可見模塊4 -->
<div class="module-4"></div>
</body>
</html>
複製代碼
此時首屏時間等於 firstScreen - performance.timing.navigationStart
事實上首屏模塊標籤標記法 在業務中的狀況比較少,大多數頁面都須要經過接口拉取數據才能完整展現
一般咱們首屏內容加載最慢的就是圖片資源,所以咱們會把首屏內加載最慢的圖片的時間當作首屏的時間。
DOM 樹構建完成後將會去遍歷首屏內的全部圖片標籤,而且監聽全部圖片標籤 onload 事件,最終遍歷圖片標籤的加載時間的最大值,並用這個最大值減去 navigationStart
便可得到近似的首屏時間。
此時首屏時間等於 加載最慢的圖片的時間點 - performance.timing.navigationStart
因爲統計首屏內圖片完成加載的時間比較複雜。所以咱們在業務中一般會經過自定義模塊內容,來簡化計算首屏時間。以下面的作法:
1.4.3首屏優化方案:
公司:快手
分類:JavaScript
要了解某個元素是否進入了"視口"(viewport),即用戶能不能看到它,傳統的實現方法是,監聽到scroll
事件後,調用目標元素的getBoundingClientRect()
方法,獲得它對應於視口左上角的座標,再判斷是否在視口以內。而後聲明一個全局變量,每出現一次就加一,就能夠得出在視口出現了幾回。這種方法的缺點是,因爲scroll
事件密集發生,計算量很大,容易形成性能問題。
因而便有了 IntersectionObserver API
var io = new IntersectionObserver(callback, option);
複製代碼
上面代碼中,IntersectionObserver
是瀏覽器原生提供的構造函數,接受兩個參數:callback
是可見性變化時的回調函數,option
是配置對象(該參數可選)。
構造函數的返回值是一個觀察器實例。實例的observe
方法能夠指定觀察哪一個 DOM 節點。
// 開始觀察
io.observe(document.getElementById("example"));
// 中止觀察
io.unobserve(element);
// 關閉觀察器
io.disconnect();
複製代碼
上面代碼中,observe
的參數是一個 DOM 節點對象。若是要觀察多個節點,就要屢次調用這個方法。
io.observe(elementA);
io.observe(elementB);
複製代碼
目標元素的可見性變化時,就會調用觀察器的回調函數callback
。
callback
通常會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另外一次是徹底離開視口(開始不可見)。
var io = new IntersectionObserver((entries) => {
console.log(entries);
});
複製代碼
callback
函數的參數(entries
)是一個數組,每一個成員都是一個IntersectionObserverEntry
對象。若是同時有兩個被觀察的對象的可見性發生變化,entries
數組就會有兩個成員。
IntersectionObserverEntry
對象提供目標元素的信息,一共有六個屬性。
{
time: 3893.92,
rootBounds: ClientRect {
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
},
boundingClientRect: ClientRect {
// ...
},
intersectionRect: ClientRect {
// ...
},
intersectionRatio: 0.54,
target: element
}
複製代碼
每一個屬性的含義以下。
time
:可見性發生變化的時間,是一個高精度時間戳,單位爲毫秒target
:被觀察的目標元素,是一個 DOM 節點對象rootBounds
:根元素的矩形區域的信息,getBoundingClientRect()
方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回null
boundingClientRect
:目標元素的矩形區域的信息intersectionRect
:目標元素與視口(或根元素)的交叉區域的信息intersectionRatio
:目標元素的可見比例,即intersectionRect
佔boundingClientRect
的比例,徹底可見時爲1
,徹底不可見時小於等於0
IntersectionObserver
構造函數的第二個參數是一個配置對象。它能夠設置如下屬性。
2.3.1 threshold 屬性:
threshold
屬性決定了何時觸發回調函數。它是一個數組,每一個成員都是一個門檻值,默認爲[0]
,即交叉比例(intersectionRatio
)達到0
時觸發回調函數。
new IntersectionObserver(
(entries) => {
/* ... */
},
{
threshold: [0, 0.25, 0.5, 0.75, 1],
}
);
複製代碼
用戶能夠自定義這個數組。好比,[0, 0.25, 0.5, 0.75, 1]
就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。
2.3.2 root 屬性、rootMargin 屬性:
不少時候,目標元素不只會隨着窗口滾動,還會在容器裏面滾動(好比在iframe
窗口裏滾動)。容器內滾動也會影響目標元素的可見性。
IntersectionObserver API 支持容器內滾動。root
屬性指定目標元素所在的容器節點(即根元素)。注意,容器元素必須是目標元素的祖先節點。
var opts = {
root: document.querySelector(".container"),
rootMargin: "500px 0px",
};
var observer = new IntersectionObserver(callback, opts);
複製代碼
上面代碼中,除了root
屬性,還有rootMargin屬性。後者定義根元素的margin
,用來擴展或縮小rootBounds
這個矩形的大小,從而影響intersectionRect
交叉區域的大小。它使用 CSS 的定義方法,好比10px 20px 30px 40px
,表示 top、right、bottom 和 left 四個方向的值。
這樣設置之後,無論是窗口滾動或者容器內滾動,只要目標元素可見性變化,都會觸發觀察器。
---------------------------------- 一條講武德的分割線 ------------------------------
因掘金髮文的字數限制,剩下的答案你們掃碼便可查看。
var versions = ["1.45.0", "1.5", "6", "3.3.3.3.3.3.3"];
// 要求從小到大排序,注意'1.45'比'1.5'大
function sortVersion(versions) {
// TODO
}
// => ['1.5','1.45.0','3.3.3.3.3.3','6']
複製代碼
公司:頭條
分類:JavaScript
分類:Node
function repeat(func, times, wait) {
// TODO
}
const repeatFunc = repeat(alert, 4, 3000);
// 調用這個 repeatFunc ("hellworld"),會alert4次 helloworld, 每次間隔3秒
複製代碼
公司:頭條
分類:JavaScript
公司:玄武科技
分類:JavaScript
公司:玄武科技
分類:JavaScrip