摘要:當引擎開始執行腳本是的時候,會先創建一個全局執行上下文,并將其到當前執行棧,無論何時一個函數被調用,就會創建一個新的函數執行上下文并壓入棧中。當函數執行完畢,執行棧會將其彈出,并把控制權交給當前棧的下一個上下文。
從過去直到 React.lazy
寫一個沒有 JSX 的 React
執行上下文和執行棧
公私有域和方法
數組在性能方面的一個注意點
從過去直到 React.lazy code-splitting當我們最最開始做前端開發的時候,JavaScript 文件自然就一個個羅列在一起,通過 script 標簽引入到 html 里。當然,即使在現在,我們也還是會在寫一些 Demo 時使用這樣的方式。
如今,我們有了如 Webpack、Parcel 等 Module bundler 來為我們更好的組織 JavaScript 文件。我們可以使用各種模塊系統如 CommonJS(require、module.exports)或者 ES Modules(import、export)來定義文件之間的依賴。
然而,隨著我們的應用越來越大,我們就會得到一個巨大的 JS bundle,而這種慢慢等待加載的體驗是絕不能忍受的。因此,code-splitting 就成了一種廣泛接受的做法。
下面的例子就是沒有拆分過的、只會打包成一份的應用,在加載時會同步全部加載再渲染:
import Description from "./Description"; function App() { return (); }My Movie
現在我們來開始看看,如何讓我們的 Module bundler 來懶加載我們的模塊呢?
Dynamic import proposal動態 import 提案為 ES Modules 添加了新特性,使我們可以以異步的方式定義我們的依賴關系。import 語句可以作為一個函數來調用,并返回一個 Promise,這個 Promise 會 resolve 我們想要加載的模塊。使用方式只需要從上面的 ES Modules 的 import 方式略加調整:
- import Description from "./Description"; + const Description = import("./Description");
上面的用法就會告訴 Webpack 或 Parcel 我們的 Description 模塊并不是立即就需要,而是可以等到加載好后再使用。并且,動態 import 就可以使得 Module bundler 將該模塊打包成多帶帶的 js 文件,而這就是所謂的 code-split。
但是還不夠,這還只是開始。讓我們繼續往下走。
React 組件的懶加載如果我們使用上述動態 import,我們的 App 組件就要修改成如下的方式:
const LoadDescription = () => import("./Description"); class App extends React.Component { state = { Description: null, }; componentDidMount() { LoadDescription.then(Description => { this.setState({ Description: Description.default }); }); } render() { const { Description } = this.state; return (); } }My Movie
{Description ?: "Loading..."}
這樣寫未免就有點蛋疼了,所幸的是我們有一個非常好用的庫,即 react-loadable:
react-loadable 會幫我們省掉很多模板代碼,改寫后的效果如下:
import Loadable from "react-loadable"; const LoadableDescription = Loadable({ loader: () => import("./Description"), loading() { returnLoading...; }, }); function App() { return (); }My Movie
這樣看上去就好多了,我們就不需要再自己去管生命周期之類的事,只需要靠它來 load 我們需要的組件、指定相應的 loading 即可使用。
既然 react-loadable 已經這么好用了,我們還干嘛要用 React.lazy 呢?
Suspensereact-loadable 實際上還是有一些不足的,主要的一點就是它只作用于每一個多帶帶組件。什么意思呢?如果你有一堆想要懶加載的組件,你需要分別為他們指定 loading 狀態。當然,你可以使用一個公用的組件,這樣你每個 loading 狀態都可以復用,但是你仍然會看到每一個懶加載的組件各自有一個 loading。如果你在一個頁面有很多懶加載的組件,那就牛逼了,你會看到一堆小菊花,這恐怕也不是什么好的體驗。
說到這一缺點,在我們團隊的一些項目中,CLI 目前是在路由層面配合使用 react-router 和 react-loadable 的,一次只會 load 一個組件,因而就不存在一堆要懶加載的組件同時出現在頁面上;而 loading 狀態,我們也可以設計一個全局的 Spin 來使用。總的來說,肯定是存在一些方法或替代方案來彌補或避免這些問題的。
但是,在我們目前的工程中,仍然有可以改善的點:
一堆 loadable 文件;
react-loadable 有除懶加載以外功能的其他代碼,這些可能是我們不需要的;
如果我們想對更深層的子組件做懶加載,就還需要引入 loadable 文件,不優雅。
好的,讓我們來看看 React.lazy 可以做到什么吧!
與 react-loadable 不同的是,我們不需要在每一個 React.lazy 處定義一個 loading 狀態,我們要搭配使用 Suspense,在 Suspense 這里定義一個 loading 狀態。這就意味著,你可以有很多個 React.lazy 組件,但你只需要給對應的 Suspense 指定一個 loading 狀態就可以了。
此外,我們可以在任意深的地方放入一個 React.lazy 組件,Suspense 會統一的、干干凈凈的處理好懶加載的任務。
那么我們要怎樣使用 React.lazy 來改寫上面的代碼呢?如下所示:
import React, { Suspense } from "react"; const Description = React.lazy(() => import("./Description")); function App() { return (); }My Movie
Suspense 就像是 try-catch 一樣,會「捕獲」到 React.lazy 實例,然后會進入同一個 fallback 組件。也就是說,下面的例子中,我們只會渲染同一個 fallback:
import React, { Suspense } from "react"; const Description = React.lazy(() => import("./Description")); function App() { return (); } // AnotherLazyComponent.js (imagine in another file) const AndYetAnotherLazyComponent = React.lazy(() => import("./AndYetAnotherLazyComponent") ); function AnotherLazyComponent() { return (My Movie
Cast So...so..lazy..); }
如果我們想更自由的指定不同的懶加載組件的不同 loading 狀態,只需要像下面一樣嵌套 Suspense 即可:
function App() { return (); }My Movie
Cast
厲害的是,如果 AnotherLazyComponent 很久都沒有加載完,沒關系,他不會影響到其他組件的渲染。React.lazy 和 Suspense 會把 AnotherLazyComponent 和他的子組件們隔離開來,避免它加載的延遲影響到其他內容的渲染。
這樣一來,與前面沒有另一個 Suspense 的寫法相比,后者就不會等待所有懶加載組件都加載好后才能呈現,而是逐個呈現各個組件,這就有些像是 Promise.all 和各自異步的感覺。
最后是不是可以準備改造一下項目了呢?
源地址:https://hswolff.com/blog/reac...
參考:https://reactjs.org/docs/code...
寫一個沒有 JSX 的 React習慣了 JSX 的寫法,今天來感受下沒有 JSX 的 React 的酸爽。
我們知道,通常我們在使用 React 時所寫的 JSX,都會被 Babel 編譯成一些方法,一個很有名的方法就是 React.createElement。
React.createElement 方法需要三個參數:
type: HTML 元素或組件的類型(例如: h1、h2、p、button 等等);
props: 傳入的屬性對象;
children: 任何可以穿入的夾在元素中的東西。
簡單的例子那么我們把最基本的 React 去掉 JSX 來寫,就有下面的代碼:
let welcome = React.createElement("h1",{style:{color:"red"}},`Welcome to react world`); ReactDOM.render(welcome,document.querySelector("#root"));
上面的代碼就是純 React,當然,ReactDOM.render 方法還是一樣的。
我們調整下上面的代碼,組織成一個組件:
class Welcome extends React.Component{ render(){ return React.createElement("h1",{style:{color:"red"}}, `Welcome to ${this.props.name}`); } } ReactDOM.render(React.createElement(Welcome, {name:"Homepage"},null),document.querySelector("#root"));
我們在 React.createElement 方法傳入了第二個參數 {name:"Homepage"},因此在 Welcome 類內部,就可以通過 this.props.name 訪問到這個傳入的屬性。
counter 例子const el = React.createElement; function Button(props){ return el("button", { onClick: props.handleClick }, props.name); } class Counter extends React.Component{ state= { num: 0, } handleIncrement = () =>{ this.setState({ num: this.state.num + 1, }); } handleDecrement = () =>{ this.setState({ num: this.state.num - 1, }); } render(){ return el("div",null, el(Button, { handleClick: this.handleIncrement, name:"Increment" }, null), el(Button,{ handleClick: this.handleDecrement, name:"Decrement" }, null), el("p", null, this.state.num), } } ReactDOM.render(el(Counter,null,null),document.querySelector("#root"))
可以看到,沒有 JSX,我們的 render 方法變得復雜了很多。上面代碼的效果如下圖所示:
我們再回來看看 JSX 的寫法:
function Button(props) { return } class Counter extends React.Component { state = { num: 0 } handleIncrement = () => { this.setState({ num: this.state.num + 1 }) } handleDecrement = () => { this.setState({ num: this.state.num - 1 }) } render() { return () } } ReactDOM.render({this.state.num}
, document.querySelector("#root"))
JSX 的可讀性原來還算好的了。
源地址:https://codeburst.io/how-to-u...
執行上下文和執行棧 什么是執行上下文這可能是很多書本上都會講的基礎知識,這里我們也帶一遍。執行上下文就是 JavaScript 代碼求值和執行的環境。不管跑什么代碼,都是跑在一個執行上下文里。
執行上下文有 3 種:
全局上下文
程序里只會有一個。
函數上下文
函數上下文可以有人以多個,只要一個新的函數被調用,就會創建一個函數上下文,而且他們會按照一種定義好的順序逐個執行。
Eval 上下文
這個咱們還是不多講了,危險。
其實也就是調用棧。當 JavaScript 引擎開始執行腳本是的時候,會先創建一個全局執行上下文,并將其 push 到當前執行棧,無論何時一個函數被調用,就會創建一個新的(函數)執行上下文并壓入棧中。
引擎會執行那些在棧頂的執行上下文。當函數執行完畢,執行棧會將其彈出,并把控制權交給當前棧的下一個上下文。
舉個例子:
let a = "Hello World!"; function first() { console.log("Inside first function"); second(); console.log("Again inside first function"); } function second() { console.log("Inside second function"); } first(); console.log("Inside Global Execution Context");
以一個圖來展示就是:
怎么個執行真的不需要多說了。我們還是接著講點不知道的吧。
執行上下文是怎么被創建的?上面的內容告訴我們 JavaScript 引擎是怎么管理執行上下文的,現在我們來講下上下文是怎么被創建的。
執行上下文的創建總共分兩步:
創建階段
執行階段
創建階段執行上下文其實就是在創建階段被創建的。在創建階段,我們會有兩種環境被創建:
LexicalEnvironment,我們叫作詞匯環境;
VariableEnvironment,我們叫作變量環境。
所以,執行上下文可以從概念上標識如下:
ExecutionContext = { LexicalEnvironment =詞匯環境, VariableEnvironment = , }
官方 ES6 是這么定義詞匯環境的:
詞匯環境是一種規范類型,用于根據 ECMAScript 代碼的詞法嵌套結構定義標識符與特定變量和函數的關聯。詞匯環境由環境記錄和的可能為 null 引用的外部詞匯環境組成。
簡單來說,詞匯環境就是一種維護標識符到變量的映射,這里標識符指變量或函數的名字,而變量指的是一個實際對象(包括函數對象、數組對象)或基本值的引用。
每個詞匯環境由三部分組成:
環境記錄
外部環境引用
this binding
我們還需要再繼續展開講:
環境記錄
環境記錄,就是變量和函數聲明存儲在詞法環境中的位置。有兩種環境記錄:
聲明式環境記錄
對象環境記錄
前者主要就是存放變量、函數這類聲明了的,后者則是對全局的代碼進行記錄,例如全局綁定的 window。
注意,對于函數,環境記錄還會包含 arguments 對象,用于映射傳入函數的參數和記錄傳入參數的個數。我們舉個例子就很明白了:
function foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2},
外部環境引用
對外部環境的引用意味著它可以訪問其外部詞匯環境。這意味著如果在當前詞匯環境中找不到它們,JavaScript 引擎可以在外部環境中查找變量。
this binding
這一部分就是講 this 是怎么設置的。
在全局執行上下文,this 指向全局對象,比如瀏覽器環境下就是 window。
【基礎知識】在函數執行上下文里,this 就取決于函數調用的方式。如果是通過對象引用,那么 this 就是這個對象,不然的話,this 就會是全局對象或者 undefined (嚴格模式下)。舉個例子:
const person = { name: "peter", birthYear: 1994, calcAge: function() { console.log(2018 - this.birthYear); } } person.calcAge(); // "this" refers to "person", because "calcAge" was called with //"person" object reference const calculateAge = person.calcAge; calculateAge(); // "this" refers to the global window object, because no object reference was given
綜上:詞匯環境的偽代碼如下:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here } outer:變量環境, this: } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here } outer: , this: } }
它也是一個詞法環境,因此它具有上面定義的詞法環境的所有內容。唯一的不同是,在 ES6 中,詞法環境和變量環境這兩個,前者用于存儲函數聲明和變量(let 和 const)綁定,而后者僅用于存儲變量(var)綁定。
執行階段在這個階段,變量賦值都結束了,代碼也最終被執行掉。
舉個例子:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
執行上述代碼時,JavaScript 引擎會創建一個全局執行上下文來執行全局代碼。因此,在創建階段,全局執行上下文將如下所示:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer:, ThisBinding: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: , ThisBinding: } }
在執行階段,完成變量賦值。因此,在執行階段,全局執行上下文將看起來像這樣:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: 20, b: 30, multiply: < func > } outer:, ThisBinding: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: , ThisBinding: } }
當遇到函數 multiply(20,30) 被調用時,會創建一個新的函數執行上下文來執行函數代碼。因此,在創建階段,函數執行上下文將如下所示:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer:, ThisBinding: , }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: , ThisBinding: } }
在此之后,執行上下文將走完執行階段,這意味著完成了對函數內部變量的賦值。因此,在執行階段,函數執行上下文將如下所示:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer:, ThisBinding: , }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: 20 }, outer: , ThisBinding: } }
函數完成后,返回的值存儲在 c 中。因此全局詞匯環境得到了更新。之后,全局代碼完成,程序結束。
注意!你可能發現一個有意思的東西,就是在創建階段,let 和 const 定義的變量是「未初始化」狀態,而 var 定義的則是 undefined。
這是因為,在定義階段,代碼會掃描變量和函數聲明,函數聲明會在環境中被完整存著,對 var 定義的就會被初始化成 undefined,而 let 和 const 定義的就成了未初始化狀態。
這就是為什么,我們如果在 var 定義的變量定義之前使用它,會得到 undefined,但在 let 或 const 定義的變量定義之前使用會報 error。
這就是我們所說的提升。
Javascript Hoisting:In javascript, every variable declaration is hoisted to the top of its declaration context.
另一個點則是,如果在執行階段,JavaScript 引擎找不到 let 變量實際的值,他就會被賦值為 undefined。
源地址:https://blog.bitsrc.io/unders...
公私有域和方法這篇文章主要介紹 V8 v7.2 和 Chrome 72 新的 class fields 語法,以及即將出現的 private class fields。
我們來創建一個 IncreasingCounter 實例:
const counter = new IncreasingCounter(); counter.value; // logs "Getting the current value!" // → 0 counter.increment(); counter.value; // logs "Getting the current value!" // → 1ES2015 class
如果使用 ES2015 class 語法,我們應該會這么實現 IncreasingCounter:
class IncreasingCounter { constructor() { this._count = 0; } get value() { console.log("Getting the current value!"); return this._count; } increment() { this._count++; } }
該類在原型上添上了一個 value getter 和 increment 方法。類有一個構造函數,它創建一個實例屬性 _count,并將其默認值設置為0。我們目前傾向于使用下劃線前綴來表示 _count 不應該由該類的使用者直接使用,但這只是一個約定;它不是真正的「私有」屬性,只是有這個特殊語義而已。
const counter = new IncreasingCounter(); counter.value; // logs "Getting the current value!" // → 0 // Nothing stops people from reading or messing with the // `_count` instance property.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100756.html
摘要:目前前端主要有以下四種方法會觸發對應的回調方法方法客戶端回調客戶端回調參考地址每日一瞥是團隊內部日常業界動態提煉,發布時效可能略有延后。 showImg(https://segmentfault.com/img/remote/1460000017975436?w=1200&h=630); 「ES2015 - ES2018」Rest / Spread Properties 梳理 Thr...
showImg(https://segmentfault.com/img/remote/1460000018793640?w=900&h=500); 簡介 安全、注入攻擊、XSS 13歲女學生被捕:因發布 JavaScript 無限循環代碼。 這條新聞是來自 2019年3月10日 很多同學匆匆一瞥便滑動屏幕去看下一條消息了,并沒有去了解這段代碼是什么,怎么辦才能防止這個問題。事情發生后為了抗議日本...
摘要:配置在設置項中確認包含增加設置項,值為一個字符串路徑,必須以結尾在模板中這樣引用在的目錄存放靜態文件開發期間使用極度低效時有別的做法注意默認為,一個列表,表示獨立于的靜態文件存放位置。 配置 1.在INSTALLED_APPS設置項中確認包含django.contrib.staticfiles 2.增加STATIC_URL設置項,值為一個字符串(路徑),必須以‘/’結尾 3.在模板中...
閱讀 2323·2023-04-26 00:28
閱讀 3067·2019-08-30 15:55
閱讀 2742·2019-08-30 12:47
閱讀 1550·2019-08-29 11:04
閱讀 3150·2019-08-28 18:14
閱讀 945·2019-08-28 18:11
閱讀 1671·2019-08-26 18:36
閱讀 3383·2019-08-23 18:21