js tips
09 Nov 2017原型链
js对象都有一个__proto__属性指向它的原型对象(ES6的标准语法是Object.getPrototypeOf()),原型对象也有__proto__,自然形成一个链。当访问对象属性时,js会从对象自己,然后一直顺着原型链找下去,直到找到一个包含属性的对象。__proto__的生成规则:
- 当直接创建对象时,创建出的对象的__proto__是Object.prototype,直接创建数组时创建出的数组的__proto__是Array.prototype。
- 当使用new a()创建对象时,创建出的对象的__proto__是a.prototype。
- 当使用Object.create(a)创建对象时,创建出的对象的__proto__就是a。
js执行方法时也顺着原型链去找,需要注意,即使找到了原型里的方法,this还是”子”对象,举例:
var o = {
a: 2,
m: function() {
return this.a + 1;
}
};
console.log(o.m()); // 3
var p = Object.create(o);
p.a = 4;
console.log(p.m()); // 5
Number.MAX_SAFE_INTEGER
这个是js里能保持精度的最大整数,实际上是power(2,53) - 1,正负都不能超过这个数,过了就会计算出错,且没有异常。需要注意的是这个数比java的long要小很多,所以要十分小心,比如java 后台返回json数据里如果有大的整数超过这个范围,在java侧是好的,到js侧就出错了。
promise
- promise的finally部分是一个比较新的特性,很多浏览器不支持(尤其是移动端),小心使用。
遍历数组
- 注意forEach有个明显的缺点,不能break和return
- for in 是用于遍历对象,不要用来遍历数组
- ES6的for of可以用
lodash
虽然lodash很好用,类似Java里的Apache common,但注意lodash会给js增加30k(gzip之后), 所以通常应该根据需要导入特定的函数,其中合并对象的几个函数很容易混淆:
- assign和merge用于向第一个object复制额外的属性
- defaults和defaultsDeep只在第一个object不存在这个属性才复制,所以类似取缺省值的含义。
- assign和defaults执行浅拷贝,merge和defaultsDeep执行深拷贝,或许merge改名为assignDeep更容易理解。
更多细节如下:
_.assign ({}, { a: 'a' }, { a: 'bb' }) // => { a: "bb" }
_.merge ({}, { a: 'a' }, { a: 'bb' }) // => { a: "bb" }
_.defaults ({}, { a: 'a' }, { a: 'bb' }) // => { a: "a" }
_.defaultsDeep({}, { a: 'a' }, { a: 'bb' }) // => { a: "a" }
_.assign handles undefined but the others will skip it
_.assign ({}, { a: 'a' }, { a: undefined }) // => { a: undefined }
_.merge ({}, { a: 'a' }, { a: undefined }) // => { a: "a" }
_.defaults ({}, { a: undefined }, { a: 'bb' }) // => { a: "bb" }
_.defaultsDeep({}, { a: undefined }, { a: 'bb' }) // => { a: "bb" }
They all handle null the same
_.assign ({}, { a: 'a' }, { a: null }) // => { a: null }
_.merge ({}, { a: 'a' }, { a: null }) // => { a: null }
_.defaults ({}, { a: null }, { a: 'bb' }) // => { a: null }
_.defaultsDeep({}, { a: null }, { a: 'bb' }) // => { a: null }
But only _.merge and _.defaultsDeep will merge child objects
_.assign ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "b": "bb" }}
_.merge ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a", "b": "bb" }}
_.defaults ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a" }}
_.defaultsDeep({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a", "b": "bb" }}
And none of them will merge arrays it seems
_.assign ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "bb" ] }
_.merge ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "bb" ] }
_.defaults ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "a" ] }
_.defaultsDeep({}, {a:['a']}, {a:['bb']}) // => { "a": [ "a" ] }
All modify the target object
a={a:'a'}; _.assign (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.merge (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.defaults (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.defaultsDeep(a, {b:'bb'}); // a => { a: "a", b: "bb" }
Lodash treats arrays as objects where the keys are the index into the array.
_.assign ([], ['a'], ['bb']) // => [ "bb" ]
_.merge ([], ['a'], ['bb']) // => [ "bb" ]
_.defaults ([], ['a'], ['bb']) // => [ "a" ]
_.defaultsDeep([], ['a'], ['bb']) // => [ "a" ]
_.assign ([], ['a','b'], ['bb']) // => [ "bb", "b" ]
_.merge ([], ['a','b'], ['bb']) // => [ "bb", "b" ]
_.defaults ([], ['a','b'], ['bb']) // => [ "a", "b" ]
_.defaultsDeep([], ['a','b'], ['bb']) // => [ "a", "b" ]
延时执行
console.log('Start');
setTimeout(() => console.log('TO1'), 0);
setImmediate(() => console.log('IM1'));
process.nextTick(() => console.log('NT1'));
setImmediate(() => console.log('IM2'));
process.nextTick(() => console.log('NT2'));
http.get(options, () => console.log('IO1'));
fs.readdir(process.cwd(), () => console.log('IO2'));
setImmediate(() => console.log('IM3'));
process.nextTick(() => console.log('NT3'));
setImmediate(() => console.log('IM4'));
fs.readdir(process.cwd(), () => console.log('IO3'));
console.log('Done');
将会输出:
Start
Done
NT1
NT2
NT3
TO1
IO2
IO3
IM1
IM2
IM3
IM4
IO1
yarn 和 npm
在npm5之前,新项推荐使用yarn代替npm,最大的好处是对小版本的锁定,性能也好一些。
- yarn upgrade package 可以单独升级一个包,即使这个包使用github仓库也可以(否则yarn.lock会记录commit hash,导致不升级)
- yarn import可以按照现有node_modules目录内容生成一个yarn.lock文件,方便从npm迁移到yarn
- 如果部分包限制node版本导致安装失败,可以用yarn –ignore-engines install,或者干脆配置一下yarn config set ignore-engines true
npm5版本引入了package-lock.json,使得yarn和npm的区别已经不大了。
arrow function
和普通function的一个巨大的差异是this,arrow function保留了this为定义时刻的this,所以在vue的组件method定义中基本上不能使用arrow function
ESLint
如果代码中引用全局变量被ESLint报错,比如Media,可以在相应文件开头上加下面的注释来临时禁止报错。
/* global Media */
VSCode
VSCode如果按照ESLint插件,可以设置以下参数以打开vue文件支持和autoFix,这个设置时针对vscode 1.36.1,高版本不适用。
"eslint.validate": [
"javascript",
{
"language": "vue",
"autoFix": true
},
"javascriptreact"
],
"eslint.autoFixOnSave": true,
"eslint.packageManager": "yarn",
"vetur.validation.template": false
上面最后一句是让vscode忽略一些<template>上的错误。
如果不想在search的时候包括一些文件,可以在设置中加入exclude,比如:
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/build":true,
"**/dist":true
}
ES6 import/export
在ES6之前,JS的模块化有两种方式 AMD和CommonJS,ES6终于有了语言级别的模块化支持:
命名方式:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
或者
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
default方式:
//------ myFunc.js ------
export default function () { ... };
//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
混合方式:
//------ underscore.js ------
export default function (obj) {
...
};
export function each(obj, iterator, context) {
...
}
export { each as forEach };
//------ main.js ------
import _, { each } from 'underscore';
和非ES6代码的对应关系
export { Tiger } <=> module.exports.Tiger = Tiger
module.exports = Tiger <=> export default Tiger
现代一些js库,比如vue,会在dist里直接生成一个.esm.js文件,表示纯ES6模块文件
webpack
- import @ 这是webpack的一个alias,用来代替src目录
- webpack可以load json,所以可以这样
import { version } from '../package.json'
来导入项目package.json里的版本号。 - webpack和rollup的对比,可以看这个小代码 https://gist.github.com/Rich-Harris/79a02519fb00837e8d5a4b24355881c0
vue
vue的目录结构建议(使用vue-router和vuex):
app/
modelA/
components/
xxx.vue
vuex/
xxx.js
index.js
mutations.js
index.js
routes.js
App.vue
index.js
routes.js
vuex.js
assets/
router/index.js
vuex/index.js
components/ (共享的模块)
main.js
index.html
export大多通过index.js方式,写法是这样:
export { default as AAA } from './AAA';
export { default as BBB } from './BBB';
这样的好处是在import时可以比较简洁,参考这里。
localStorage
使用chrome的developer tools可以查看和修改localstorage,非常方便。查看是在application tab页下,双击value可以修改。
localStorage有大小限制,一般认为2.5M是比较安全的一个限制。
chart库
- chart.js 115k 早期版本非常轻量(10k),如果不需要新的功能,可以考虑用旧版本的chart.js
- frappe charts 看起来不错,轻量 但是webpack打包的时候会报错Invalid assignment
- highcharts 商用使用需要授权,75k 生成的chart右下角有个到highcharts的链接,可以通过选项去除。
- c3.js 本身就比较大,而且还需要依赖d3
兼容性
- 字符串的startsWith方法比较新,很多浏览器不支持。
- promise的finally部分很多浏览器不支持
- 使用queryNodeAll得到的NodeList不是Array,它的forEach方法也是后来加上的,所以很多浏览器不支持。