xiaochengyin / blog Goto Github PK
View Code? Open in Web Editor NEWHome Page: https://github.com/XiaoChengyin/blog
License: MIT License
Home Page: https://github.com/XiaoChengyin/blog
License: MIT License
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)
})
})
}
}
作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。
var a = 2;
=> var
、a
、=
、2
、;
引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。
找不到变量:
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
指向 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
call
、apply
、bind
方法绑定,但不能绑定箭头函数。当显示绑定传入参数为 null
或 undefined
时,实际应用默认绑定。new
操作符调用时
this
new
表达式中的函数调用会自动返回这个新对象new
绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数不使用 this
的四种规则,而是根据外层作用域来决定 this
,也就是使用词法作用域规则
var myObject = {
a: 2
}
Object.getOwnPropertyDescriptor(myObject, 'a')
/*
* {
* value: 2, // 值
* writable: true, // 可写
* enumerable: true, // 可枚举
* configurable: true // 可配置
* }
*/
writable
:writable
决定是否可以修改属性的值当
writable
属性是false
时,非严格模式下修改属性值会静默失败,严格模式下会抛出TypeError
错误
configurable
:configurable
决定是否可以使用 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
Getter
和 Setter
// 字面量
var a = {
get a() {}
set a(val) ()
}
// 定义属性
Object.defineProperty(obj, 'prop', {
get() {},
set(val) {}
})
'prop' in obj
:in
操作符会检查属性是否在对象及其原型链中obj.hasOwnProperty('prop')
:只会检查是否在对象中null
)undefined
)boolean
)number
)string
)object
)symbol
)// 两种特殊情况
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()
、String()
、12 + ''
null
=> 'null'
undefined
=> 'undefined'
'[object Object]'
引用类型调用 toString()
方法[1,2,3]
=> 1,2,3
Number()
+new Date()
true
=> 1
false
=> 0
undefined
=> NaN
null
=> 0
''
=> 0
[]
=> 0
引用类型被强制类型转换时先通过 ToPrimitive 操作将值转换为基本类型,然后再将该基本类型值转换为对应的类型。
ToPrimitive 操作会首先检查该值是否有 valueOf()
方法,如果有并返回基本类型值就使用该值进行强制类型转换,如果没有就使用 toString()
的返回值(如果存在)来进行强制类型转换。如果 valueOf()
和 toString()
均不返回基本类型值,会产生 TypeError
错误。
let date = new Date()
+date // 1568801394219
date.valueOf() // 1568801394219
Boolean()
、!!number
undefined
null
false
+0
、-0
、NaN
''
不在假值列表中的值都是真值
Symbol
String(Symbol())
=> 'Symbol'
Symbol
类型不能转换成数字,不能隐式转换成字符串。但可以被转换为布尔值
宽松相等 ==
在比较过程中包含隐式强制类型转换
严格相等 ===
没有强制类型转换
Object.is()
比较与严格相等基本相同
null == undefined
,除此之外 null
和 undefined
不和其他任何值宽松相等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." );
}
"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
[] == ![] // 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 {} 被当作空代码块跳过
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
寄生组合式继承是在组合式继承中用原型式继承代替原型链继承
具有两条继承链,在寄生组合式继承的基础上,子类能够继承父类的静态方法,即 Child.__proto__ === Parent
function Vue () {
this._init()
}
function VueComponent () {
this._init()
}
VueComponent.prototype = Object.create(Vue.prototype)
VueComponent.prototype.constructor = VueComponent
VueComponent.extend = Vue.extend
// ...
在寄生组合式继承的基础上继承了父类的静态方法
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。
如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
理想的 key 值是每项都有的唯一 id。
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
当基于下标的组件进行重新排序时,组件 state 可能会遇到一些问题。
由于组件实例是基于它们的 key 来决定是否更新以及复用,如果 key 是一个下标,那么修改顺序时会修改当前的 key,导致非受控组件的 state(比如输入框)可能相互篡改导致无法预期的变动。
避免“就地复用”,便于跟踪节点位置变化,防止“就地复用”时导致丢失表单输入值等问题。
消息队列是一个先进先出的队列,队列中存有需要执行的任务。事件循环是指每当主线程任务执行完毕后会从消息队列中取出下一个要执行的任务。
所有异步任务可以分为两类,即宏任务和微任务。微任务的优先级大于宏任务,当宏任务执行完毕后会立即执行微任务队列中的所有任务,直到当前微任务队列为空时才会执行下一个宏任务。
宏任务:I/O
、setTimeout
、setInterval
、setImmediate
、requestAnimationFrame
。
微任务:process.nextTick
、MutationObserver
、Promise#then/catch/finally
。
Promise
和 async
函数async/await
只是 Promise
的语法糖,原理与 Promise
是一样的,所以 async
也属于微任务。一个 async
函数就可以理解为一个 Promise
,await
之前的代码相当于 Promise
的回调函数,都是立即执行的同步代码;await
语句相当于 Promise
中的子 Promise
,父 Promise
想要 resolve
必须等子 Promise
先 resolve
才行;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 进行转换,而这是一个微任务。
浏览器解析遇到<script>
标签,页面必须停下来等待代码下载(如果是外链文件)并执行,然后继续处理其他部分。
</body>
标签闭合之前,将所有的<script>
标签放在页面底部。这样能确保在脚本执行前页面已经完成了渲染。<script>
标签越少,加载也就越快,响应也更迅速。无论外链文件还是内嵌脚本都是如此。<script>
标签的 defer/async 属性;<script>
元素来下载并执行代码;defer 与 async :相同点是采用并行下载,在下载过程中不会产生阻塞。区别在于执行时机,async 是加载完成后自动执行,而 defer 需要等待页面加载完成后执行(触发 load 事件,并先于 load 监听函数)。defer 属性仅当 src 属性声明时才生效。
HTTP 请求会带来额外的性能开销,因此下载单个 100KB 的文件将比下载 4 个 25KB 的文件更快。也就是说,减少页面中外链文件的数量将会改善性能。
Function 对象(构造器)的内部属性[[scope]]
包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问。当函数创建时,它的作用于链中插入了一个对象变量。执行此函数时会创建一个称为执行环境/执行上下文(execution context)的内部对象。一个执行环境定义了一个函数执行时的环境。函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境。当函数执行完毕,执行环境就会被销毁。
每个执行环境都有自己的作用域链,用于解析标识符。当执行环境被创建时,它的作用域链初始化为当前运行函数的[[scope]]
属性中的对象。这些值按照它们出现在函数中的顺序,被复制到执行环境的作用域链中。这个过程一旦完成,一个被称为“活动对象(activation object)”的新对象就为执行环境创建好了。活动对象作为函数运行时的变量对象,包含了所有局部变量,命名参数,参数集合以及 this。然后此对象被推入作用域链的最前端。当执行环境被销毁,活动对象也随之被销毁。由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。
闭包允许函数访问局部作用域之外的数据。由于闭包的[[scope]]
属性包含了与执行环境作用域链相同的对象的引用,因此会产生副作用。通常来说,函数的活动对象会随着执行环境一同销毁。但引入闭包时,由于引用仍然存在于闭包的[[scope]]
属性中,因此激活对象无法被销毁。因此闭包需要更多的内存开销。
对象通过一个内部属性_proto_
绑定到它的原型
// memoize() 函数接收两个参数:一个是需要增加缓存功能的函数,
// 一个是可选的缓存对象。
function memoize(fundamental, cache){
cache = cache || {};
let shell = (arg) => {
if(!cache.hasOwnProperty(arg)){
cache[arg] = fundamental(arg);
}
return cache[arg];
};
return shell;
}
*?
而不是*
。因为在贪婪模式下正则引擎会从后向前检索*
后面的部分正则表达式的起始标记应尽可能快速地测试并排出明显不匹配的位置。如一个锚
^ $
、特定字符串x
、字符类[a-z] \s
、单词边界\b
。尽量避免以分组或选择字元开头。
替换前 | 替换后 |
---|---|
`cat | bat` |
`red | read` |
`red | raw` |
.
可以避免回溯失控navigator
-> {appName, appVersion, userAgent, platform}
location
-> window.location
所有属性都是只读的self
-> 指向全局 worker 对象importScripts()
-> 用来加载 Worker 用到的外部 JS 文件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}!`);
};
evel()
和 Function()
构造器,给 setTimeout()
和 setInterval()
传递回调函数而不是字符串作为参数。 > 纸上谈兵?我这没有强缓存头,还是from disk cache
谷歌开发者博客写到关于这个的原因
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)
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可以获取用户的敏感信息如Cookie、SessionID等,进而危害数据安全。
DOM型XSS攻击中取出和执行恶意代码都由前端完成,反射型XSS攻击中取出恶意代码由服务端完成。
在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
转义应在输出是进行,而不是用户提交输入时
避免将不可信数据拼接到以下位置:
onclick
onerror
onload
onmouseover
<a>
标签的 href
location.href =
eval()
的参数setTimeout
、setInterval
、setImmediate
的参数谨慎使用.innerHTML
、v-html
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
禁止来自第三方域名的请求
在Cookie中添加SameSite属性,该属性可以阻止跨站请求时发送Cookie。
跨站:网站顶级域名和二级域名相同视为同站,否则为跨站
跨域:协议、域名、端口完全相同视为同源,否则为跨域
请求类型 | 实例 | Lax |
---|---|---|
链接 | 发送Cookie | |
预加载 | 发送Cookie | |
get表单 | 发送Cookie | |
post表单 | 不发送 | |
iframe | <iframe src="..."></iframe> | 不发送 |
Ajax | $.get() | 不发送 |
img | <img src="..."> | 不发送 |
JSONP | ... | 不发送 |
CSRF攻击无法直接获取用户信息,仅仅是冒用Cookie中的信息。前端在发送请求时携带一个CSRF Token,服务端对Token进行校验。这个Token不能存放在Cookie中。
GET /home HTTP/1.1
(请求) HTTP/1.1 200 OK
(响应)注意事项
_
:
Content-Type
/ Accept
Content-Encoding
/ Accept-Encoding
Content-Language
/ Accept-Language
// 发送端
Content-Language: zh-CN, zh, en
// 接收端
Accept-Language: zh-CN, zh, en
,
隔开scheme://user:password@host:port/path/to/somewhere?key=value#fragment
表示目前是协议处理的中间状态,还需要后续操作
比如post请求一般是先发送head部分,服务器返回100(continue)再发送body
Content-Encoding
对实体部分压缩,HTTP/2实现了对头部的压缩命中强缓存直接读取本地资源,不会向服务器发请求
max-age=10000
命中协商缓存返回304
// 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')
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.