-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 89.7 KB
/
content.json
1
{"meta":{"title":"sym blog","subtitle":"keep trying","description":"hard work will payoff","author":"Nyan Shen","url":"https://nyanshen.github.io","root":"/"},"pages":[{"title":"about","date":"2019-08-10T12:52:13.000Z","updated":"2019-08-11T00:36:19.882Z","comments":true,"path":"about/index.html","permalink":"https://nyanshen.github.io/about/index.html","excerpt":"","text":""},{"title":"404","date":"2019-07-27T03:32:07.000Z","updated":"2019-08-06T05:34:40.007Z","comments":true,"path":"404/index.html","permalink":"https://nyanshen.github.io/404/index.html","excerpt":"","text":""},{"title":"categories","date":"2019-08-10T12:51:04.000Z","updated":"2019-08-11T00:36:19.882Z","comments":true,"path":"categories/index.html","permalink":"https://nyanshen.github.io/categories/index.html","excerpt":"","text":""},{"title":"tags","date":"2019-08-10T12:47:47.000Z","updated":"2019-08-11T00:36:19.882Z","comments":true,"path":"tags/index.html","permalink":"https://nyanshen.github.io/tags/index.html","excerpt":"","text":""},{"title":"search","date":"2019-07-27T03:31:55.000Z","updated":"2019-08-06T05:34:40.010Z","comments":true,"path":"search/index.html","permalink":"https://nyanshen.github.io/search/index.html","excerpt":"","text":""}],"posts":[{"title":"javascript Event Loop","slug":"event loop","date":"2019-08-25T01:02:14.000Z","updated":"2019-08-25T01:02:55.484Z","comments":true,"path":"2019/08/25/event loop/","link":"","permalink":"https://nyanshen.github.io/2019/08/25/event loop/","excerpt":"","text":"1. 堆,栈,队列堆(Heap)堆是一种数据结构,是利用完全二叉树维护的一组数据,堆分为两种,一种为最大堆,一种为最小堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。堆是线性数据结构,相当于一维数组,有唯一后继。 栈(Stack)栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。 栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。栈是只能在某一端插入和删除的特殊线性表。 队列(Queue)特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。 队列中没有元素时,称为空队列。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out) 2. Event loop在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。 MacroTask(宏任务)script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。 MicroTask(微任务)Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver 3. 浏览器中的Event LoopJavascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。 JS调用栈JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。 同步任务和异步任务Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。 事件循环的进程模型 择当前要执行的任务队列,选择任务队列中最先进入的任务,如果任务队列为空即null,则执行跳转到微任务(MicroTask)的执行步骤。 将事件循环中的任务设置为已选择任务。 执行任务。 将事件循环中当前运行任务设置为null。 将已经运行完成的任务从任务队列中删除。 microtasks步骤:进入microtask检查点。 更新界面渲染。 返回第一步。 执行进入microtask检查点时,用户代理会执行以下步骤: 设置microtask检查点标志为true。 当事件循环microtask执行不为空时:选择一个最先进入的microtask队列的microtask,将事件循环的microtask设置为已选择的microtask,运行microtask,将已经执行完成的microtask为null,移出microtask中的microtask。 清理IndexDB事务 设置进入microtask检查点的标志为false。 执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。 4. setTimeout Promise12345678910111213141516console.log('script start');setTimeout(function() { console.log('start setTimeout'); Promise.resolve().then(function() { console.log('promise4'); }) console.log('end setTimeout');}, 0);Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end'); 第一次执行:执行同步代码,将宏任务(Tasks)和微任务(Microtasks)划分到各自队列中。 123456Tasks:run script、 setTimeout callbackMicrotasks:Promise then JS stack: script Log: script start、script end。 第二次执行:执行宏任务run script后,检测到微任务(Microtasks)队列中不为空,执行Promise1,执行完成Promise1后,调用Promise2.then,放入微任务(Microtasks)队列中,再执行Promise2.then。 12345678910111213Tasks:run script、 setTimeout callbackMicrotasks:Promise then Promise then JS stack: Promise1 callback Log: script start、script end、promise1、promise2// 2Tasks:run script、 setTimeout callbackMicrotasks:Promise then JS stack: Promise2 callback Log: script start、script end、promise1、promise2 第三次执行:当微任务(Microtasks)队列中为空时,执行宏任务(Tasks),执行setTimeout同步代码,将宏任务(setTimeout)的微任务(Microtasks)划分到各自队列中。。 123456Tasks:setTimeout callbackMicrotasks: Promise thenJS stack: setTimeout callbackLog: script start、script end、promise1、promise2、start setTimeout、 end setTimeout 第四次执行:执行宏任务setTimeout后,检测到微任务(Microtasks)队列中不为空,执行Promise3 123456Tasks:setTimeout callbackMicrotasks: Promise thenJS stack: promise3 callbackLog: script start、script end、promise1、promise2、start setTimeout、 end setTimeout、 promise3 最后清空Tasks队列和JS stack。 123456Tasks:Microtasks: JS stack:Log: script start、script end、promise1、promise2、start setTimeout、 end setTimeout、 promise3 5. Async awaitasync/await 在底层转换成了 promise 和 then 回调函数。也就是说,这是 promise 的语法糖。每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。async/await 的实现,离不开 Promise。从字面意思来理解,async 是“异步”的简写,而 await 是 async wait 的简写可以认为是等待异步方法执行完成。 123456789101112131415161718192021222324252627282930console.log('script start') // 1async function async1() { await async2() console.log('async1 end') // 5, async 的 then}async function async2() { console.log('async2 end') // 2}async1()setTimeout(function() { console.log('setTimeout')}, 0)new Promise(resolve => { console.log('Promise') // 3 resolve()}) .then(function() { console.log('promise1') // 6 }) .then(function() { console.log('promise2') // 7 })console.log('script end') // 4task run script(include async1(), promise resolve), setTimeoutmicroTask async1 then, Promise then,","categories":[],"tags":[{"name":"javascript","slug":"javascript","permalink":"https://nyanshen.github.io/tags/javascript/"}]},{"title":"clone && deepClone","slug":"clone","date":"2019-08-23T13:00:14.000Z","updated":"2019-08-24T23:49:30.130Z","comments":true,"path":"2019/08/23/clone/","link":"","permalink":"https://nyanshen.github.io/2019/08/23/clone/","excerpt":"","text":"1. shallow clone JavaScript原始类型: Undefined、Null、Boolean、Number、String、Symbol(栈内存)JavaScript引用类型:Object (堆内存) 浅克隆之所以被称为浅克隆,是因为对象只会被克隆最外部的一层,至于更深层的对象,则依然是通过引用指向同一块堆内存. 123456789101112131415161718192021222324// shallow_clone.tsexport const shallowClone = (object: any) => { const newObject = {}; for (const key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } } return newObject;}// shallow_clone.test.tsimport { shallowClone } from \"../../util/clone\";it(\"new object in deep object should be the same\", () => { const mockData = { a: 1, b: [1, 2, 3], c: { d: 4 } } const newObject: any = shallowClone(mockData); expect(newObject).toEqual(mockData); expect(newObject).not.toBe(mockData); expect(newObject.c.d).toBe(mockData.c.d); // 更深层的对象,则依然是通过引用指向同一块堆内存}) 2. JSON.parse method便捷实现深克隆的方法, JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,这两个方法结合起来就能产生一个便捷的深克隆. 123456789101112export const jsonClone = (object: any) => { return JSON.parse(JSON.stringify(object));}it(\"new object in deep object should not be the same\", () => { const mockData = { a: 1, b: [1, 2, 3], c: { d: { e: 4 } } } const newObject: any = jsonClone(mockData); expect(newObject.c.d).not.toBe(mockData.c.d);}) 这个方法的缺点: 他无法实现对函数 、RegExp等特殊对象的克隆 会抛弃对象的constructor,所有的构造函数会指向Object 对象有循环引用,会报错 1234567891011121314151617181920212223242526272829303132333435363738it(\"clone complex object like RegExp\", () => { // constructor function person(name) { this.name = name; } const personInstance = new person(\"test\"); const mockData = { a: /^[1-9]/i, b: new RegExp(\"^[a-z]\", \"g\"), c: [1], d: new Array(1), // 稀疏数组, e: personInstance, f: person } const newObject = jsonClone(mockData); /** * old object { a: /^[1-9]/i, b: /^[a-z]/g, c: [ 1 ], d: [ <1 empty item> ], e: person { name: 'test' }, f: [Function: person] } * new object { a: {} b: {}, // RegExp c: [ 1 ], d: [ null ], // Array e: { name: 'test' }, // constructor f: undefind } // function } */ expect(newObject.a).toEqual({}); expect(newObject.b).toEqual({}); expect(newObject.c[0]).toBe(1); expect(newObject.d[0]).toBe(null); expect(mockData.d[0]).toBe(undefined);}) 对象的循环引用会抛出错误 123456const oldObject = {};oldObject.a = oldObject;const newObject = JSON.parse(JSON.stringify(oldObject));console.log(newObject.a, oldObject.a); // TypeError: Converting circular structure to JSON 与JSON.parse clone效果差不多的clonedeep方法: 1234567891011121314export const cloneDeep = (object: any) => { if (typeof object !== \"object\") return object; const newObject = object instanceof Array ? [] : {}; for (const key in object) { if (object.hasOwnProperty(key)) { if (typeof object[key] === \"object\" && object[key] !== null) { newObject[key] = cloneDeep(object[key]); } else { newObject[key] = object[key]; } } } return newObject} 3. deep clone由于要面对不同的对象(正则、数组、Date等)要采用不同的处理方式,我们需要实现一个对象类型判断函数。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108// 对特殊对象进行类型判断const isType = (object, type): boolean => { if (typeof object !== \"object\") return false; const typeString = Object.prototype.toString.call(object); switch (type) { case \"Array\": return typeString === \"[object Array]\"; case \"Date\": return typeString === \"[object Date]\"; case \"RegExp\": return typeString === \"[object RegExp]\"; default: return false; }}// 实现一个提取flags的函数const getRegExpFlags = (regExp: RegExp) => { let flags = \"\"; if (regExp.global) flags += \"g\"; if (regExp.ignoreCase) flags += \"i\"; if (regExp.multiline) flags += \"m\"; return flags;}// Buffer对象、Promise、Set、Map暂未处理export const deepClone = oldObject => { // 维护两个储存循环引用的数组 const oldObjects = []; const newObjects = []; const _deepClone = oldObject => { // 递归直到oldobject为null时,或类型不为“object”时停止。 if (oldObject === null) return null; if (typeof oldObject !== 'object') return oldObject; let newObject, newProtoType; if (isType(oldObject, 'Array')) { // 对数组做特殊处理 newObject = []; } else if (isType(oldObject, 'RegExp')) { // 对正则对象做特殊处理 newObject = new RegExp(oldObject.source, getRegExpFlags(oldObject)); if (oldObject.lastIndex) newObject.lastIndex = oldObject.lastIndex; } else if (isType(oldObject, 'Date')) { // 对Date对象做特殊处理 newObject = new Date(oldObject.getTime()); } else { // 处理对象原型 newProtoType = Object.getPrototypeOf(oldObject); // 利用Object.create切断原型链 newObject = Object.create(newProtoType); } // 处理循环引用 const index = oldObjects.indexOf(oldObject); if (index != -1) { // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象 return newObjects[index]; } oldObjects.push(oldObject); newObjects.push(newObject); for (const key in oldObject) { if (oldObject.hasOwnProperty(key)) { // newObject 已根据条件做了特殊处理 newObject[key] = _deepClone(oldObject[key]); } } return newObject; }; return _deepClone(oldObject);};// testit(\"test deep clone method\", () => { // constructor function person(name) { this.name = name; } const personInstance = new person(\"test\"); const mockData = { a: null, b: [1, 2, 3], c: { d: { e: 4 } }, f: new RegExp(\"^[a-z]\", \"g\"), h: new Array(1, 4, 6), i: personInstance, j: person, l: \"test\", m: new Array(2) } const newObject: any = deepClone(mockData); console.log(newObject) /** * { a: null, b: [ 1, 2, 3 ], c: { d: { e: 4 } }, f: /^[a-z]/g, h: [ 1, 4, 6 ], i: person { name: 'test' }, j: [Function: person], l: 'test', m: [] } */}) 对于Buffer对象、Promise、Set、Map可能都需要我们做特殊处理,另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间,不过一个基本的深克隆函数我们已经实现了,在生产环境中最好用lodash的深克隆实现.","categories":[],"tags":[{"name":"javascript","slug":"javascript","permalink":"https://nyanshen.github.io/tags/javascript/"}]},{"title":"jest mock axios complex implement","slug":"login test use jest testing-library-react","date":"2019-08-14T12:38:39.000Z","updated":"2019-08-15T07:44:15.971Z","comments":true,"path":"2019/08/14/login test use jest testing-library-react/","link":"","permalink":"https://nyanshen.github.io/2019/08/14/login test use jest testing-library-react/","excerpt":"","text":"1. 登录页面场景 进入登录页面时,获取验证码,接口返回一个imageId和content。 用户输入登录名,密码,和验证码。 用户点击登录,调用验证码是否正确接口,返回imageVerification为true时,说明验证码填写正确。 然后请求获取token接口,返回用户信息和其他相关信息,将返回信息存入localstorage. 2. 集成测试12345678910111213141516171819202122import {simpleLocalStorage} from \"simple-storage\";import {act, fireEvent} from \"@testing-library/react\";it(\"currentUser should be in the localStorage when login success\", () => { const container = RenderWithRouter(<Login />); // some same code ignore const usernameElem: HTMLInputElemnt = container.querySelector(\"#username\"); const passwordElem = getByPlaceholderText(/password/i) as HTMLInputElemnt; await act(async () => { fireEvent.change(usernameElem,{target: {value: username}}); }) process.nextTick(() => { fireEvent.click(submitElem); expect(usernameElem.value).toEqual(username); setTimeout( () => { const storageData = simpleLocalStorage.getItem(\"currentUser\"); expect(storageData).not.toBeNull(); }) })})","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"testing-library-react","slug":"testing-library-react","permalink":"https://nyanshen.github.io/tags/testing-library-react/"}]},{"title":"jest mock axios complex implement","slug":"jest mock axios complex implement","date":"2019-08-14T03:26:30.000Z","updated":"2019-08-14T12:38:16.483Z","comments":true,"path":"2019/08/14/jest mock axios complex implement/","link":"","permalink":"https://nyanshen.github.io/2019/08/14/jest mock axios complex implement/","excerpt":"","text":"1. mock axiosto implement this, we need ensure we have an axios file in your project.filename must be axios, axios.js or axios.ts 123456// axios.tsconst mockAxios: any = jest.genMockFromModule(\"axios\");mockAxios.create = jest.fn(() => mockAxios);export default mockAxios; 2. mock some service data;you can get the mock data according to the special url . 1234567891011const mockData: any = { \"/api/sample/data\": { data: { }, status: 200 }}export const getMockDataByUrl = (url: string) => { return mockData[url];} 3. mock axios request method123456789// axios.tsimport {getMockDataByUrl} from \"service\";mockAxios.request = (params: any) => { const mockData = getMockDataByUrl(params.url); return new Promise((resolve) => { resolve(mockData) })}","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"axios","slug":"axios","permalink":"https://nyanshen.github.io/tags/axios/"}]},{"title":"testing-library/react define renderer like renderwithRouter, renderWithRedux","slug":"testing-library: react define renderer","date":"2019-08-13T00:36:13.000Z","updated":"2019-08-13T12:37:17.838Z","comments":true,"path":"2019/08/13/testing-library: react define renderer/","link":"","permalink":"https://nyanshen.github.io/2019/08/13/testing-library: react define renderer/","excerpt":"","text":"1. mock renderWithRouter123456789101112131415// this is a handy function that I would utilize for any component// that relies on the router being in contextconst renderWithRouter = ( ui, {route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},) => { return { ...render(<Router history={history}>{ui}</Router>), // adding `history` to the returned utilities to allow us // to reference it in our tests (just try to avoid using // this to test implementation details). history, }}export defalut renderWithRouter 2. relative test123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869import React from 'react'import {withRouter} from 'react-router'import {Link, Route, Router, Switch} from 'react-router-dom'import {createMemoryHistory} from 'history'import {render, fireEvent} from '@testing-library/react'const About = () => <div>You are on the about page</div>const Home = () => <div>You are home</div>const NoMatch = () => <div>No match</div>const LocationDisplay = withRouter(({location}) => ( <div data-testid=\"location-display\">{location.pathname}</div>))function App() { return ( <div> <Link to=\"/\">Home</Link> <Link to=\"/about\">About</Link> <Switch> <Route exact path=\"/\" component={Home} /> <Route path=\"/about\" component={About} /> <Route component={NoMatch} /> </Switch> <LocationDisplay /> </div> )}// Ok, so here's what your tests might look like// this is a handy function that I would utilize for any component// that relies on the router being in contextfunction renderWithRouter( ui, {route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},) { return { ...render(<Router history={history}>{ui}</Router>), // adding `history` to the returned utilities to allow us // to reference it in our tests (just try to avoid using // this to test implementation details). history, }}test('full app rendering/navigating', () => { const {container, getByText} = renderWithRouter(<App />) // normally I'd use a data-testid, but just wanted to show this is also possible expect(container.innerHTML).toMatch('You are home') const leftClick = {button: 0} fireEvent.click(getByText(/about/i), leftClick) // normally I'd use a data-testid, but just wanted to show this is also possible expect(container.innerHTML).toMatch('You are on the about page')})test('landing on a bad page', () => { const {container} = renderWithRouter(<App />, { route: '/something-that-does-not-match', }) // normally I'd use a data-testid, but just wanted to show this is also possible expect(container.innerHTML).toMatch('No match')})test('rendering a component that uses withRouter', () => { const route = '/some-route' const {getByTestId} = renderWithRouter(<LocationDisplay />, {route}) expect(getByTestId('location-display').textContent).toBe(route)}) 3. render with redux1234const renderWithRedux = (ui, { initialState, store = createStore(reducer, initialState), } = {}) => ({ ...render(<Provider store={store}>{ui}</Provider>), store,});","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"react hooks","slug":"react-hooks","permalink":"https://nyanshen.github.io/tags/react-hooks/"}]},{"title":"testing-library/jest-dom config issue","slug":"testing library: jest-dom config issue","date":"2019-08-11T08:10:20.000Z","updated":"2019-08-13T12:39:03.055Z","comments":true,"path":"2019/08/11/testing library: jest-dom config issue/","link":"","permalink":"https://nyanshen.github.io/2019/08/11/testing library: jest-dom config issue/","excerpt":"","text":"1. running issuewhen I directly config the testing-library/jest-dom in jest.config.js,it will come as follow: TypeScript diagnostics (customize using [jest-config].globals.ts-jest.diagnostics option)Property ‘toBeInTheDocument’ does not exist on type ‘Matchers‘.read the intruduction here 2. configuring again then can use the library in global env1234567891011121314// config/setup-test.jsrequire(\"@testing-library/jest-dom/extend-expect\");// jest.config.js add globals{ globals: { 'ts-jest': { diagnostics: false } }, setupFilesAfterEnv: [ \"<rootDir>/config/setup-enzyme.js\", \"<rootDir>/config/setup-test.js\" ]} 3. node version request it will run errors when the node version under 8.make sure you have the latest node version. 4. babel-plugin-react-remove-properties如果考量到 data-testid 被 build 後會被看見,也可以透過 babel-plugin-react-remove-properties 將 data-testid 移除。","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"react hooks","slug":"react-hooks","permalink":"https://nyanshen.github.io/tags/react-hooks/"}]},{"title":"webpack 4.x common config","slug":"webpack common config","date":"2019-08-10T02:50:22.000Z","updated":"2019-08-11T00:36:19.881Z","comments":true,"path":"2019/08/10/webpack common config/","link":"","permalink":"https://nyanshen.github.io/2019/08/10/webpack common config/","excerpt":"","text":"1. install dependencieswebpack basic 1npm i webpack weebpack-cli webpack-merge -D html packing plugin 1npm i html-webpack-plugin -D css packing plugin, It supports On-Demand-Loading of CSS and SourceMaps. 123npm i style-loader css-loader postcss-loader scss-loader autoprefixer -Dnpm i mini-css-extract-plugin OptimizeCssAssetsPlugin -Dnpm i sass-resources-loader node-sass -D scss-loader将scss样式转换为css, postcss-loader对转好的css样式做一些列处理 css-loader将处理好的css样式转为字符串, style-loader将css-loader打包好的样式字符串,载入html的style标签上。 miniCssExtractLoader将html里的样式,抽取出来放到link标签引入 autoprefixer样式智能加后缀 build after clean 1npm i clean-webpack-plugin -D 加载图片资源 1npm i file-loader url-loader -D 打包体积优化 1npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D hash: 每次编译compilation对象的hash,全局一致,跟每次编译有关,跟单个文件无关,不推荐使用 chunkhash: chunk的hash,chunk中包含的任一模块发生改变,则chunkhash发生变化,推荐使用。 contenthash: css文件特有的hash值,是根据css文件内容计算出来的,css发生变化则其值发生变化,推荐css导出中使用 分析打包结果 1npm i webpack-bundle-analyzer -D 2. webpack.base.config12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394const path = require(\"path\");const HtmlWebpackPlugin = require(\"html-webpack-plugin\");const MiniCssExtractPlugin = require(\"mini-css-extract-plugin\");const isDev = process.env.NODE_ENV === \"development\";const styleLoader = isDev ? \"style-loader\" : MiniCssExtractPlugin.loader;module.exports = { mode: isDev ? \"development\" : \"production\", entry: \"./src/index.tsx\", resolve: { extensions: [\".ts\", \".tsx\", \".js\", \".json\"], alias: { \"@assets\": path.resolve(__dirname, \"src/assets/\"), \"@components\": path.resolve(__dirname, \"src/components/\") } }, module: { rules: [ { test: /\\.tsx?$/, exclude: /node_modules/, loader: \"awesome-typescript-loader\" }, { test: /\\.js$/, enforce: \"pre\", loader: \"source-map-loader\" }, { test: /\\.css$/, use: [ styleLoader, { loader: \"css-loader\", options: { importLoaders: 1 } }, \"postcss-loader\" ] }, { test: /\\.scss$/, use: [ styleLoader, { loader: \"css-loader\", options: { importLoaders: 3 } }, \"postcss-loader\", \"sass-loader\", { loader: \"sass-resources-loader\", options: { resources: [ \"./src/styles/_variables.scss\", \"./src/styles/_mixins.scss\" ] } } ] }, { test: /\\.(png|jpe?g|svg|gif)$/, use: { loader: \"url-loader\", options: { limit: 3 * 1024 //3k, 超过3k不被处理为base64 } } }, { test: /\\.(eot|woff|woff2|ttf)$/, loader: \"file-loader\", query: { name: \"assets/[name].[hash].[ext]\" } } ] }, externals: { \"react\": \"React\", \"react-dom\": \"ReactDOM\" }, plugins: [ new HtmlWebpackPlugin({ template: \"./src/index.html\", filename: \"index.html\" }) ]} 3. webpack.development.config12345678910111213141516171819202122232425262728293031const webpack = require(\"webpack\");module.exports = { devServer: { port: 3000, hot: true, open: \"Chrome\", inline: true, //自动刷新 historyApiFallback: true, overlay: { warnings: true, errors: true }, proxy: { \"/api/*\": { target: \"http://localhost:12306\", changeOrigin: false, secure: false } } }, plugins: [ new webpack.DefinePlugin({ \"process.env.NODE_ENV\": JSON.stringify(\"development\") }), //开启HMR(热替换功能,替换更新部分,不重载页面!) new webpack.HotModuleReplacementPlugin(), //显示模块相对路径 new webpack.NamedModulesPlugin() ]} 4. webpack.production.config1234567891011121314151617181920212223242526272829303132333435363738394041424344const path = require(\"path\");const webpack = require(\"webpack\");const TerserPlugin = require(\"terser-webpack-plugin\");const { CleanWebpackPlugin } = require(\"clean-webpack-plugin\");const MiniCssExtractPlugin = require(\"mini-css-extract-plugin\");const OptimizeCssAssetsPlugin = require(\"optimize-css-assets-webpack-plugin\");const BundleAnalyzerPlugin = require(\"webpack-bundle-analyzer\").BundleAnalyzerPlugin;module.exports = { output: { path: path.resolve(__dirname, './dist'), filename: \"js/bundle.[chunkhash:8].js\", chunkFilename: 'js/[name].[id].[chunkhash:8].js' }, plugins: [ new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: \"css/[name].[contenthash:8].css\", chunkFilename: \"css/[id].[contenthash:8].css\" }), new OptimizeCssAssetsPlugin(), new webpack.DefinePlugin({ \"process.env.NODE_ENV\": JSON.stringify(\"production\") }), new BundleAnalyzerPlugin() ], optimization: { minimizer: [ new TerserPlugin({ cache: true, parallel: true, terserOptions: { unused: true, // 删除无用代码 drop_debugger: true, drop_console: true, dead_code: true } }) ], splitChunks: { chunks: 'all' } }}","categories":[],"tags":[{"name":"react","slug":"react","permalink":"https://nyanshen.github.io/tags/react/"},{"name":"webpack","slug":"webpack","permalink":"https://nyanshen.github.io/tags/webpack/"}]},{"title":"testing reack hooks","slug":"react hooks testing","date":"2019-08-09T21:25:30.000Z","updated":"2019-08-11T00:36:19.878Z","comments":true,"path":"2019/08/10/react hooks testing/","link":"","permalink":"https://nyanshen.github.io/2019/08/10/react hooks testing/","excerpt":"","text":"1. Can we use Jest or Enzyme?Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)The error above means that Hooks are not yet supported in Enzyme as seen in this issue here.As a result, we cannot use Enzyme to carry out component tests for React Hooks. So what can be used? 2. Introducing react-testing-libraryreact-testing-library is a very light-weight solution for testing React components. It extends upon react-dom and react-dom/test-utils to provide light utility functions. It encourages you to write tests that closely resemble how your react components are used. 3. InstallationThis module is distributed via npm which is bundled with node and should be installed as one of your project’s devDependencies: 1npm install --save-dev @testing-library/react This library has peerDependencies listings for react and react-dom. 4. Suppressing unnecessary warnings on React DOM 16.8There is a known compatibility issue with React DOM 16.8 where you will see the following warning: 1Warning: An update to ComponentName inside a test was not wrapped in act(...). If you cannot upgrade to React DOM 16.9, you may suppress the warnings by adding the following snippet to your test configuration learn more 123456789101112131415// this is just a little hack to silence a warning that we'll get until we// upgrade to 16.9: https://github.com/facebook/react/pull/14853const originalError = console.errorbeforeAll(() => { console.error = (...args) => { if (/Warning.*not wrapped in act/.test(args[0])) { return } originalError.call(console, ...args) }})afterAll(() => { console.error = originalError}) 4. configuring some import12import '@testing-library/react/cleanup-after-each' // deprecatedimport '@testing-library/jest-dom/extend-expect' // not work in jest config file when use typescript these imports are something you’d normally configure Jest to import for you.automatically. Learn more in the setup docs: https://testing-library.com/docs/react-testing-library/setup#cleanup sample test Header1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859import * as React from \"react\";import {useState} from \"react\";const Header = () => { const [value, setValue] = useState<string>(\"\"); const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { setValue(event.target.value) } return ( <div className=\"header\"> <div className=\"header-content\"> TodoList <input value={value} data-testid=\"header_input\" onChange={handleInputChange} /> </div> </div> )}export default Header;// Header.test.tsximport * as React from \"react\";import { render, fireEvent } from \"@testing-library/react\";import \"@testing-library/jest-dom/extend-expect\";import Header from \"../Header\";let wrapper = null;describe(\"test Header component\", () => { beforeEach(() => { wrapper = render(<Header />); }) it(\"should contain title 'TodoList'\", () => { const {getByText} = wrapper; const title = \"TodoList\"; expect(getByText(title)).toBeInTheDocument(); }) it(\"input initial value should be empty\", () => { const {getByTestId} = wrapper; expect(getByTestId(\"header_input\").value).toEqual(\"\"); }) it(\"value should be set when input some text\", () => { const {getByTestId} = wrapper; const inputElem = getByTestId(\"header_input\"); const context = \"input text test\"; fireEvent.change(inputElem, {target: { value: context }}); expect(inputElem.value).toEqual(context); })})","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"react hooks","slug":"react-hooks","permalink":"https://nyanshen.github.io/tags/react-hooks/"}]},{"title":"jest enzyme unit test react","slug":"unit test for react","date":"2019-08-09T10:10:12.000Z","updated":"2019-08-11T00:36:19.881Z","comments":true,"path":"2019/08/09/unit test for react/","link":"","permalink":"https://nyanshen.github.io/2019/08/09/unit test for react/","excerpt":"","text":"1. 测试类型 单元测试:指的是以原件的单元为单位,对软件进行测试。单元可以是一个函数,也可以是一个模块或一个组件,基本特征就是只要输入不变,必定返回同样的输出。一个软件越容易些单元测试,就表明它的模块化结构越好,给模块之间的耦合越弱。React的组件化和函数式编程,天生适合进行单元测试 功能测试:相当于是黑盒测试,测试者不了解程序的内部情况,不需要具备编程语言的专门知识,只知道程序的输入、输出和功能,从用户的角度针对软件界面、功能和外部结构进行测试,不考虑内部的逻辑 集成测试:在单元测试的基础上,将所有模块按照设计要求组装成子系统或者系统,进行测试 冒烟测试:在正式全面的测试之前,对主要功能进行的与测试,确认主要功能是否满足需要,软件是否能正常运行 2. 开发模式 TDD: 测试驱动开发,英文为Testing Driven Development,强调的是一种开发方式,以测试来驱动整个项目,即先根据接口完成测试编写,然后在完成功能是要不断通过测试,最终目的是通过所有测试 BDD: 行为驱动测试,英文为Behavior Driven Development,强调的是写测试的风格,即测试要写的像自然语言,让项目的各个成员甚至产品都能看懂测试,甚至编写测试 TDD和BDD有各自的使用场景,BDD一般偏向于系统功能和业务逻辑的自动化测试设计;而TDD在快速开发并测试功能模块的过程中则更加高效,以快速完成开发为目的。 3. JestJest是Facebook开源的一个前端测试框架,主要用于React和React Native的单元测试,已被集成在create-react-app中。Jest特点: 易用性:基于Jasmine,提供断言库,支持多种测试风格 适应性:Jest是模块化、可扩展和可配置的 沙箱和快照:Jest内置了JSDOM,能够模拟浏览器环境,并且并行执行 快照测试:Jest能够对React组件树进行序列化,生成对应的字符串快照,通过比较字符串提供高性能的UI检测 Mock系统:Jest实现了一个强大的Mock系统,支持自动和手动mock 支持异步代码测试:支持Promise和async/await 自动生成静态分析结果:内置Istanbul,测试代码覆盖率,并生成对应的报告 4. EnzymeEnzyme是Airbnb开源的React测试工具库库,它功能过对官方的测试工具库ReactTestUtils的二次封装,提供了一套简洁强大的 API,并内置Cheerio,实现了jQuery风格的方式进行DOM 处理,开发体验十分友好。在开源社区有超高人气,同时也获得了React 官方的推荐。 三种渲染方法 shallow:浅渲染,是对官方的Shallow Renderer的封装。将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,使得效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息 render:静态渲染,它将React组件渲染成静态的HTML字符串,然后使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构 mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期。用到了jsdom来模拟浏览器环境 三种方法中,shallow和mount因为返回的是DOM对象,可以用simulate进行交互模拟,而render方法不可以。一般shallow方法就可以满足需求,如果需要对子组件进行判断,需要使用render,如果需要测试组件的生命周期,需要使用mount方法。 常用方法simulate(event, mock):模拟事件,用来触发事件,event为事件名称,mock为一个event objectinstance():返回组件的实例find(selector):根据选择器查找节点,selector可以是CSS中的选择器,或者是组件的构造函数,组件的display name等at(index):返回一个渲染过的对象get(index):返回一个react node,要测试它,需要重新渲染contains(nodeOrNodes):当前对象是否包含参数重点 node,参数类型为react对象或对象数组text():返回当前组件的文本内容html(): 返回当前组件的HTML代码形式props():返回根组件的所有属性prop(key):返回根组件的指定属性state():返回根组件的状态setState(nextState):设置根组件的状态setProps(nextProps):设置根组件的属性 5. config explain setupFilesAfterEnv:配置文件,在运行测试案例代码之前,Jest会先运行这里的配置文件来初始化指定的测试环境 moduleFileExtensions:代表支持加载的文件名 testPathIgnorePatterns:用正则来匹配不用测试的文件 testRegex:正则表示的测试文件,测试文件的格式为xxx.test.js等 collectCoverage:是否生成测试覆盖报告,如果开启,会增加测试的时间 collectCoverageFrom:生成测试覆盖报告是检测的覆盖文件 moduleNameMapper:代表需要被Mock的资源名称 transform:用babel-jest来编译文件,生成ES6/7的语法, ts-jest编译typescript文件 6. jest对象 jest.fn(implementation):返回一个全新没有使用过的mock function,这个function在被调用的时候会记录很多和函数调用有关的信息 jest.mock(moduleName, factory, options):用来mock一些模块或者文件 jest.spyOn(object, methodName):返回一个mock function,和jest.fn相似,但是能够追踪object[methodName]的调用信息,类似Sinon 7. 常见断言expect(value):要测试一个值进行断言的时候,要使用expect对值进行包裹toBe(value):使用Object.is来进行比较,如果进行浮点数的比较,要使用toBeCloseTonot:用来取反toEqual(value):用于对象的深比较toMatch(regexpOrString):用来检查字符串是否匹配,可以是正则表达式或者字符串toContain(item):用来判断item是否在一个数组中,也可以用于字符串的判断toBeNull(value):只匹配nulltoBeUndefined(value):只匹配undefinedtoBeDefined(value):与toBeUndefined相反toBeTruthy(value):匹配任何使if语句为真的值toBeFalsy(value):匹配任何使if语句为假的值toBeGreaterThan(number): 大于toBeGreaterThanOrEqual(number):大于等于toBeLessThan(number):小于toBeLessThanOrEqual(number):小于等于toBeInstanceOf(class):判断是不是class的实例anything(value):匹配除了null和undefined以外的所有值resolves:用来取出promise为fulfilled时包裹的值,支持链式调用rejects:用来取出promise为rejected时包裹的值,支持链式调用toHaveBeenCalled():用来判断mock function是否被调用过toHaveBeenCalledTimes(number):用来判断mock function被调用的次数assertions(number):验证在一个测试用例中有number个断言被调用extend(matchers):自定义一些断言 8. 测试场景对组件节点进行测试12345678910111213141516171819202122it('should has Button', () => { const { wrapper } = setup(); expect(wrapper.find('Button').length).toBe(2);});it('should render 2 item', () => { const { wrapper } = setupByRender(); expect(wrapper.find('button').length).toBe(2);});it('should render item equal', () => { const { wrapper } = setupByMount(); wrapper.find('.item-text').forEach((node, index) => { expect(node.text()).toBe(wrapper.props().list[index]) });});it('click item to be done', () => { const { wrapper } = setupByMount(); wrapper.find('Button').at(0).simulate('click'); expect(props.deleteTodo).toBeCalled();}); 使用 snapshot 进行 UI 测试 1234567it('renders correctly', () => { const tree = renderer .create(<TodoList {...props} />) .toJSON(); expect(tree).toMatchSnapshot();}); 测试组件的内部函数 123456789101112131415it('calls component handleTest', () => { // class中使用箭头函数来定义方法 const { wrapper } = setup(); const spyFunction = jest.spyOn(wrapper.instance(), 'handleTest'); wrapper.instance().handleTest(); expect(spyFunction).toHaveBeenCalled(); spyFunction.mockRestore();});it('calls component handleTest2', () => { //在constructor使用bind来定义方法 const spyFunction = jest.spyOn(TodoList.prototype, 'handleTest2'); const { wrapper } = setup(); wrapper.instance().handleTest2(); expect(spyFunction).toHaveBeenCalled(); spyFunction.mockRestore();});","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"react","slug":"react","permalink":"https://nyanshen.github.io/tags/react/"},{"name":"enzyme","slug":"enzyme","permalink":"https://nyanshen.github.io/tags/enzyme/"}]},{"title":"jest config issue assets && alias","slug":"jest config issue","date":"2019-08-08T12:21:50.000Z","updated":"2019-08-11T03:08:22.751Z","comments":true,"path":"2019/08/08/jest config issue/","link":"","permalink":"https://nyanshen.github.io/2019/08/08/jest config issue/","excerpt":"","text":"1. path mappingIf you use “baseUrl” and “paths” options in your tsconfig file, you should make sure the “moduleNameMapper” option in your Jest config is setup accordingly. ts-jest provides a helper to transform the mapping from tsconfig to Jest config format, but it needs the .js version of the config file. 2. sample hereWith the below config in your tsconfig: 1234567891011121314151617{ \"compilerOptions\": { \"paths\": { \"@App/*\": [\"src/*\"], \"common/*\": [\"common/*\"] } }}// jest.config.jsmodule.exports = { // [...] moduleNameMapper: { '^@App/(.*)$': '<rootDir>/src/$1', '^common/(.*)$': '<rootDir>/common/$1' }}; 3. Handling Static AssetsNext, let’s configure Jest to gracefully handle asset files such as stylesheets and images. Usually, these files aren’t particularly useful in tests so we can safely mock them out. However, if you are using CSS Modules then it’s better to mock a proxy for your className lookups. 12345678910\"moduleNameMapper\": { \"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/__mocks__/fileMock.js\", \"\\\\.(css|less)$\": \"<rootDir>/__mocks__/styleMock.js\" }// __mocks__/styleMock.jsmodule.exports = {};// __mocks__/fileMock.jsmodule.exports = 'test-file-stub';","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"typescript","slug":"typescript","permalink":"https://nyanshen.github.io/tags/typescript/"}]},{"title":"react typescript jest config (二)","slug":"react typescript jest config (二)","date":"2019-08-07T11:11:55.000Z","updated":"2019-08-11T00:36:19.880Z","comments":true,"path":"2019/08/07/react typescript jest config (二)/","link":"","permalink":"https://nyanshen.github.io/2019/08/07/react typescript jest config (二)/","excerpt":"","text":"1. Add a basic TypeScript configuration filetsconfig.jsonYou’ll want to bring your TypeScript files together - both the code you’ll be writing as well as any necessary declaration files. To do this, you’ll need to create a tsconfig.json which contains a list of your input files as well as all your compilation settings. Simply create a new file in your project root named tsconfig.json and fill it with the following contents: 123456789101112131415161718192021222324252627282930// tsconfig.json{ \"compilerOptions\": { \"outDir\": \"./dist/\", \"target\": \"es5\", \"module\": \"commonJs\", // 组织代码方式 \"sourceMap\": true, \"allowJs\": true, \"jsx\": \"react\", \"removeComments\": true, // 编译 js 的时候,删除掉注释 \"typeRoots\": [], // 默认所有可见的\"@types\"包会在编译过程中被包含进来 /** *添加types后,目前加index.tsx后会报错 */ \"types\": [\"node\", \"lodash\"], // 只有被列出来的包才会被包含进来 \"baseUrl\": \".\" }, \"exclude\": [ // 不需要编译 \"node_modules\", \"dist\", \"webpack\", \"jest\", \"enzyme\", \"**/*.test.ts\", \"**/*.test.tsx\" ], \"include\": [ \"./src/**/*\" ]} tsconfig module click here 2. webpack basic config123456789101112131415161718192021222324252627282930313233343536// webpack.bask.config.jsconst path = require(\"path\");const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: \"./src/index.tsx\", resolve: { extensions: [\".ts\", \".tsx\", \".js\", \".json\"], alias: { components: path.resolve(__dirname, \"src/components/\") } }, module: { rules: [ { test: /\\.tsx?$/, exclude: /node_modules/, loader: \"awesome-typescript-loader\" }, { test: /\\.js$/, enforce: \"pre\", loader: \"source-map-loader\" } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], externals: { react: \"React\", reactDom: \"ReactDom\" }} 3. jest enzyme basic configsetup enzyme config 1234// config/setup-test.tsimport {configure} from \"enzyme\";import * as Adapter from \"anzyme-adater-react-16\"configure({adapter: new Adapter()}); 1234567891011121314151617181920212223module.exports = { \"roots\": [ // 要测试的根目录默认为<rootDir> \"<rootDir>/src\" ], \"transform\": { \"^.+\\\\.tsx?$\": \"ts-jest\" }, // 设置识别哪些文件是测试文件(正则形式),与testMatch互斥 \"testRegex\": \"(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.tsx?$\", \"moduleFileExtensions\": [ \"ts\", \"tsx\", \"js\", \"jsx\", \"json\", \"node\" ], // 测试环境,默认值是:jsdom,可修改为node testRnviroment: \"jsdom\", // setup enzyme setupFilesAfterEnv: [\"<rootDir>/src/config/setup-test.ts\"], snapshotSerializers: [\"enzyme-to-json/serializer\"]} 4. jest-enzyme config(这一点实现有问题,请暂时忽略)The best setup is to use jest-environment-enzyme 1npm i jest-environment-enzyme jest-enzyme -D jest config add like: 1234567{ \"setupTestFrameworkScriptFile\": \"jest-enzyme\", \"testEnvironment\": \"enzyme\", \"testEnvironmentOptions\": { \"enzymeAdapter\": \"react16\" }} If you prefer not to use the environment, you can also do this: 123{ \"setupFilesAfterEnv\": ['./node_modules/jest-enzyme/lib/index.js'],} As with Create React App, when using jest-enzyme with TypeScript and ts-jest, you’ll need to add a setupTests.ts file to your app that explicitly imports jest-enzyme, and point the setupTestFrameworkScriptFile field in your jest.config.js or package.json. 123456// setupTests.tsimport 'jest-enzyme';// jest.config.js{ \"setupTestFrameworkScriptFile\": \"./src/config/setupTests.ts\" } ** notice: 测试组件文件后缀为.tsx **“setupTestFrameworkScriptFile” was replace by configuration “setupFilesAfterEnv”, which supports multi path. 123{ \"setupFilesAfterEnv\": ['./node_modules/jest-enzyme/lib/index.js', 'jest-enzyme'],}","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"typescript","slug":"typescript","permalink":"https://nyanshen.github.io/tags/typescript/"},{"name":"react","slug":"react","permalink":"https://nyanshen.github.io/tags/react/"},{"name":"enzyme","slug":"enzyme","permalink":"https://nyanshen.github.io/tags/enzyme/"}]},{"title":"react typescript jest config (一)","slug":"react typescript jest config (一)","date":"2019-08-06T09:20:10.000Z","updated":"2019-08-11T00:36:19.879Z","comments":true,"path":"2019/08/06/react typescript jest config (一)/","link":"","permalink":"https://nyanshen.github.io/2019/08/06/react typescript jest config (一)/","excerpt":"","text":"1. initialize projectcreate a folder projectNow we’ll turn this folder into an npm package. 1npm init -y This creates a package.json file with default values. 2. Install react typescript dependenciesFirst ensure Webpack is installed. 1npm i webpack webpack-cli webpack-merge html-webpack-plugin webpack-dev-server -D Webpack is a tool that will bundle your code and optionally all of its dependencies into a single .js file. Let’s now add React and React-DOM, along with their declaration files, as dependencies to your package.json file: 12npm i react react-dom npm i @types/react @types/react-dom -D That @types/ prefix means that we also want to get the declaration files for React and React-DOM. Usually when you import a path like “react”, it will look inside of the react package itself; however, not all packages include declaration files, so TypeScript also looks in the @types/react package as well. You’ll see that we won’t even have to think about this later on. Next, we’ll add development-time dependencies on the ts-loader and source-map-loader. 123npm i typescript ts-loader source-map-loader -Dornpm i awesome-typescript-loader source-map-loader -D Both of these dependencies will let TypeScript and webpack play well together. ts-loader helps Webpack compile your TypeScript code using the TypeScript’s standard configuration file named tsconfig.json. source-map-loader uses any sourcemap outputs from TypeScript to inform webpack when generating its own sourcemaps. This will allow you to debug your final output file as if you were debugging your original TypeScript source code. Please note that ts-loader is not the only loader for typescript. You could instead use awesome-typescript-loaderRead about the differences between them:https://github.com/s-panferov/awesome-typescript-loader#differences-between-ts-loaderNotice that we installed TypeScript as a development dependency. We could also have linked TypeScript to a global copy with npm link typescript, but this is a less common scenario. 3. Install jest enzyme dependencies1、install jest dependencies 1npm i jest @types/jest ts-jest -D 2、 install enzyme dependencies 12npm i enzyme enzyme-adapter-react-16 jest-enzyme enzyme-to-json -Dnpm i @types/enzyme @types/enzyme-adapter-react-16 -D 4. install another compiler for typescript use babel dependenciesinstall babel loader 123npm i @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript -Dnpm i babel-loader babel-plugin-import -D config babel 1234567891011module.exports={ presets: [ \"env\", \"react\", \"typascript\" ], plugins: [ [\"lodash\"], [\"import\", {libraryName: \"antd\", style: true}] ]} use babel loader instaed of ts-loader","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"},{"name":"typescript","slug":"typescript","permalink":"https://nyanshen.github.io/tags/typescript/"},{"name":"react","slug":"react","permalink":"https://nyanshen.github.io/tags/react/"},{"name":"enzyme","slug":"enzyme","permalink":"https://nyanshen.github.io/tags/enzyme/"}]},{"title":"jest test todo list","slug":"jest test todo list","date":"2019-08-04T01:23:11.000Z","updated":"2019-08-11T00:36:19.875Z","comments":true,"path":"2019/08/04/jest test todo list/","link":"","permalink":"https://nyanshen.github.io/2019/08/04/jest test todo list/","excerpt":"","text":"1. Header组件单元测试header组件主要由一个title,和一个输入框组成,输入框有以下几个处理情况: 输入框初始化为空 输入框内容为空时,回车事件不做任何操作 输入框内容不为空时,回车事件会调用添加todo的函数,并且清空输入框内容 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122// Header.jsxexport default class Header extends React.Component { constructor(props) { super(props); this.state = { value: \"\" } this.handleKeyUp = this.handleKeyUp.bind(this); this.handleInputChange = this.handleInputChange.bind(this); } handleKeyUp(e) { const { value } = this.state; if (e.keyCode === 13 && value) { this.props.addUndoItem(value) this.setState({value: \"\"}) } } handleInputChange(e) { this.setState({ value: e.target.value }) } render() { const { value } = this.state; return ( <div className=\"header\"> <div className=\"header-content\">TodoList <input value={value} data-test=\"input\" className=\"header-input\" placeholder=\"add todo\" onKeyUp={this.handleKeyUp} onChange={this.handleInputChange} /> </div> </div> ) }}// test/Header.test.jsximport React from \"react\";import { shallow } from \"enzyme\";import Header from \"../Header\";import { findTestWrapper } from \"common/util/testUtils\"describe(\"Header Componet Test\", () => { it(\"样式渲染正常\", () => { const wrapper = shallow(<Header />); expect(wrapper).toMatchSnapshot(); }) it(\"组件包含输入框\", () => { const wrapper = shallow(<Header />); const inputElem = findTestWrapper(wrapper, \"input\"); expect(inputElem.length).toBe(1); }) it(\"输入框内容初始化应该为空\", () => { const wrapper = shallow(<Header />); const inputElem = wrapper.find(\"[data-test='input']\"); expect(inputElem.prop(\"value\")).toEqual(\"\"); }) it(\"输入框随用户输入时发生改变\", () => { const wrapper = shallow(<Header />); const inputElem = wrapper.find(\"[data-test='input']\"); /** * 模拟change事件,输入test nyan */ inputElem.simulate(\"change\", { target: { value: \"test nyan\" } }) expect(wrapper.state(\"value\")).toEqual(\"test nyan\"); }) it(\"输入框没有内容时,回车时无反应\", () => { const fn = jest.fn(); const wrapper = shallow(<Header addUndoItem={fn} />); const inputElem = wrapper.find(\"[data-test='input']\"); wrapper.setState({ value: \"\" }) inputElem.simulate(\"kepUp\", { keyCode: 13 }) expect(fn).not.toHaveBeenCalled(); }) it(\"输入框有内容触发回车时,函数应该被调用\", () => { const fn = jest.fn(); const wrapper = shallow(<Header addUndoItem={fn} />); const inputElem = wrapper.find(\"[data-test='input']\"); // 准备数据 wrapper.setState({ value: \"jest react\" }) /** * 设置keyUp的keyCode模拟回车 */ inputElem.simulate(\"keyUp\", { keyCode: 13 }) expect(fn).toHaveBeenCalled(); expect(fn).toHaveBeenLastCalledWith(\"jest react\"); // 最后的参数 }) it(\"输入框有内容触发回车时,内容应该被清除\", () => { const fn = jest.fn(); const wrapper = shallow(<Header addUndoItem={fn} />); const inputElem = wrapper.find(\"[data-test='input']\"); // 准备数据 wrapper.setState({ value: \"jest react add one\" }) /** * 设置keyUp的keyCode模拟回车 */ inputElem.simulate(\"keyUp\", { keyCode: 13 }) const newInputElem = wrapper.find(\"[data-test='input']\"); expect(newInputElem.prop(\"value\")).toBe(\"\"); })}) 2. TodoList组件单元测试TodoList组件主要控制undoList数据状态的改变,实现改变数据项显示状态(输入框或文字)。 首先数据项undoList初始化为空 其次判断TodoList组件所包含那些子组件以及属性方法 每一个数据项都有修改和删除操作 通过状态的改变进行数据项修改 点击每一个数据项,会调用状态改变函数 当输入框失去焦点时,改变数据项的状态 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180import React from \"react\";import Header from \"../components/Header\"import UndoList from \"../components/UndoList\";export default class TodoList extends React.Component { constructor(props) { super(props); this.state = { undoList: [] } this.deleteItem = this.deleteItem.bind(this); this.addUndoItem = this.addUndoItem.bind(this); this.changeStatus = this.changeStatus.bind(this); this.handleBlur = this.handleBlur.bind(this); this.handleValueChange = this.handleValueChange.bind(this); } addUndoItem(value) { this.setState({ undoList: [...this.state.undoList, { status: \"div\", value }] }) } deleteItem(index) { const newUndoList = [...this.state.undoList] newUndoList.splice(index, 1) this.setState({ undoList: newUndoList }) } changeStatus(index) { const newUndoList = this.state.undoList.map((item, listIndex) => { if (listIndex === index) { return { ...item, status: \"input\" } } return { ...item, status: \"div\" } }) this.setState({ undoList: newUndoList }) } handleBlur(index) { const newUndoList = this.state.undoList.map((item, listIndex) => { if (listIndex === index) { return { ...item, status: \"div\" } } return item }) this.setState({ undoList: newUndoList }) } handleValueChange(index, value) { const newUndoList = this.state.undoList.map((item, listIndex) => { if (listIndex === index) { return { ...item, value } } return item }) this.setState({ undoList: newUndoList }) } render() { const { undoList } = this.state; return ( <> <Header addUndoItem={this.addUndoItem} /> <UndoList list={undoList} deleteItem={this.deleteItem} changeStatus={this.changeStatus} handleBlur={this.handleBlur} handleValueChange={this.handleValueChange} /> </> ) }}// todoList.test.jsimport React from \"react\";import { shallow } from \"enzyme\";import TodoList from \"../../../containers/TodoList\";describe(\"TodoList Comonent Test\", () => { it(\"初始化列表为空\", () => { const wrapper = shallow(<TodoList />); expect(wrapper.state(\"undoList\")).toEqual([]); }) it(\"Header组件存在addUndoItem的属性\", () => { const wrapper = shallow(<TodoList />); const header = wrapper.find(\"Header\"); //expect(header.prop(\"addUndoItem\")).toBe(wrapper.instance().addUndoItem); expect(header.prop(\"addUndoItem\")).toBeTruthy(); }) it(\"addUndoItem方法被调用时,undoList数据项新增\", () => { const wrapper = shallow(<TodoList />); /** * const header = wrapper.find(\"Header\"); * const addFn = header.prop(\"addUndoItem\"); * addFn(\"add item one\"); * 这样操作,测试就会跟Header组件耦合(像集成测试),应该修改为一下方案 */ const content = \"add item one\" wrapper.instance().addUndoItem(content) expect(wrapper.state(\"undoList\").length).toBe(1); expect(wrapper.state(\"undoList\")[0]).toEqual({ status: \"div\", value: content }) }) it(\"UndoList 组件应该有接收list,deleteItem,changeStatus, handleBlur属性\", () => { const wrapper = shallow(<TodoList />); const undoList = wrapper.find(\"UndoList\"); expect(undoList.prop(\"list\")).toBeTruthy(); expect(undoList.prop(\"deleteItem\")).toBeTruthy(); expect(undoList.prop(\"changeStatus\")).toBeTruthy(); expect(undoList.prop(\"handleBlur\")).toBeTruthy(); }) it(\"deleteItem被执行时,应该删除对应数据项\", () => { const wrapper = shallow(<TodoList />); const data =[ {status: \"div\",value: \"react\"}, {status: \"div\",value: \"ject\"}, {status: \"div\",value: \"enzyme\"}, ] wrapper.setState({undoList: data}); wrapper.instance().deleteItem(1); expect(wrapper.state(\"undoList\")).toEqual([data[0], data[2]]) }) it(\"changeStatus被执行时,undoList数据项被修改\", () => { const wrapper = shallow(<TodoList />); const data =[ {status: \"div\",value: \"react\"}, {status: \"div\",value: \"ject\"}, {status: \"div\",value: \"enzyme\"}, ] wrapper.setState({undoList: data}); wrapper.instance().changeStatus(1); expect(wrapper.state(\"undoList\")[1]).toEqual({ ...data[1], status: \"input\" }) }) it(\"handleBlur被执行时,undoList数据项被修改\", () => { const wrapper = shallow(<TodoList />); const data =[ {status: \"input\",value: \"react\"}, {status: \"div\",value: \"ject\"}, {status: \"div\",value: \"enzyme\"}, ] wrapper.setState({undoList: data}); wrapper.instance().handleBlur(0); expect(wrapper.state(\"undoList\")[0]).toEqual({ ...data[0], status: \"div\" }) })}) 3. undoList组件单元测试undoList组件是TodoList组件的子组件,控制显示todoList数据,主要包含title、统计添加的todo数据项个数、显示添加的数据项,以及父亲组件传入修改的数据项。 当没有添加todo数据项时,count的长度为0 当添加了todo数据项时,显示数量以及对应的数据项 当有todo数据项时,每个数据项显示删除按钮 当有todo数据项时,点击数据项时触发改变数据项状态函数 当数据项状态为可修改时,失去焦点时触发失去失去焦点函数 当数据项状态为可修改时,修改数据时触发数据项值改变的函数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157import React from \"react\";export default class UndoList extends React.Component { render() { const { list, deleteItem, changeStatus, handleBlur, handleValueChange } = this.props; return ( <div className=\"undo-list\"> <div className=\"undo-list-title\"> 正在进行 <span className=\"undo-list-count\" data-test=\"count\">{list.length}</span> </div> <ul className=\"undo-list-content\"> { list.map((item, index) => { return ( <li className=\"undo-list-item\" data-test=\"listItem\" key={`${item}-${index}`} onClick={() => changeStatus(index)} > {item.status === \"div\" ? item.value : ( <input value={item.value} data-test=\"input\" className=\"undo-list-input\" onBlur={() => handleBlur(index)} onChange={(e) => handleValueChange(index, e.target.value)} /> )} <span className=\"undo-list-delete\" data-test=\"deleteItem\" onClick={(e) => { e.stopPropagation() deleteItem(index) }}>-</span> </li> ) }) } </ul> </div> ) }}// undoList.test.jsximport React from \"react\";import { shallow } from \"enzyme\";import UndoList from \"../../UndoList\";import { findTestWrapper } from \"../../../common/util/testUtils\";describe(\"UndoList Component Test\", () => { it(\"当数据为空数组时,count为0,列表无内容\", () => { const undoList = []; const wrapper = shallow(<UndoList list={undoList} />); const countElem = findTestWrapper(wrapper, \"count\"); const listItems = findTestWrapper(wrapper, \"listItem\"); expect(countElem.text()).toBe(\"0\"); expect(listItems.length).toEqual(0); }) it(\"当数据不为空数组时,显示count,列表内容不为空\", () => { const undoList = [ { status: \"div\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} />); const countElem = findTestWrapper(wrapper, \"count\"); const listItems = findTestWrapper(wrapper, \"listItem\"); expect(countElem.text()).toBe(\"3\"); expect(listItems.length).toEqual(3); }) it(\"当数据不为空数组时,应该有删除按钮\", () => { const undoList = [ { status: \"div\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} />); const deleteItem = findTestWrapper(wrapper, \"deleteItem\"); expect(deleteItem.length).toEqual(3); }) it(\"当数据不为空数组时,点击某个删除按钮,调用删除方法\", () => { const fn = jest.fn(); const index = 1; const undoList = [ { status: \"div\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} deleteItem={fn} />); const deleteItem = findTestWrapper(wrapper, \"deleteItem\"); deleteItem.at(index).simulate(\"click\", { stopPropagation: () => { } // 阻止事件冒泡 }); expect(fn).toHaveBeenCalledWith(index); }) it(\"当某一项被点击时,触发执行changeStatus函数\", () => { const fn = jest.fn(); const index = 1; const undoList = [ { status: \"div\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} changeStatus={fn} />); const changeStatus = findTestWrapper(wrapper, \"listItem\"); changeStatus.at(index).simulate(\"click\"); expect(fn).toHaveBeenCalledWith(index); }) it(\"当某一项的状态为‘input’时,存在一个输入框\", () => { const undoList = [ { status: \"input\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} />); const inputElem = findTestWrapper(wrapper, \"input\"); expect(inputElem.length).toBe(1); }) it(\"当某一项失去焦点时,执行handleBlur函数\", () => { const fn = jest.fn(); const undoList = [ { status: \"input\", value: \"react\" }, { status: \"div\", value: \"ject\" }, { status: \"div\", value: \"enzyme\" }, ]; const wrapper = shallow(<UndoList list={undoList} handleBlur={fn} />); const inputElem = findTestWrapper(wrapper, \"input\"); inputElem.simulate(\"blur\"); expect(fn).toHaveBeenCalledWith(0); }) it('当某一个输入框变更时,触发 handleValueChange 方法', () => { const listData = [ { status: 'input',value: 'jest' }, ] const value = 'react'; const fn = jest.fn(); const wrapper = shallow(<UndoList handleValueChange={fn} list={listData}/>); const inputElem = findTestWrapper(wrapper, \"input\"); inputElem.simulate('change', { target: {value} }); expect(fn).toHaveBeenLastCalledWith(0, value); });})","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"}]},{"title":"Jest Setup and Teardown","slug":"Jest Setup and Teardown","date":"2019-08-03T03:20:10.000Z","updated":"2019-08-11T00:36:19.873Z","comments":true,"path":"2019/08/03/Jest Setup and Teardown/","link":"","permalink":"https://nyanshen.github.io/2019/08/03/Jest Setup and Teardown/","excerpt":"","text":"1. Repeating Setup For Many TestsIf you have some work you need to do repeatedly for many tests, you can use beforeEach and afterEach.there is a Counter Class for test like: 12345678910111213141516171819export default class Counter { constructor() { this.number = 0 } addOne() { this.number += 1 } minusOne() { this.number -= 1 } addTwo() { this.number += 2 } minusTwo() { this.number -= 2 }} you might repeat to create the instance for counter, so we can use beforeEach to prepare. 1234567891011121314import Counter from \"./counter\"let counter = nullbeforeEach(() => { console.log(\"beforeEach\") counter = new Counter()})afterEach(() => { console.log(\"afterEach\")})test(\"test counter addOne\", () => { console.log(\"test counter addOne\") counter.addOne() expect(counter.number).toBe(1)}) 2. Scoping && Order of execution of describe and test blocksBy default, the before and after blocks apply to every test in a file. You can also group tests together using a describe block. When they are inside a describe block, the before and after blocks only apply to the tests within that describe block. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import Counter from \"./counter\"// describe block Applies to all tests in this filedescribe(\"counter js test\", () => { let counter = null; console.log(\"counter js test\") beforeAll(() => { console.log(\"beforeAll\") }) beforeEach(() => { console.log(\"beforeEach\") counter = new Counter(); }) afterEach(() => { console.log(\"afterEach\") }) afterAll(() => { console.log(\"afterAll\") }) describe(\"counter js test add\", () => { console.log(\"counter js test add\") beforeAll(() => { console.log(\"beforeAll add\") }) beforeEach(() => { console.log(\"beforeEach add\") }) afterEach(() => { console.log(\"afterEach add\") }) afterAll(() => { console.log(\"afterAll add\") }) test(\"test counter addOne\", () => { console.log(\"test counter addOne\") counter.addOne() expect(counter.number).toBe(1) }) }) describe(\"counter js test minus\", () => { // // Applies only to tests in this describe block console.log(\"counter js test minus\") test(\"test counter minusOne\", () => { console.log(\"counter js test minusOne\") counter.minusOne() expect(counter.number).toBe(-1) }) test(\"test counter minusTwo\", () => { console.log(\"counter js test minusTwo\") counter.minusTwo() expect(counter.number).toBe(-2) }) })})// execute order/** * counter js test * counter js test add * counter js test munis * beforeAll * beforeAll add * beforeEach * beforeEach add * test counter addOne * afterEach add * afterEach * afterAll add * beforeEach * counter js test minusOne * afterEach * beforeEach * counter js test minusTwo * afterEach */ 3. General Advice If a test is failing, one of the first things to check should be whether the test is failing when it’s the only test that runs. In Jest it’s simple to run only one test - just temporarily change that test command to a test.only If you have a test that often fails when it’s run as part of a larger suite, but doesn’t fail when you run it alone, it’s a good bet that something from a different test is interfering with this one. You can often fix this by clearing some shared state with beforeEach. If you’re not sure whether some shared state is being modified, you can also try a beforeEach that just logs data.","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"}]},{"title":"jest-异步代码测试方法","slug":"jest-异步代码测试方法","date":"2019-08-02T01:00:10.000Z","updated":"2019-08-11T00:36:19.876Z","comments":true,"path":"2019/08/02/jest-异步代码测试方法/","link":"","permalink":"https://nyanshen.github.io/2019/08/02/jest-异步代码测试方法/","excerpt":"","text":"1. prepare fetch moduleLet’s implement a simple module that fetches success data from an API and returns the {success: true}. 12345678// fetchData.jsimport axios from \"axios\";export const fetchData = (fn) => { axios.get('http://www.dell-lee.com/reactt/api/demo.json').then(res => { fn(res.data) })} In the above implementation we expect the fetchData.js module to return a promise. We chain a call to then to receive the data. 2. async function use callback type1234567// fetchData.test.jsimport {fetchData} from \"./fetchData.js\";test('fetchData return {success: true}', () => { fetcchData((data) => { expect(data).toEqual({success: true}) })}) npm run testactually it always execute successfully,because it won’t wait the async function finished. we can use done to ensure the test is execute finished. 1234567import {fetchData} from \"./fetchData.js\";test('fetchData return {success: true}', (done) => { fetcchData((data) => { expect(data).toEqual({success: true}) done(); })}) 3. return promise derectly1234567891011121314// fetchData.jsimport axios from \"axios\";export const fetchData = () => { return axios.get('http://www.dell-lee.com/reactt/api/demo.json')}// fetchData.test.jsimport {fetchData} from \"./fetchData.js\";test('fetchData return {success: true}', () => { return fetcchData().then((res) => { expect(res.data).toEqual({success: true}) })}) need to add return 4. Error handlingErrors can be handled using the .catch method. Make sure to add expect.assertions to verify that a certain number of assertions are called. Otherwise a fulfilled promise would not fail the test: 12345678// fetchData.test.jstest('test fetchData return 404', () => { // use assertions at least execute the expect method one time expect.assertions(1) return fetchData().catch((e) => { expect(e.toString().indexOf('404') > -1).toBe(true) })}) 5. use resolvesThere is a less verbose way using resolves to unwrap the value of a fulfilled promise together with any other matcher. If the promise is rejected, the assertion will fail. 12345678// fetchData.test.jstest('test fetchData return { success: true}', () => { return expect(fetchData()).resloves.toMatchObject({ data: { success: true } })}) use rejectsThe .rejects helper works like the .resolves helper. If the promise is fulfilled, the test will automatically fail. 1234// fetchData.test.jstest('test fetchData return 404', () => { return expect(fetchData()).rejects.toThrow()}) async/awaitWriting tests using the async/await syntax is easy. Here is how you’d write the same examples from before: 1234567891011121314151617181920// fetchData.test.jstest('test fetchData return { success: true}', async () => { const result = await fetchData(); return expect(result.data).toEqual({success: true})})// async/await can also be used with `.resolves`.it('works with async/await and resolves', async () => { expect.assertions(1); await expect(fetchData()).resolves.toEqual({success: true});});test('test fetchData return 404', async () => { expect.assertions(1); try { await fetchData(); } catch(e) { expect(e.toString()).toEqual(\"Error: Request faild with with status code 404\") }})","categories":[],"tags":[{"name":"jest","slug":"jest","permalink":"https://nyanshen.github.io/tags/jest/"}]},{"title":"es6 新特性","slug":"es6-新特性","date":"2019-08-01T12:34:14.000Z","updated":"2019-08-11T00:36:19.874Z","comments":true,"path":"2019/08/01/es6-新特性/","link":"","permalink":"https://nyanshen.github.io/2019/08/01/es6-新特性/","excerpt":"","text":"1. 类(class)这三个特性涉及了ES5中最令人头疼的的几个部分:原型、构造函数,继承…ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。 12345678910111213141516171819202122232425262728293031class Animal { constructor(name, color) { // 构造函数 this.name = name; this.color = color; } toString() { // toString方法 console.log(`name: ${this.name}, color: ${this.color}`); } getName() { // 取值 return this.name; } setName(value) { // 存值 this.name = value; } getColor() { // 取值 return this.color; } setColor(value) { // 存值 this.color = value; }}let animal = new Animal(\"cat\", \"white\");animal.toString(); // name: cat, color: whiteconsole.log(animal.hasOwnProperty('name')) // trueconsole.log(animal.hasOwnProperty('toString')) // falseconsole.log(animal.__proto__.hasOwnProperty('toString')) // true 2. super 和 extends使用新的super和extends关键字扩展类1234567891011121314151617181920212223class Cat extends Animal { constructor(action = \"catch thing\",name, color) { super(\"cat\", \"black\"); // super用作函数, 必须在this使用前调用 this.action = action; // Cat 类本身属性 } toString() { super.toString();// super用作对象 } getAction() { return this.action; } setAction(value) { this.action = value; }}let cat = new Cat(\"eat fish\");cat.toString(); // name: cat, color: whiteconsole.log(cat instanceOf Cat) // trueconsole.log(cat instanceOf Animal) // true 使用ES5编写同样功能的类123456789101112131415161718192021222324function Animal(name, color) { this.name = name || \"cat\"; this.color = color || \"orange\";}Animal.prototype.toString = function() { console.log(`name: ${this.name}, color: ${this.color}`);}function Cat (action, name, color) { Animal.call(this, name, color); this.action = action || \"catch thing\";}Cat.prototype = Object.create(Animal.prototype);Cat.prototype.constructor = Cat;Cat.prototype.toString = function() { Tree.prototype.toString.call(this);}Cat.prototype.setAction = function(value) { this.action = value;} 3. 模块化(Module)es5不支持原生的模块化,在es6模块作为重要的组成部分被添加进来。模块的共能主要由export和import组成。每一个模块都有自己的单独的作用域,模块之间的调用是通过export来规定模块对外暴露的接口,通过import来引用其他模块提供的接口。同时模块还创造了命名空间,防止函数的命名冲突。 导出(export)ES6允许在一个模块中使用export来导出多个变量和函数。 导出变量12345export let varible = \"test variable exprot\";export default varible;let name = \"cat\";let color = \"yellow\";export {name, color} 导出常量1export const NUMBER_LENGTH = 20 导出函数123export function myModuleFn(somParams) { return somParams} 导入(import)定义好模块的输出,就可以在另一个模块通过import导入 123import varible from \"test.js\"import {myModuleFn} from \"myModuleFn\";import {name, color} from \"./path/to/test.js\"; 4. 箭头函数(Arrow Function)=>不只是function的简写,它还带来了其他好处。箭头函数与包围它的代码共享同一个this,能够帮你解决this的指向问题。有经验的javaScript开发者都熟悉var self = this或var that = this这种引用外围的this的模式。但=>就不需要这种模式了。 不论是箭头函数还是bind,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做别的一些事(如卸载监听器),那么你必须保存这个引用。 箭头函数与普通函数的区别 箭头函数是匿名函数,不能作为构造函数,不能使用new 箭头函数不绑定arguments,取而代之用rest参数…解决 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。 箭头函数没有原型属性 箭头函数不能当做Generator函数,不能使用yield关键字 箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()普通函数的this指向调用它的那个对象 5. 模板字符串ES6支持模板字符串,使得字符串的拼接更加简洁、直观。 不使用模板字符串 1var name = 'your name is ' + first + ' ' + last; 使用模板字符串 1var name = `your name is ${first} ${last}`; 在ES6中通过${}就可以完成字符串拼接,只需要将变量放在大括号中。 7. 解构赋值解构赋值语法是Javascript的一种表达式,可以方便的从数组或对象中快速提取值赋给定义的变量。 获取数组的值从数据中获取值并赋值到的变量中,变量的顺序与数组中对象顺序对应 123456789var foo = [{\"a\": \"one\"}, \"two\", \"three\", \"four\"];var [one, two, three] = foo;console.log(one, two, three)// 如果你要忽略某些值, 你可以按照下面的写法获取你想要的值var [first, , , last] = foo;console.log(first, last)var [a, b] = [1, 2]console.log(a, b) 如果没有从数组中获取到值,可以为变量设置一个默认值。 12var a ,b;[a= 3, b=7]=[1] 获取对象的值1234567const student = { name: \"Nyan\", age: 27, city: \"ShenZhen\"}const {name, age, city} = student;console.log(name, age, city) 8. 延展操作符(Spread operator)延展操作符...可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;还可以在构造对象时,将对象表达式按key-value方式展开。 语法 函数调用 1myFunction(...iterableObj) 数组构造或字符串 1[...iterableObj, '4', ...'hello', 6] 构造对象时,进行克隆或属性拷贝(ES2018)新增特性: 1let objClone = {...obj}; 应用场景 在函数调用时使用延展操作符 123456function (x, y, z) { return x + y + z;}const numbers= [1, 2, 3];console.log(sum.apply(null, numbers))console.log(sum(...numbers)) 构造数组没有延展操作符的时候,只能通过使用push, splice, concat等方法,来将已有的数组元素变成新数组的一部分。有了延展操作符,构造数组更加简单,优雅。 123const students = [\"Jack\", \"Tom\"];const persons = [\"Jony\", ...students, \"Kin\", \"Anny\"]console.log(persons) 和参数列表的展开类似,...在构造数组时,可以任意位置多次使用。 数组拷贝1234var arr = [1, 2, 4];var arr2 = [...arr]arr2.push(5)console.log(arr2) 展开语法,与object.assign()行为一致,执行的是都是浅拷贝(只遍历一层) 连接多个数组123var arr3 = [...arr1, ...arr3]// 等同与var arr4 = arr1.concat(arr2) 在ES2018中延展操作符增加了对对象的支持12345var obj1 = {foo: \"baz\", x: 12};var obj2 = {foo: \"bar\", y: 34};var cloneObj = {...obj1};var mergeObj = {...obj, ...obj2};console.log(obj1, obj2) 在React中使用通常我们在封装一个组件时,会对外公开一些props用于实现功能。大部分情况下在外部使用都应显示传递的props.但是当传递大量的props时,会非常繁琐,这时我们可以使用...延展操作符,用于取出参数对象的所有可遍历属性,来进行传递 一般情况下,我们这样传递 1<CustomComponent name=\"Nyan\" age={27} /> 使用…,等同于上面的写法 12345const params = { name: \"name\", age: 27}<CustomComponent {...params} /> 配合解构赋值避免传入一些不需要的参数 1234567const params = { name: \"name\", age: 27, type: \"type Name\" } var {type, ...other} = params;<CustomComponent {...other} /> 9. 对象属性的简写在ES6中允许我们在设置一个对象的属性的时候不指定属性名 不使用es612const name= \"name\", age = 27const student = {name: name,age: age, getName: function(){}} 对象必须包含属性和值,显得非常冗余 使用Es612const name= \"name\", age = 27const student = {name,age, function getName(){}} 10. PromisePromise是异步编程的一种解决方案,比传统的解决方案callback更加优雅。它最早由社区提出与实现,ES6将其写进了语言标准,统一了写法,原生提供了Promise对象。 不使用ES6 123456setTimeout(function(){ console.log(\"promise test\") setTimeout(function(){ console.log(\"promise test 1\") }, 1000)}, 1000) 使用ES6 12345678910var waitSecond = new Promise(function(resolve, reject){ setTimeout(resolve, 1000)});waitSeccond.then(function(){ console.log(\"promise test\") return waitSecond}).then(function(){ console.log(\"promise test 1\")})","categories":[],"tags":[{"name":"javascript","slug":"javascript","permalink":"https://nyanshen.github.io/tags/javascript/"}]},{"title":"Javasript Variables","slug":"js-variables","date":"2019-08-01T02:34:14.000Z","updated":"2019-08-11T00:36:19.877Z","comments":true,"path":"2019/08/01/js-variables/","link":"","permalink":"https://nyanshen.github.io/2019/08/01/js-variables/","excerpt":"","text":"JavaScript 进阶问题列表1. 下面的代码输出的是什么12345678var name = \"test name\";function testVariables() { console.log(name); console.log(age); var name = \"NyanShen\" let age = 27}testVariables(); 在函数中,我们首先使用var关键字声明了name变量。这意味着变量在创建阶段会被提升(javascript会在创建变量创建阶段为其分配内存空间),默认值为undefined的值. 使用let关键字(和const)声明的变量也会存在变量提升,但与var不同,初始化没有被提升。在我们声明(初始化)它们之前,它们是不可被访问的。这被称为“暂时死区”。当我们在声明变量之前尝试访问变量是,javascript会抛出一个ReferenceError。 关于let的是否存在变量提升,我们可以用下面例子来验证: 12345let name = \"Nyan Test\";{ console.log(name); let name = \"Nyan\";} let变量如果不存在变量提升,那么console.log(name)就会打印出Nyan Test,结果去抛出了ReferenceError,那么这很好的说明了let也存在变量提升,但是它存在一个“暂时死区”,在变量未初始化或未赋值时不允许访问。 要理解提升的定义,还需要搞懂js变量的执行过程 js变量的执行过程包括 1创建 =》初始化 =》 赋值 var 声明的变量执行过程 1找到当前作用域中所有var声明的变量,创建变量 =》初始化为 undefined =》**执行代码** =》 赋值 function 声明的变量执行过程 1找到当前作用域中所有function声明的变量,创建变量 =》初始化 =》赋值 =》**执行代码** let 声明的变量执行过程 1找到当前作用域中所有let声明的变量,创建变量 =》**执行代码** =》初始化为let声明的值,没有就为undefined =》 赋值(修改值) const 声明的变量执行过程 1找到当前作用域中所有let声明的变量,创建变量 =》**执行代码** =》初始化为let声明的值,没有就为undefined 有没有被提升,主要看代码执行的时机 let 的「创建」过程被提升了,但是初始化没有提升。 var 的「创建」和「初始化」都被提升了。 function 的「创建」「初始化」和「赋值」都被提升了。","categories":[],"tags":[{"name":"javascript","slug":"javascript","permalink":"https://nyanshen.github.io/tags/javascript/"}]}]}