Git Product home page Git Product logo

blog's Introduction

blog's People

Contributors

xiaochengyin avatar

Watchers

 avatar

blog's Issues

Promise 原理

const isFunction = fn => typeof fn === 'function'

class Promise {
    constructor(handle) {
        // 1. 判断参数是不是函数
        if (!isFunction(handle)) {
            throw new Error()
        }
        // 2. 初始化私有属性
        this._state = 'pending'
        this._value = undefined
        this._resolvedQueue = []
        this._rejectedQueue = []
        // 3. 执行回调
        try {
            handle(this._resolve.bind(this), this._reject.bind(this))
        } catch(error) {
            this._reject(error)
        }
    }

    _reject(error) {
        // 1. 判断实例状态
        if (this._state !== 'pending') return 
        // 2. 修改私有属性,清空队列任务
        const run = () => {
            this._state = 'rejected'
            this._value = error
            let task
            while(task = this._rejectedQueue.shift()) {
                task(error)
            }
        }
        // 异步执行
        setTimeout(run, 0)
    }

    _resolve(value) {
        // 1. 判断实例状态
        if (this._state !== 'pending') return 
        // 2. 修改私有属性,清空队列任务
        const run = () => {
            const runResolved = value => {
                this._state = 'resolved'
                this._value = value
                let task
                while(task = this._resolvedQueue.shift()) {
                    task(value)
                }
            }
            const runRejected = error => {
                this._state = 'rejected'
                this._value = error
                let task
                while(task = this._rejectedQueue.shift()) {
                    task(error)
                }
            }
            // 2.0 判断参数是不是一个Promise实例
            if (value instanceof Promise) {
                value.then(runResolved, runRejected)
            } else {
                runResolved(value)
            }
        }
        // 3. 异步执行
        setTimeout(run, 0)
    }

    then(onResolved, onRejected) {
        // 1. 返回一个新的Promise实例
        const {_state, _value} = this
        return new Promise((resolve, reject) => {
            // 2。 执行回调 
            const resolved = value => {
                try {
                    // 2.1 判断参数是不是函数
                    if (!isFunction(onResolved)) {
                        resolve(value)
                    } else {
                        // 2.2 执行回调判断返回值是不是Promise实例
                        const result = onResolved(value)
                        if (result instanceof Promise) {
                            result.then(resolve, reject)
                        } else {
                            resolve(result)
                        }
                    }
                } catch(error) {
                    reject(error)
                }
            }
            const rejected = error => {
                try {
                    if (!isFunction(onRejected)) {
                        reject(error)
                    } else {
                        const result = onRejected(error)
                        if (result instanceof Promise) {
                            result.then(resolve, reject)
                        } else {
                            resolve(result)
                        }
                    }
                } catch(error) {
                    reject(error)
                }
            }
            // 3. 判断实例状态
            switch(_state) {
                case 'pending':
                    this._resolvedQueue.push(resolved)
                    this._rejectedQueue.push(rejected)
                    break
                case 'resolved':
                    resolved(_value)
                    break
                case 'rejected': 
                    rejected(_value)
            }
        })
    }

    catch(onRejected) {
        return this.then(undefined, onRejected)
    }

    finally(callback) {
        return this.then(value => {
            callback()
            return Promise.resolve(value)
        }, error => {
            callback()
            return Promise.reject(error)
        })
    }

    static resolve(value) {
        if (value instanceof Promise) return value
        return new Promise(resolve => resolve(value))
    }

    static reject(error) {
        return new Promise((resolve, reject) => reject(error))
    }

    static all(list) {
        return new Promise((resolve, reject) => {
            const values = []
            let count = 0
            list.forEach((p, i) => {
                this.resolve(p).then(value => {
                    values[i] = value
                    count++
                    if (count === list.length) {
                        resolve(values)
                    }
                }, reject)
            })
        })
    }

    static race(list) {
        return new Promise((resolve, reject) => {
            list.forEach(p => {
                this.resolve(p).then(resolve, reject)
            })
        })
    }
}

你不知道的JavaScript读书笔记

你不知道的JavaScript读书笔记

作用域

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。

  • 词法作用域
  • 动态作用域

编译

  • 分词/词法分析:将代码字符串转换为词法单元 eg. var a = 2; => vara=2;
  • 解析/语法分析:将词法单元流转换为抽象语法树(AST)
  • 代码生成:将AST转换为可执行代码

the-super-tiny-compiler

作用域链

引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。

找不到变量

  • RHS查询找不到变量会直接抛出ReferenceError异常。
  • LHS查询找不到变量,非严格模式下会在全局作用域下创建一个具有该名称的变量并返回给引擎,严格模式下同RHS查询一样抛出ReferenceError异常。

LHS和RHS的含义是“赋值操作的左侧或右侧”

词法作用域

词法作用域就是定义在词法阶段的作用域,也就是由写代码时将变量和块作用域写在哪里来决定的。
作用域查找会在找到第一个匹配的标识符时停止,如果在多层的嵌套作用域中定义同名标识符,则无法访问外层的标识符(遮蔽效应:内部标识符遮蔽外部标识符)

动态作用域

动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,动态作用域的作用域链是基于调用栈的,而不是代码中的作用域嵌套。
JavaScript 并不具有动态作用域。

function foo() {
    console.log(a)
}
function bar() {
    var a = '动态作用域'
    foo()
}
var a = '词法作用域'
bar() // 调用栈为:bar -> foo

闭包

当函数可以记住并访问所在的词法作用域时,即使函数式在当前词法作用域之外执行,这时就产生了闭包。
无论何时何地,如果将函数作为第一级的值类型到处传递,就会应用到闭包。
在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers、或其他任何异步任务中,只要使用了回调函数,实际上就是在使用闭包。

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2
function setupBot(name, selector) {
    $(selector).click(function() {
        console.log('Activating: ' + name);
    })
}
setupBot('Closure Bot 1', '#bot_1');
setupBot('Closure Bot 2', '#bot_2');

THIS

this 机制很像动态作用域,即函数内 this 的值由运行时的调用栈决定,而不是代码中的作用域嵌套

绑定方式

  • 默认绑定:当无法应用其他绑定规则时,非严格模式下 this 指向 window 对象,严格模式下,this 指向 undefined
var a = 2
function foo() {
    console.log(this.a)
}
foo() // 2

function bar() {
    'use strice'
    console.log(this)
}
bar() // undefined
  • 隐式绑定:当函数是由上下文对象调用时,this 指向上下文对象
var obj = {
    a: 2,
    foo: function() {
        console.log(this.a)
    }
}
var bar = obj.foo
var a = 'global'
bar() // 'global'
obj.foo() // 2
  • 显式绑定:callapplybind方法绑定,但不能绑定箭头函数。当显示绑定传入参数为 nullundefined 时,实际应用默认绑定。
  • new 绑定:当函数被 new 操作符调用时
    • 创建一个全新的对象
    • 将这个新对象绑定到函数调用的 this
    • 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象

优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数不使用 this 的四种规则,而是根据外层作用域来决定 this,也就是使用词法作用域规则

对象

属性描述符

var myObject = {
    a: 2
}
Object.getOwnPropertyDescriptor(myObject, 'a')
/*  
 *  {
 *      value: 2, // 值
 *      writable: true, // 可写
 *      enumerable: true, // 可枚举
 *      configurable: true // 可配置
 *  }
 */ 
  • writablewritable 决定是否可以修改属性的值

writable 属性是 false 时,非严格模式下修改属性值会静默失败,严格模式下会抛出 TypeError 错误

  • configurableconfigurable 决定是否可以使用 Object.defineProperty 方法修改属性描述符

不管是不是处于严格模式,尝试修改一个不可配置的属性描述符都会抛出 TypeError 错误
所以,把 configurable 修改成 false 是单向操作,无法撤销
configurable: false 会禁止删除该属性。(静默失败)

  • enumerable:不可枚举属性能够被访问,但不会出现在对象属性的遍历中
var obj = {}
Object.defineProperty(obj, 'a', {enumerable: true, value: 2})
Object.defineProperty(obj, 'b', {enumerable: false, value: 3})
obj.propertyIsEnumerable('a') // true
obj.propertyIsEnumerable('b') // false
Object.keys(obj) // ['a']
Object.getOwnPropertyNames(obj) // ['a', 'b']

不变性

  • 对象常量:writable: false, configurable: false 可以创建一个不可修改、重定义、删除的常量属性
  • 禁止扩展:Object.preventExtensions 能够禁止添加新属性,非严格模式静默失败,严格模式抛出 TypeError
  • 密封:Object.seal 在现有对象上调用 Object.preventExtensions 并把所有现有属性标记为 configurable: false
  • 冻结:Object.freeze 在现有对象上调用 Object.seal 并把所有“数据访问”属性标记为 writable: false

GetterSetter

// 字面量
var a = {
    get a() {}
    set a(val) ()
}
// 定义属性
Object.defineProperty(obj, 'prop', {
    get() {},
    set(val) {}
})

存在性

  • 'prop' in objin 操作符会检查属性是否在对象及其原型链中
  • obj.hasOwnProperty('prop'):只会检查是否在对象中

类型

七种内置类型

  • 空值(null)
  • 未定义(undefined)
  • 布尔值(boolean)
  • 数字(number)
  • 字符串(string)
  • 对象(object)
  • 符号(symbol)

typeof

// 两种特殊情况
typeof null === 'object' // true
typeof function() {} === 'function' // true

数字

42.toFixed( 3 ); // SyntaxError
// 42. 是一个合法的数字,等于 42.0,
42..toFixed( 3 ); // 合法
(42).toFixed( 3 ); // 合法
整数检测
Number.isInteger(42) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
NaN

NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

var a = 2 / 'foo' // NaN
typeof a === 'number' // true

// NaN 和自身不相等
a == NaN // false
a === NaN // false

isNaN(a) // true
isNaN('foo') // true isNaN 只能检查参数是否不是NaN,也不是数字
Number.isNaN(a) // true 可靠
a !== a // true NaN 是 js 中唯一不等于自身的值
特殊等式

能使用 ===== 时就尽量不要使用 Object.is(),因为前者效率更高、更为通用。Object.is() 主要用来处理那些特殊的相等比较。

NaN === NaN // false
Object.is(NaN, NaN) // true

+0 === -0 // true
Object.is(+0, -0) // false

强制类型转换 🙃

ToString

.toString()String()12 + ''

  • null => 'null'
  • undefined => 'undefined'
  • 普通对象 => '[object Object]' 引用类型调用 toString() 方法
  • [1,2,3] => 1,2,3

ToNumber

Number() +new Date()

  • true => 1
  • false => 0
  • undefined => NaN
  • null => 0
  • '' => 0
  • [] => 0
ToPrimitive

引用类型被强制类型转换时先通过 ToPrimitive 操作将值转换为基本类型,然后再将该基本类型值转换为对应的类型。
ToPrimitive 操作会首先检查该值是否有 valueOf() 方法,如果有并返回基本类型值就使用该值进行强制类型转换,如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。如果 valueOf()toString() 均不返回基本类型值,会产生 TypeError 错误。

let date = new Date()
+date // 1568801394219
date.valueOf() // 1568801394219

ToBoolean

Boolean()!!number

假值
  • undefined
  • null
  • false
  • +0-0NaN
  • ''
真值

不在假值列表中的值都是真值

Symbol

String(Symbol()) => 'Symbol'

Symbol 类型不能转换成数字,不能隐式转换成字符串。但可以被转换为布尔值

相等比较

宽松相等 == 在比较过程中包含隐式强制类型转换
严格相等 === 没有强制类型转换
Object.is() 比较与严格相等基本相同

  • 字符串和数字进行宽松相等比较时,先把字符串转为数字
  • 布尔值和其他类型进行宽松相等比较,先把布尔值转为数字
  • null == undefined,除此之外 nullundefined 不和其他任何值宽松相等
  • 对象和非对象进行宽松相等比较,先把对象转为基本类型(ToPrimitive操作)
特殊情况
  1. 重写对象原型链上的 toString()valueOf() 方法
Number.prototype.valueOf = function() {
    return 3;
};
new Number( 2 ) == 3; // true

var i = 2;
Number.prototype.valueOf = function() {
    return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
    console.log( "Yep, this happened." );
}
  1. 假值的相等比较
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕! false => 0
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕! [] => '', false => 0, '' => 0
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!  
0 == {}; // false
  1. 极端情况
[] == ![] // true

2 == [2] // true
"" == [null] // true [null] => ''

抽象关系比较(不等式比较)

如果比较双方都是字符串,直接进行比较
如果是其他情况,双方首先调用ToPrimitive,如果结果出现非字符串,就调用ToNumber将双方转为数字进行比较

// 特殊情况
var a = { b: 42 };
var b = { b: 43 };
a < b; // false [object Object] < [object Object]
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
// a <= b --> !(a > b)

其他

[] + {} // '[object Object]'
{} + [] // 0  {} 被当作空代码块跳过

Javascript 继承

Javascript 继承

原型链继承

function Parent() {}
function Child() {}
Child.prototype = new Parent()
Child.prototype.constructor = Child

缺点:1、属性定义在原型上会被共享 2、不能单独向父类的构造函数传递参数

借用构造函数继承

function Parent() {}
function Child() {
    Parent.call(this)
}

只是复制了父类的实例属性,无法访问父类的原型链

组合继承

function Parent() {}
function Child() {
    Parent.call(this)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

结合了原型链继承和借用构造函数继承,解决了上述缺陷但至少要调用两次父类的构造函数,子类的实例和原型上都继承了父类的实例属性(原型属性被屏蔽)

原型式继承

function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

与借用构造函数相反,原型式继承实现了原型属性继承但没有实现实例属性的继承

寄生式继承

function Parent() {}
function Child() {
    this.say = function() {}
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

寄生式继承是在原型式继承的基础上为子类实例添加一些方法,但他们共同的问题是无法继承父类的实例属性

寄生组合式继承

function Parent() {}
function Child() {
    Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

寄生组合式继承是在组合式继承中用原型式继承代替原型链继承

Class继承

具有两条继承链,在寄生组合式继承的基础上,子类能够继承父类的静态方法,即 Child.__proto__ === Parent

Vue 中的继承

function Vue () {
    this._init()
}
function VueComponent () {
    this._init()
}
VueComponent.prototype = Object.create(Vue.prototype)
VueComponent.prototype.constructor = VueComponent
VueComponent.extend = Vue.extend
// ...

在寄生组合式继承的基础上继承了父类的静态方法

每日一题

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。
如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
理想的 key 值是每项都有的唯一 id。

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
当基于下标的组件进行重新排序时,组件 state 可能会遇到一些问题。
由于组件实例是基于它们的 key 来决定是否更新以及复用,如果 key 是一个下标,那么修改顺序时会修改当前的 key,导致非受控组件的 state(比如输入框)可能相互篡改导致无法预期的变动。

避免“就地复用”,便于跟踪节点位置变化,防止“就地复用”时导致丢失表单输入值等问题。

Promise 和 async/await

Promise 和 async/await

事件循环和消息队列

消息队列是一个先进先出的队列,队列中存有需要执行的任务。事件循环是指每当主线程任务执行完毕后会从消息队列中取出下一个要执行的任务。

微任务和宏任务

所有异步任务可以分为两类,即宏任务和微任务。微任务的优先级大于宏任务,当宏任务执行完毕后会立即执行微任务队列中的所有任务,直到当前微任务队列为空时才会执行下一个宏任务。

宏任务:I/OsetTimeoutsetIntervalsetImmediaterequestAnimationFrame
微任务:process.nextTickMutationObserverPromise#then/catch/finally

Promiseasync 函数

async/await 只是 Promise 的语法糖,原理与 Promise 是一样的,所以 async 也属于微任务。一个 async 函数就可以理解为一个 Promiseawait 之前的代码相当于 Promise 的回调函数,都是立即执行的同步代码;await 语句相当于 Promise 中的子 Promise,父 Promise 想要 resolve 必须等子 Promiseresolve 才行;await 后面的代码相当于父 Promise.then 中的回调函数,即子 Promise resolve 之后才执行父 Promise.then 中的代码。

举个例子:

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start')
setTimeout(function(){
    console.log('setTimeout') 
},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
console.log('script end')

输出结果为:

'script start'
'async1 start'
'async2'
'promise1'
'script end'
'promise2'
'async1 end'
'setTimeout'

把 async 函数改写成 Promise 能够的到相同的结果

function async2() {
    return new Promise((resolve, reject) => {
        console.log('async2')
        resolve()
    })
}
// chrome 71
function async1(){
    console.log('async1 start');
    const p = async2();
    return new Promise((resolve) => {
        Promise.resolve().then(() => {
            p.then(resolve)
        })
    }).then(() => {
        console.log('async1 end')
    });
}
// chrome 73
// 优化后 async 的执行更快,
// `async1 end` 能够在 `promise2` 之前输出
function async1(){
    console.log('async1 start');
    const p = async2();
    return Promise.resolve(p).then(() => {
        console.log('async1 end')
    });
}

Promise.resolve(p)new Promsie((resolve) => resolve(p)) 的差异

当 p 是一个 promise 时,第一种写法会直接返回 p,而第二种写法会创建额外的微任务。因为这时的 p 是一个 thenable 对象,在 Promise 中 resolve 一个 thenable 对象需要通过 PromiseResolveThenableJob 进行转换,而这是一个微任务。

高性能 JavaScript 读书笔记

高性能 JavaScript 总结

1. 加载和执行

浏览器解析遇到<script>标签,页面必须停下来等待代码下载(如果是外链文件)并执行,然后继续处理其他部分。

  • </body>标签闭合之前,将所有的<script>标签放在页面底部。这样能确保在脚本执行前页面已经完成了渲染。
  • 合并脚本。页面中的<script>标签越少,加载也就越快,响应也更迅速。无论外链文件还是内嵌脚本都是如此。
  • 有多种无阻塞下载 JavaScript 的方法:
    • 使用<script>标签的 defer/async 属性;
    • 使用动态创建的<script>元素来下载并执行代码;
    • 使用 XHR 对象下载 JavaScript 代码并注入页面中。

defer 与 async :相同点是采用并行下载,在下载过程中不会产生阻塞。区别在于执行时机,async 是加载完成后自动执行,而 defer 需要等待页面加载完成后执行(触发 load 事件,并先于 load 监听函数)。defer 属性仅当 src 属性声明时才生效。

HTTP 请求会带来额外的性能开销,因此下载单个 100KB 的文件将比下载 4 个 25KB 的文件更快。也就是说,减少页面中外链文件的数量将会改善性能。

  • 使用雪碧图
  • 使用 CDN ,虽然会增加外链数量但能够尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。

2. 数据存取

作用域链

Function 对象(构造器)的内部属性[[scope]]包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问。当函数创建时,它的作用于链中插入了一个对象变量。执行此函数时会创建一个称为执行环境/执行上下文(execution context)的内部对象。一个执行环境定义了一个函数执行时的环境。函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境。当函数执行完毕,执行环境就会被销毁。

每个执行环境都有自己的作用域链,用于解析标识符。当执行环境被创建时,它的作用域链初始化为当前运行函数的[[scope]]属性中的对象。这些值按照它们出现在函数中的顺序,被复制到执行环境的作用域链中。这个过程一旦完成,一个被称为“活动对象(activation object)”的新对象就为执行环境创建好了。活动对象作为函数运行时的变量对象,包含了所有局部变量,命名参数,参数集合以及 this。然后此对象被推入作用域链的最前端。当执行环境被销毁,活动对象也随之被销毁。由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。

闭包

闭包允许函数访问局部作用域之外的数据。由于闭包的[[scope]]属性包含了与执行环境作用域链相同的对象的引用,因此会产生副作用。通常来说,函数的活动对象会随着执行环境一同销毁。但引入闭包时,由于引用仍然存在于闭包的[[scope]]属性中,因此激活对象无法被销毁。因此闭包需要更多的内存开销。

原型

对象通过一个内部属性_proto_绑定到它的原型

3. DOM 编程

  • 最小化 DOM 的访问次数,尽可能在 JavaScript 端处理。
  • 如果需要多次访问某个 DOM 节点,请使用局部变量存储它的引用。
  • 尽可能使用访问速度更快的 API,如 querySelector() 和 firstElementChild。
  • 要留意重绘和重排,批量修改样式时,“离线”操作 DOM 树,使用缓存,并减少访问布局信息的次数。
  • 使用事件委托来减少事件处理器的数量。

4. 算法和流程控制

  • 减少迭代的工作量
  • 减少迭代次数
  • 大量条件判断使用 switch 代替 if-else
  • 递归可以降低时间复杂度,但会增加空间复杂度
  • 使用尾调用优化(即在函数的最后一步调用另一个函数)来节省内存
  • 使用 Memoization 技术为重复计算提供缓存
// memoize() 函数接收两个参数:一个是需要增加缓存功能的函数,
// 一个是可选的缓存对象。
function memoize(fundamental, cache){
    cache = cache || {};
    let shell = (arg) => {
        if(!cache.hasOwnProperty(arg)){
            cache[arg] = fundamental(arg);
        }
        return cache[arg];
    };
    return shell;
}

5. 字符串和正则表达式

  • 注意正则表达式的贪婪模式和惰性模式,想要正向检索时应使用*?而不是*。因为在贪婪模式下正则引擎会从后向前检索*后面的部分
  • 正则表达式匹配失败的位置要比匹配成功的位置更多,让正则表达式匹配更快失败可以提高性能
  • 正则表达式以简单、必须的字元开始

正则表达式的起始标记应尽可能快速地测试并排出明显不匹配的位置。如一个锚^ $、特定字符串x、字符类[a-z] \s、单词边界\b。尽量避免以分组或选择字元开头。

  • 使用量词模式,使他们后面的字元互斥
  • 减少分支数量,缩小分支范围
    替换前 替换后
    `cat bat`
    `red read`
    `red raw`
  • 使用非捕获组
  • 把正则表达式赋值并重用它们
  • 具体化要匹配的字符,尽量避免使用.可以避免回溯失控

6. 快速响应的用户界面

  • 任何 JS 任务都不应当执行超过 100 毫秒。过长的运行时间会导致 UI 更新出现明显的延迟,从而对用户体验产生负面影响。
  • JS 运行期间,浏览器响应用户交互的行为存在差异。无论何时,JS 长时间运行将导致用户体验变得混乱和脱节。
  • 定时器可用来安排代码延迟执行,它使得你可以把长时间运行脚本分解成一系列的小任务。
  • Web Workers 允许你在 UI 线程外执行 JS 代码,从而避免锁定 UI。

Web Worker

运行环境
  • navigator -> {appName, appVersion, userAgent, platform}
  • location -> window.location 所有属性都是只读的
  • self -> 指向全局 worker 对象
  • importScripts() -> 用来加载 Worker 用到的外部 JS 文件
  • 所有的 ECMAScript 对象
  • XMLHttpRequest 构造器
  • setTimeout()setInterval()
  • close() -> 立刻停止 Worker 运行
通信
var worker = new Worker('code.js);
worker.onmessage = function(event){
    alert(event.data);
};
worker.postMessage('Nicholas');

// code.js 内部代码
importScripts('file1.js', 'file2.js'); // 同步加载
self.onmessage = function(event){
    self.postMessage(`Hello, ${event.data}!`);
};
用途
  • 编码/解析大字符串
  • 复杂数学运算
  • 大数组排序

7. Ajax

  • 减少请求数,可通过合并 JS 和 CSS 文件,或使用 MXHR。
  • 缩短页面的加载时间,页面主要内容加载完成后,用 Ajax 获取次要文件。
  • 确保你的代码错误不会输出给用户,并在服务端处理错误。
  • 知道何时使用成熟的 Ajax 类库,以及何时编写自己的底层 Ajax 代码。

8. 编程实践

  • 避免使用 evel()Function() 构造器,给 setTimeout()setInterval() 传递回调函数而不是字符串作为参数。
  • 使用字面量创建对象和数组。
  • 尽量使用原生方法。

9. 构建并部署高性能的 JS 应用

  • 合并 JS 文件以减少 HTTP 请求数
  • 压缩 JS 文件
  • 在服务器端压缩 JS 文件(Gzip编码)
  • 通过正确设置 HTTP 响应头来缓存 JS 文件,通过向文件名增加 hash 值来触发缓存更新
  • 使用 CDN 提供 JS、CSS 文件;CDN 不仅可以提升性能,它也为你管理文件的压缩与缓存

10. 工具

  • 使用网络分析工具找出加载脚本和页面中其他资源的瓶颈,这会帮助你决定哪些脚本需要延迟加载,或者需要进一步分析。
  • 尽管传统的经验告诉我们要尽量减少 HTTP 请求数,但把脚本尽可能延迟加载可以加快页面渲染速度,给用户带来更好的体验。
  • 使用性能分析工具找出脚本运行过程中速度慢的地方,检查每个函数所消耗的时间,以及函数被调用的次数,通过调用栈自身提供的一些线索来找出需要集中精力优化的地方。
  • 尽管消耗的时间和调用次数通常是数据中最有价值的部分,但仔细观察函数的调用过程,你也许会发现其他优化目标。

> 纸上谈兵?我这没有强缓存头,还是from disk cache

          > 纸上谈兵?我这没有强缓存头,还是from disk cache

image

谷歌开发者博客写到关于这个的原因

Leaving out the Cache-Control response header does not disable HTTP caching! Instead, browsers effectively guess what type of caching behavior makes the most sense for a given type of content. Chances are you want more control than that offers, so take the time to configure your response headers.

这被称为启发式缓存, 当没有显示设置cache-control或是expire时, 大部分浏览器会使用启发式缓存, 把资源缓存下来; 如果真的不想用缓存, 还是主动设置一下cache-control, 具体可以看这篇文章: https://www.mnot.net/blog/2017/03/16/browser-caching#heuristic-freshness

@bowencool 不知道对你是否有帮助

题外话, 关于这个我在国内的论坛找了很久, 很难找到原因, 找了一天才从谷歌开发者博客找到相关信息, 后续引出了启发式缓存这个概念; 国内的很多文档介绍的点还是以面试为主(个人觉得不太好), 但还是要对未来抱有期待~

Originally posted by @ruofee in Advanced-Frontend/Daily-Interview-Question#53 (comment)

WEB安全

web安全

XSS攻击

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可以获取用户的敏感信息如Cookie、SessionID等,进而危害数据安全。

XSS攻击的分类

存储型XSS

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM型XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM型XSS攻击中取出和执行恶意代码都由前端完成,反射型XSS攻击中取出恶意代码由服务端完成。

XSS攻击的预防

预防存储型和反射型XSS攻击:

前端渲染
  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
  2. 然后浏览器执行 HTML 中的 JavaScript。
  3. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。

在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。

转义HTML

转义应在输出是进行,而不是用户提交输入时

预防DOM行XSS攻击

避免将不可信数据拼接到以下位置:

  • 元素内联的 onclick
  • 元素内联的 onerror
  • 元素内联的 onload
  • 元素内联的 onmouseover
  • <a> 标签的 href
  • location.href =
  • eval() 的参数
  • setTimeoutsetIntervalsetImmediate 的参数

谨慎使用.innerHTMLv-html

参考链接

如何防止XSS攻击?

CSRF攻击

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF的攻击流程

  1. 受害者登录a.com,并保留了登录凭证(Cookie)。
  2. 攻击者引诱受害者访问了b.com。
  3. b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。
  4. a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  5. a.com以受害者的名义执行了act=xx。
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

CSRF的特点

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
  • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
  • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

防护策略

同源检测

禁止来自第三方域名的请求

SameSite Cookie

在Cookie中添加SameSite属性,该属性可以阻止跨站请求时发送Cookie。

  • Strict:跨站请求不会携带Cookie
  • Lax:允许部分跨站请求携带Cookie
  • None:允许所有请求携带Cookie

跨站:网站顶级域名和二级域名相同视为同站,否则为跨站
跨域:协议、域名、端口完全相同视为同源,否则为跨域

请求类型 实例 Lax
链接 发送Cookie
预加载 发送Cookie
get表单 发送Cookie
post表单 不发送
iframe <iframe src="..."></iframe> 不发送
Ajax $.get() 不发送
img <img src="..."> 不发送
JSONP ... 不发送

CSRF Token

CSRF攻击无法直接获取用户信息,仅仅是冒用Cookie中的信息。前端在发送请求时携带一个CSRF Token,服务端对Token进行校验。这个Token不能存放在Cookie中。

参考链接

如何防止CSRF攻击?
Cookie 的 SameSite 属性

HTTP知识点总结

HTTP知识点总结

HTTP

HTTP报文结构

  • 起始行:GET /home HTTP/1.1(请求) HTTP/1.1 200 OK(响应)
  • 头部
  • 空行:用来区分头部和实体,第一个空行后的内容全部认为是实体
  • 实体

注意事项

  • 字段名不区分大小写
  • 字段名不允许出现空格,不可以出现下滑线_
  • 字段名后面必须紧接着:

Content-Type / Accept

  • text: text/html, text/plain, text/css
  • image: image/gif, image/jpeg, image/png
  • audio: audio/video: audio/mpeg, video/mp4
  • multipart: multipart/form-data
  • application: application/json, application/javascript, application/pdf, application/x-www-form-urlencoded

Content-Encoding / Accept-Encoding

  • gzip
  • deflate
  • br

Content-Language / Accept-Language

// 发送端
Content-Language: zh-CN, zh, en
// 接收端
Accept-Language: zh-CN, zh, en

代理

  • Via: 代理服务器转发记录,多个代理按转发顺序排列,用,隔开
  • X-Forwarded-For: 记录请求方的IP,客户端或代理服务器Ip
  • X-Real-IP: 获取用户真实IP

URI结构

scheme://user:password@host:port/path/to/somewhere?key=value#fragment

状态码

1xx

表示目前是协议处理的中间状态,还需要后续操作
比如post请求一般是先发送head部分,服务器返回100(continue)再发送body

  • 100
  • 101 Switching Protocols: HTTP升级为WebSocket时服务器返回101

2xx

  • 200
  • 204 No Content

3xx

  • 301 永久重定向:当网站从HTTP升级为HTTPS时之前的URL被废弃时应返回301,浏览器会做缓存优化,下次自动访问新地址
  • 302 临时重定向
  • 304 Not Modified 命中协商缓存

4xx

  • 400 Bad Request 请求出错,原因位置
  • 403 Forbidden 服务器禁止访问
  • 404 Not Found 找不到资源
  • 405 Method Not Allowed

5xx

  • 500 Internal Server Error:服务器出错,原因位置
  • 502 Bad Gateway
  • 503 Service Unavailable:服务器当前很忙,暂时无法响应服务

HTTP/2

  • 头部压缩: HTTP/1可以根据Content-Encoding对实体部分压缩,HTTP/2实现了对头部的压缩
  • 多路复用:
    • 同域名下所有通信都在单个连接中完成。
    • 单个连接可以承载任意数量的双向数据流。
    • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。
  • 二进制分桢: HTTP/2将报文全部转为二进制,多个二进制帧之间不存在前后关系,因此不会有阻塞问题
  • 服务器推送: TCP连接建立后,服务器可以主动将客户端之后需要的数据直接推送,不需要先等待客户端的请求

HTTPS

证书验证阶段

  • 客户端发起HTTPS请求
  • 服务器返回HTTPS证书(包含公钥)
  • 客户端验证证书是否合法,如果不合法则提示警告

数据传输阶段

  • 验证证书合法后,在本地生成一个随机数
  • 通过公钥将随机数加密后发送至服务器
  • 服务器通过私钥对随机数解密
  • 服务器通过随机数构造对称加密算法,对返回数据加密
  • 客户端通过随机数对数据解密

缓存

强缓存

命中强缓存直接读取本地资源,不会向服务器发请求

  • Expires: 值是一个日期,表示资源过期时间,本地时间可能与服务器时间不一致,优先级最低
  • Cache-Control
    • max-age: 单位是秒,表示缓存有效时间。max-age=10000
    • no-cache: 不使用强缓存
    • no-store: 禁止缓存(包括强缓存和协商缓存)
    • private: 只允许客户端缓存,不允许代理服务器或CDN缓存
    • public: 允许客户端、代理服务器、CDN缓存
    • must-revalidate: 在缓存过期前可以使用,过期后必须向服务器验证
  • Pragma: 只有一个值(no-cache,与Cache-Control的no-cache一致),优先级最高

协商缓存

命中协商缓存返回304

  • ETag/If-None-Match: 值是一串hash,服务器资源发生变化时hash值也会改变
  • Last-Modified/If-Modified-Since: 值代表文件最后的修改时间,前者存在于响应头中,后者存在于请求头中

多语言文件导入导出脚本

// npm run i18n export
// npm run i18n import
// "i18n": "npx babel-node --presets=@babel/preset-env  assets/locale/script.js"

import fs from 'fs'
import XLSX from 'xlsx'
import flatten from 'flat'
import consola from 'consola'
import languages from './index'

// const languages = {
//   'zh-CN': {},
//   'en-US': {},
//   'ko-KR': {}
// }

const json2excel = () => {
  const flattenLang = Object.keys(languages).reduce((flattenLang, key) => {
    flattenLang[key] = flatten(languages[key])
    return flattenLang
  }, {})
  const cn = flattenLang['zh-CN']
  const otherLangs = Object.keys(languages).filter(key => key !== 'zh-CN')
  const data = Object.entries(cn).map(
    ([key, cnValue]) => [key, cnValue, ...otherLangs.map(lang => flattenLang[lang][key] || '')]
  )
  data.unshift(['key', 'zh-CN', ...otherLangs])
  const worksheetName = 'sheet1'
  const workbook = XLSX.utils.book_new()
  const worksheet = XLSX.utils.aoa_to_sheet(data)
  XLSX.utils.book_append_sheet(workbook, worksheet, worksheetName)
  XLSX.writeFile(workbook, `${__dirname}/auction_locale.xlsx`)
}

const excel2json = () => {
  const langs = Object.keys(languages)
  const langFlattenData = fs.readdirSync(__dirname)
    .filter(name => /\.xlsx/i.test(name))
    .reduce((lang, name) => {
      const workbook = XLSX.readFile(`${__dirname}/${name}`)
      workbook.SheetNames.forEach(sheetName => {
        const worksheet = workbook.Sheets[sheetName]
        const data = XLSX.utils.sheet_to_json(worksheet)
        const flattenLang = data.reduce((flattenLang, o) => {
          langs.forEach(lang => {
            flattenLang[`${lang}.${o.key}`] = o[lang] || undefined
          })
          return flattenLang
        }, {})
        Object.assign(lang, flattenLang)
      })
      return lang
    }, {})

  const langData = flatten.unflatten(langFlattenData)
  consola.info(langFlattenData)
  const exportFile = (lang, fileName) => {
    const str = JSON.stringify(lang, null, 2)
    const file = fs.openSync(`${__dirname}/${fileName}.update.js`, 'w')
    fs.writeSync(file, 'export default ' + str)
    fs.closeSync(file)
  }
  langs.forEach(l => exportFile(langData[l], l))
}

const cmd = process.argv[2]
if (cmd === 'export') {
  json2excel()
} else if (cmd === 'import') {
  excel2json()
} else {
  consola.error('请输入正确指令')
  consola.info('导入: npm run i18n import')
  consola.info('导出: npm run i18n export')
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.