如题 公司项目版本太久(n 0.10.26),二次开发做起来很多阻碍。 尝试将node升级到4,删除项目node modules后重新npm install,出现巨多错误。 请问大家有没有这种经验,平滑升级项目的node版本?
0.10.x版本项目如何平滑升级?
nodemon如何监控html文件改动?
项目目录结构如下:
通过nodemon监控public文件夹下的html文件,修改Html文件时nodemon确实会重启node进程,但是不会刷新浏览器访问的html文件。也就是说不能像 react项目一样实现热加载,,而不是手动在浏览器地址栏刷新,请问这个该如何实现?
30-seconds-code——array
英文文章来源于:https://github.com/Chalarangelo/30-seconds-of-code/blob/master/README.md
Array
数组最大公约数 (arrayGcd)
计算数字数组最大公约数 (gcd).
用 Array.reduce()
和 gcd
运算式 (使用递归) 计算一个数字数组的最大公约数.
const arrayGcd = arr => {
const gcd = (x, y) => !y ? x : gcd(y, x % y);
return arr.reduce((a,b) => gcd(a,b));
}
// arrayGcd([1,2,3,4,5]) -> 1
// arrayGcd([4,8,12]) -> 4
数组最小公倍数 (arrayLcm)
求数字数组的最小公倍数 (lcm).
用 Array.reduce()
和 lcm
运算式 (使用递归) 计算一个数字数组的最小公倍数.
const arrayLcm = arr =>{
const gcd = (x, y) => !y ? x : gcd(y, x % y);
const lcm = (x, y) => (x*y)/gcd(x, y);
return arr.reduce((a,b) => lcm(a,b));
}
// arrayLcm([1,2,3,4,5]) -> 60
// arrayLcm([4,8,12]) -> 24
arrayMax
返回一个数组中的最大值。
使用 Math.max()
结合spread操作符 (...
) 去获取数组中的最大值.
const arrayMax = arr => Math.max(...arr);
// arrayMax([10, 1, 5]) -> 10
arrayMin
返回数组中的最小值.
用 Math.min()
集合spread操作符 (...
) 去获取数组中的最小值.
const arrayMin = arr => Math.min(...arr);
// arrayMin([10, 1, 5]) -> 1
chunk
将一个数组分成指定长度的块数组.
用 Array.from()
生成一个适当长度的块数组,然后遍历整个块数组.
用 Array.slice()
分割原数组向块数组中的块中插入元素,块的长度为 size
.
如果原数组最终不能在进行分割,那么这个块将包含剩余的所有元素.
const chunk = (arr, size) =>
Array.from({length: Math.ceil(arr.length / size)}, (v, i) => arr.slice(i * size, i * size + size));
// chunk([1,2,3,4,5], 2) -> [[1,2],[3,4],[5]]
compact
将数组中的非真值移除.
用 Array.filter()
去移除非真值 (false
, null
, 0
, ""
, undefined
, and NaN
).
const compact = arr => arr.filter(Boolean);
// compact([0, 1, false, 2, '', 3, 'a', 'e'*23, NaN, 's', 34]) -> [ 1, 2, 3, 'a', 's', 34 ]
注:Boolean的初始值为false
countOccurrences
计算一个值在数组中出现的次数.
用 Array.reduce()
去计算每次遇到的特定值的次数.
const countOccurrences = (arr, value) => arr.reduce((a, v) => v === value ? a + 1 : a + 0, 0);
// countOccurrences([1,1,2,1,2,3], 1) -> 3
deepFlatten
抹平数组(没有嵌套的数组).
使用递归.
用 Array.concat()
拼接一个空数组 ([]
) 然后使用spread操作符 (...
) 去抹平数组.
const deepFlatten = arr => [].concat(...arr.map(v => Array.isArray(v) ? deepFlatten(v) : v));
// deepFlatten([1,[2],[[3],4],5]) -> [1,2,3,4,5]
difference
返回的数组中元素来自于源数组,但不包含目标数组中的元素.
将目标数组转化为一个 Set
, 然后用 Array.filter()
过滤原数组返回不在目标数组中的元素.
const difference = (a, b) => { const s = new Set(b); return a.filter(x => !s.has(x)); };
// difference([1,2,3], [1,2,4]) -> [3]
const difference2 = (a, b) => a.filter(x => b.indexOf(x) === -1)
// difference2([1,2,3], [1,2,4]) -> [3]
const difference2 = (a, b) => a.filter(x => !b.includes(x))
// difference2([1,2,3], [1,2,4]) -> [3]
differenceWith
返回的数组中元素来自于源数组和目标数组中的元素经过comp
后的元素.
用 Array.filter()
和 Array.find()
去找出合适的元素.
const differenceWith = (res, dest, comp) => res.filter(a => !dest.find(b => comp(a, b)))
// differenceWith([1, 1.2, 1.5, 3], [1.9, 3], (a,b) => Math.round(a) == Math.round(b)) -> [1, 1.2]
distinctValuesOfArray
数组去重.
用 ES6 Set
和 ...rest
操作符去除所有重复的元素.
const distinctValuesOfArray = arr => [...new Set(arr)];
// distinctValuesOfArray([1,2,2,3,4,4,5]) -> [1,2,3,4,5]
dropElements
移除数组中的元素直到func
返回 true
,返回使数组中剩余的元素.
循环数组, 判断func
是否返回 true
,如果否使用 Array.slice()
移除数组中的第一个元素,直到 function
返回 true
; 否则,直接返回数组.
const dropElements = (arr, func) => {
while (arr.length > 0 && !func(arr[0])) arr.splice(0,1);
return arr;
};
// dropElements([1, 2, 3, 4], n => n >= 3) -> [3,4]
const dropElements = (arr, func) => {
while (arr.length > 0 && !func(arr[0])) arr.splice(0, 1);
return arr;
};
dropElements([4, 3, 2, 1], n => n >= 3)
移除数组中的元素如果func
返回 true
使用 Array.filter()
来进行数组过滤
const dropElements = (arr, func) => arr.filter(!func)
dropElements([4, 3, 2, 1], n => n >= 3)
dropRight
返回一个从用变去除 n
个元素的数组
如果 n
小于数组的长度用 Array.slice()
去分割数组,然后返回分割得到的数组,否则返回一个空数组.
const dropRight = (arr, n = 1) => n < arr.length ? arr.slice(0, arr.length - n) : []
// dropRight([1,2,3]) -> [1,2]
// dropRight([1,2,3], 2) -> [1]
// dropRight([1,2,3], 42) -> []
const dropRight = (arr, n = 1) => n < arr.length ? arr.splice(0, arr.length - n) : []
// dropRight([1,2,3])
everyNth
每遍历 n
个元素,返回一个元素.
用 Array.filter()
去筛选出一个数组,筛选条件是每遍历 n
个元素,返回一个元素.
const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
// everyNth([1,2,3,4,5,6], 2) -> [ 2, 4, 6 ]
filterNonUnique
过滤移除数组中重复出现的元素.
用 Array.filter()
保留数组中独一无二的元素.
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
// filterNonUnique([1,2,2,3,4,4,5]) -> [1,3,5]
flatten
抹平数组.
用一个空数组和spread ...
操作符来生成一个没有嵌套的数组.
const flatten = arr => [].concat( ...arr );
// flatten([1,[2],3,4]) -> [1,2,3,4]
flattenDepth
抹平数组取决于指定的值 depth
.
通过递减 depth
,然后使用递归来完成.
用 Array.reduce()
和 Array.concat()
来合并元素或者数组.
默认 depth
值为 1
时停止递归.
省略定义 depth
,将返回数组本身.
const flattenDepth = (arr, depth = 1) =>
depth != 1 ? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flattenDepth(v, depth - 1) : v), [])
: arr.reduce((a, v) => a.concat(v), []);
// flattenDepth([1,[2],[[[3],4],5]], 2) -> [1,2,[3],4,5]
groupBy
根据给定的函数来对数组中的元素进行分组.
用 Array.map()
依据 func
来遍历数组中的元素.
用 Array.reduce()
创建一个对象, 对象中的key是map的结果.
const groupBy = (arr, func) =>
arr.map(typeof func === 'function' ? func : val => val[func])
.reduce((acc, val, i) => { acc[val] = (acc[val] || []).concat(arr[i]); return acc; }, {});
// groupBy([6.1, 4.2, 6.3], Math.floor) -> {4: [4.2], 6: [6.1, 6.3]}
// groupBy(['one', 'two', 'three'], 'length') -> {3: ['one', 'two'], 5: ['three']}
head
返回数组中的第一个元素.
用 arr[0]
返回传入数组的第一个元素.
const head = arr => arr[0];
// head([1,2,3]) -> 1
initial
返回数组中出最后一个外的所有元素.
用 arr.slice(0,-1)
来实现.
const initial = arr => arr.slice(0, -1);
// initial([1,2,3]) -> [1,2]
initialize2DArray
通过传入宽高和默认值来初始化一个二维数组.
用 Array.map()
去生成一个 h
列, 每列是一个长度为 w
,默认值是 value
的数组的二维数组. 如果默认值没有提供,那么默认值就为 null
.
const initialize2DArray = (w, h, val = null) => Array(h).fill().map(() => Array(w).fill(val));
// initializeArrayWithRange(2, 2, 0) -> [[0,0], [0,0]]
initializeArrayWithRange
初始化一个数组,这个数组包含限定范围的数字 start
和 end
.
用 Array.from((end + 1) - start)
去创建一个长度为end - start + 1
的数组,然后用Array.map()
为初始化的数组赋值.
你可以省略 start
,它的默认值是 0
.
const initializeArrayWithRange = (end, start = 0) =>
Array.from({ length: (end + 1) - start }).map((v, i) => i + start);
// initializeArrayWithRange(5) -> [0,1,2,3,4,5]
// initializeArrayWithRange(7, 3) -> [3,4,5,6,7]
initializeArrayWithValues
初始化一个长度为 n
数组,默认值为 value
的数组.
用 Array(n)
去创建一个长度为n,元素值为空的数组,然后用 fill(v)
为每个元素赋值为 value
.
如果省略 value
,元素的默认值为 0
.
const initializeArrayWithValues = (n, value = 0) => Array(n).fill(value);
// initializeArrayWithValues(5, 2) -> [2,2,2,2,2]
intersection
返回两个数组的共有元素.
将 b
转换为一个集合 Set
, 然后用 Array.filter()
过滤掉 a
中包含 b
中的元素.
const intersection = (a, b) => { const s = new Set(b); return a.filter(x => s.has(x)); };
// intersection([1,2,3], [4,3,2]) -> [2,3]
last
返回数组中的最后一个元素.
用 arr.length - 1
作为给定数组最后一个元素的索引,返回该索引位置的元素.
const last = arr => arr[arr.length - 1];
// last([1,2,3]) -> 3
mapObject
用一个函数作为映射规则,将数组转化为一个对象, 对象的键值对由原始的的的值作为键,映射得到的值作为值.
使用内部匿名函数的作用域去声明一个undefined的内存空间, 用闭包去存储返回值. 用一个新 Array
存储原数组和经过 fn
映射的结果,然后用 ,
操作符实现下一步的return操作, 无需上下文的切换 (由于闭包和操作顺序).
const mapObject = (arr, fn) =>
(a => (a = [arr, arr.map(fn)], a[0].reduce( (acc,val,i) => (acc[val] = a[1][i], acc), {}) )) ();
/*
const squareIt = arr => mapObject(arr, a => a*a)
squareIt([1,2,3]) // { 1: 1, 2: 4, 3: 9 }
*/
nthElement
返回数组中的第 n
个元素.
用 Array.slice()
去获得包含第 n
个元素的数组,数组长度为1.
如果索引超出数组范围,返回一个 []
.
如果省略参数 n
, 默认的获取数组的第一个元素.
const nthElement = (arr, n = 0) => (n > 0 ? arr.slice(n,n+1) : arr.slice(n))[0];
// nthElement(['a','b','c'],1) -> 'b'
// nthElement(['a','b','b'],-3) -> 'a'
pick
从对象中筛选出arr
中指定的键值对.
用 Array.reduce()
去过滤并挑选出对象中所包含的键的键值对.
const pick = (obj, arr) =>
arr.reduce((acc, cur) => (cur in obj && (acc[cur] = obj[cur]), acc), {});
// pick({ 'a': 1, 'b': '2', 'c': 3 }, ['a', 'c']) -> { 'a': 1, 'c': 3 }
pull
去除数组中的指定值.
用 Array.filter()
和 Array.includes()
清除数组中不需要的值.
用 Array.length = 0
初始化数组,然后用arr.push()放入过滤后剩余的数组元素 pulled
.
const pull = (arr, ...args) => {
let argState = Array.isArray(args[0]) ? args[0] : args;
let pulled = arr.filter((v, i) => !argState.includes(v));
arr.length = 0;
pulled.forEach(v => arr.push(v));
};
// let myArray1 = ['a', 'b', 'c', 'a', 'b', 'c'];
// pull(myArray1, 'a', 'c');
// console.log(myArray1) -> [ 'b', 'b' ]
// let myArray2 = ['a', 'b', 'c', 'a', 'b', 'c'];
// pull(myArray2, ['a', 'c']);
// console.log(myArray2) -> [ 'b', 'b' ]
pullAtIndex
过滤出指定索引的数组元素,并修改原数组.
用 Array.filter()
和 Array.includes()
提取不需要的元素.
用 Array.length = 0
初始化数组且长度为零, 用 Array.push()
重新传入剩余的元素.
用 Array.push()
记录pulled值
const pullAtIndex = (arr, pullArr) => {
let removed = [];
let pulled = arr.map((v, i) => pullArr.includes(i) ? removed.push(v) : v)
.filter((v, i) => !pullArr.includes(i))
arr.length = 0;
pulled.forEach(v => arr.push(v));
return removed;
}
// let myArray = ['a', 'b', 'c', 'd'];
// let pulled = pullAtIndex(myArray, [1, 3]);
// console.log(myArray); -> [ 'a', 'c' ]
// console.log(pulled); -> [ 'b', 'd' ]
pullAtValue
从原数组中过滤出指定元素. 返回过滤出的元素.
用 Array.filter()
和 Array.includes()
移除不必要的元素.
用 Array.length = 0
初始化元素组且长度为零,用 Array.push()
重新传入剩余的元素.
用 Array.push()
记录 pulled 值
const pullAtValue = (arr, pullArr) => {
let removed = [],
pushToRemove = arr.forEach((v, i) => pullArr.includes(v) ? removed.push(v) : v),
mutateTo = arr.filter((v, i) => !pullArr.includes(v));
arr.length = 0;
mutateTo.forEach(v => arr.push(v));
return removed;
}
/*
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtValue(myArray, ['b', 'd']);
console.log(myArray); -> [ 'a', 'c' ]
console.log(pulled); -> [ 'b', 'd' ]
*/
const pullAtValue = (arr, pullArr) => {
let removed = [];
arr.forEach((v, i) => pullArr.includes(v) ? removed.push(v) : v);
pullArr.filter((v) => arr.splice(arr.indexOf(v), 1));
return removed;
}
/*
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtValue(myArray, ['b', 'd']);
console.log(myArray); -> [ 'a', 'c' ]
console.log(pulled); -> [ 'b', 'd' ]
*/
remove
如果给定的函数返回 false
, 则从数组中移除元素.
用 Array.filter()
找返回 false
的数组元素,用 Array.reduce()
和 Array.splice()
对原数组进行处理.
func
调用 (value, index, array
) 参数.
const remove = (arr, func) =>
Array.isArray(arr) ? arr.filter(func).reduce((acc, val) => {
arr.splice(arr.indexOf(val), 1);
return acc.concat(val);
}, []) : [];
// remove([1, 2, 3, 4], n => n % 2 == 0) -> [2, 4]
sample
从数组中返回一个随机元素.
用 Math.random()
生成一个随机数, 乘以数组的 length
,然后 Math.floor()
进行下舍入.
这个方法也适合字符串.
const sample = arr => arr[Math.floor(Math.random() * arr.length)];
// sample([3, 7, 9, 11]) -> 9
similarity
返回一个数组,数组元素在两个给定的数组中均有.
用 filter()
过滤出不在另一个数组中的 values
, 确定条件用 includes()
.
const similarity = (arr, values) => arr.filter(v => values.includes(v));
// similarity([1,2,3], [1,2,4]) -> [1,2]
symmetricDifference
返回一个数组,包含在给定的两个数组中均未出现的元素.
为每个数组创建一个 Set
, 然后用 Array.filter()
过滤剩下都有的元素.
const symmetricDifference = (a, b) => {
const sA = new Set(a), sB = new Set(b);
return [...a.filter(x => !sB.has(x)), ...b.filter(x => !sA.has(x))];
}
// symmetricDifference([1,2,3], [1,2,4]) -> [3,4]
const symmetricDifference = (a, b) => [...(new Set([...a,...b]))].filter((v) => !a.includes(v) || !b.includes(v));
// symmetricDifference([1,2,3], [1,2,4]) -> [3,4]
shuffle
随机化一个数组值的顺序.
用 Array.sort()
重新排序数组 , 用 Math.random()
作比较器.
const shuffle = arr => arr.sort(() => Math.random() - 0.5);
// shuffle([1,2,3]) -> [2,3,1]
tail
返回数组中出第一个元素外的所有元素.
如果,数组 length
大于 1
返回 arr.slice(1)
,否者,返回整个数组.
const tail = arr => arr.length > 1 ? arr.slice(1) : arr;
// tail([1,2,3]) -> [2,3]
// tail([1]) -> [1]
take
返回指定长度的数组,数组中元素从数组第一个元素起.
用 Array.slice()
从原始数组的第一个元素起,截取 n
个元素.
const take = (arr, n = 1) => arr.slice(0, n);
// take([1, 2, 3], 5) -> [1, 2, 3]
// take([1, 2, 3], 0) -> []
takeRight
返回指定长度的数组,数组中元素从数组最后一个元素起.
用 Array.slice()
从原始数组的最后一个元素起,截取 n
个元素.
const takeRight = (arr, n = 1) => arr.slice(arr.length - n, arr.length);
// takeRight([1, 2, 3], 2) -> [ 2, 3 ]
// takeRight([1, 2, 3]) -> [3]
union
返回两个数组中存在的所有元素.
创建一个 Set
用 a
和 b
所有元素,然后转换为数组.
const union = (a, b) => Array.from(new Set([...a, ...b]));
// union([1,2,3], [4,3,2]) -> [1,2,3,4]
without
从数组中移除所有指定的元素.
用 Array.filter()
过滤出所有制定的元素(用 !Array.includes()
做过滤条件 ).
(For a snippet that mutates the original array see pull
)
const without = (arr, ...args) => arr.filter(v => !args.includes(v));
// without([2, 1, 2, 3], 1, 2) -> [3]
zip
创建一个数组,根据原始数组中的位置进行分组.
用 Math.max.apply()
获得参数中最长的数组的长度.
创建一个用该长度做返回值并使用 array.from
和 map()
创建分组的元素数组.
如果参数数组的长度不同, undefined
将被用于没有元素的地方.
const zip = (...arrays) => {
const maxLength = Math.max(...arrays.map(x => x.length));
return Array.from({length: maxLength}).map((_, i) => {
return Array.from({length: arrays.length}, (_, k) => arrays[k][i]);
})
}
//zip(['a', 'b'], [1, 2], [true, false]); -> [['a', 1, true], ['b', 2, false]]
//zip(['a'], [1, 2], [true, false]); -> [['a', 1, true], [undefined, 2, false]]
zipObject
给定一个有效的属性标识数组,返回一个属性与值相关联的对象.
因为对象可以有未定义的值,但不能有未定义的属性指针,所以使用 Array.reduce()
决定结果对象的结构.
const zipObject = ( props, values ) => props.reduce( ( obj, prop, index ) => ( obj[prop] = values[index], obj ), {} )
// zipObject(['a','b','c'], [1,2]) -> {a: 1, b: 2, c: undefined}
// zipObject(['a','b'], [1,2,3]) -> {a: 1, b: 2}
更多关于30-seconds-code中文翻译 https://github.com/lvzhenbang/article/blob/master/js/30-seconds-code/index.md
对比rust社区的积极性,node真的是…
对比rust社区的积极性,node真的是…
来自酷炫的 CNodeMD
Egg.js入门
前言
本文首发公众号 【一名打字员】
众所周知 JavaScript
的生态圈出了名的好,配套设施完善,所以使用起来特别的顺手。前几次我们介绍了使用 express
来进行简单的微信公众号开发,相信大家都已经了解到使用 node
可以方便快捷的进行开发 web
应用。但是由于阿里系的 egg
框架近期完成了 2.2.0
版本的迭代,让我们看到了他们努力打造的框架中有很多值得学习以及参考的地方,所以【小谱君】团队也在使用 egg
进行整个系统的重构,包括参考 egg
的代码格式规范等等,进行一系列深层次的改造。
今天来安利一下 egg
,通过 egg
快速选择适合对应业务模型的脚手架,快速启动项目的开发。
简介
熟悉的打字员应该知道, egg
现在在社区特别的火爆,官方对它自己的定义是
为企业级框架和应用而生,希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。
因为 egg
是基于koa 2x开发的,所以官方还特别强调了与其它两个社区框架 Express
与 Sails
的差异点
- Express 是 Node.js 社区广泛使用的框架,简单且扩展性强,非常适合做个人项目。但框架本身缺少约定,标准的 MVC 模型会有各种千奇百怪的写法。Egg 按照约定进行开发,奉行『约定优于配置』,团队协作成本低。
- Sails 是和 Egg 一样奉行『约定优于配置』的框架,扩展性也非常好。但是相比 Egg,Sails 支持 Blueprint REST API、WaterLine 这样可扩展的 ORM、前端集成、WebSocket 等,但这些功能都是由 Sails 提供的。而 Egg 不直接提供功能,只是集成各种功能插件,比如实现 egg-blueprint,egg-waterline 等这样的插件,再使用 sails-egg 框架整合这些插件就可以替代 Sails 了。
特性
- 提供基于 Egg 定制上层框架的能力
- 高度可扩展的插件机制
- 内置多进程管理
- 基于 Koa 开发,性能优异
- 框架稳定,测试覆盖率高
- 渐进式开发
个人比较喜欢的地方就是其可扩展性特别高,而且作为特别喜欢渐进式开发的打字员来说,不要太友好唷。而且是熟悉 Web 开发或 MVC 模式开发的童鞋的福音,因为在这里你能看到自己熟悉的controller和service,开发起来棒棒哒。
快速开始
怎么快速的用 egg
快速的进行开发呢,我们选择使用其官方提供的内置脚手架,推荐egg-init
。
- 创建项目
首先安装 egg
(npm i egg-init -g
), 然后初始化项目 egg-init egg-example --type=simple
。
- 目录结构
进入生成的 egg-example
目录,其结构如下:
egg-example ├── package.json(配置文件) ├── app | ├── router.js(路由文件) │ ├── controller │ | └── home.js(用户控制层) ├── config | ├── plugin.js(插件配置) | ├── config.default.js(默认配置) └── test └── app └── controller └── home.test.js(测试文件)
安装依赖之后我们就能够运行 npm run dev
在本地查看了。
在图中我们可以看到,程序已经正常的在本地跑了起来,打印出当前使用的版本以及当前的 agent_worker
并监听了7001这个端口。关于 agent
和 worker
以及 master
的详细不在这里具体介绍,有兴趣的童鞋可以移步官网。
- 编写程序
接下来我们就能在 controller
编写自己的业务逻辑代码了,为了保持 Controller```` 中的逻辑更加简洁,官方推荐增加
service层,什么是
Service呢,相信很多同学对它还很陌生,简单来说,
Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。同时把业务逻辑进行封装之后还可以保持业务逻辑的独立性,抽象出来的
Service可以被多个
Controller``` 重复调用。并且将逻辑和展现分离,更容易编写测试用例。
这样一来,是否可以将之前写的微信公众号系统进行重构呢,在代码和整个架构上进行一个规范,赶紧动动手吧。
结语
国内前端优秀的开发团队还有很多,但是阿里算是做的比较好的,至少他家的文档让我整体看来很舒服,而且逐渐把前端的一些概念开始扩展,融入了一些后端的思想。想想还有特别火热的 RxJs
系列,看来未来前端依旧会走在时尚的前流。
另外在学习的过程中发现问题我们可以给 egg
团队提 issue
的哇,或者有更好的解决方案可以提交 PR
,万一被合并了,这牛也可以吹半辈子了哈。
如何像调试手机版chrome一样调试华为自带手机浏览器?(急)
如何像调试手机版chrome一样调试华为自带手机浏览器?(急)
请问大佬们,cluster.on(disconnect)
请问大佬们,worker.isDead()在什么情况下才为true,试过了很多方法,process.kill(), process.exit() worker.kill()返回的都是false cluster.on(‘disconnect’,function (worker) { console.log(worker.isDead()) })
react 写cnode api 已发布话题编辑功能
在学react ,用react写cnode社区,已发布话题编辑功能不会了,请教各位大神,如果有写过的想参考一下。谢谢
2018年来了,谁有2017年JS项目的汇总?
例如:2016年崛起的JS项目https://risingstars2016.js.org/ PS:也不知道别人说的火,怎么定义的?star?
请问大家用什么方式检测nodejs内存泄漏?
最近上了一个项目,pv高了以后内存升的很快,反复测试后只发现一个渲染的方法内存升的高些,以前没有类似的经验不知道如何比较完整的检测nodejs应用内存泄漏的情况?
【长沙】互联网传媒团队求入伙
有从事互联网传媒、广告创意、视频剪辑,平面设计,图片制作美化,摄像拍照技术类经验的小伙伴求入伙
待遇啥的请加微信号:ren_lovekc 或者直接简历到邮箱:hunan@xiangbojiubo.com
[上海] Genaro 诚聘 前端/后端/全端 各种端 NodeJS/GoLang, 20-30k,入职有 sign up bonus。
Genaro 诚聘 前端 /后端 /全端 各种端 NodeJS/GoLang,20-30k,入职有 1 月薪水的 sign up bonus。 有挑战的码术,上海颜值领先的公司(特指全司女性),看看手中的 H1B,想想川建国的反移民策略,不如投入到区块链中,In Blockchain We Trust!
入职即拿一个月薪水,先定 offer,拿完年终再来也不迟。
欢迎简历投至 join#genaro.network,请注明来源 CNode 哦 ;) 有任何问题欢迎留言!
公司信息
Genaro Network,区块链 3.0 生态缔造者,首个结合去中心化存储网络的图灵完备公有链,独创 SPoR(Sentinel Proof of Retrivebility) + PoS (Proof of Stake) 混合共识机制,致力于打造可高效处理大数据区块链平台,将“云”进行“区块链”化的底层技术革命。Genaro 为开发者提供一站式开发平台,可以同时部署智能合约并存储 DAPP 所需数据,同时,为每个人提供一个更值得信任的互联网与分享社区。
更多信息: 官网: https://genaro.network/白皮书: https://genaro.network/en/whitepaper/ GitHub: https://github.com/GenaroNetwork Telegram: https://t.me/GenaroNetworkOfficial微信公众号:GenaroNetwork
招聘职位
全栈工程师
岗位职责:
- 负责各项产品的开发,与在实际业务场景中的落地
- 实现底层服务代码、前端框架的设计和开发、系统平台接口对接及研发
- 产品技术设计与开发,测试并负责产品的维护和升级
- 负责与其它项目的协调和技术交流工作
岗位要求:
- 熟悉 javascript 语言
- 有 vuejs,electron 开发经验
- 有 nodejs , python, golang 开发经验优先
- 熟悉 redis,mongoDB,levelDB 以及基础的数据库操作的优先
- 熟悉各种数据结构和算法,对密码学、安全协议有研究的优先
- 良好的代码习惯,对新技术有热情
后端开发工程师
岗位职责:
- 负责社区产品的基础架构的设计与实现
- 负责社区后端核心模块的设计与开发
- 负责技术攻关和创新技术引用,解决产品开发过程中的高性能,高并发和高可靠性问题
- 有贡献开源代码或维护自己博客的优先
岗位要求:
- 3 年以上互联网开发经验,有大规模后端服务的设计和开发方面经验,对 Unix/Linux 有深入了解
- 出色的编码能力,精通如下两种语言之一( Nodejs,golang )
- 对后端程序性能优化有丰富经验
- 熟悉常用的算法,熟悉开源框架,解决问题能力强
- 对分布式计算,分布式存储的架构设计理念和动机有一定理解
前端开发工程师
岗位职责:
- 负责产品的 Web 全栈架构设计和研发
- 负责产品跨平台的架构设计和研发
岗位要求:
- 本科以上学历,2 年以上相关工作经验
- 能够熟练运用 HTML、CSS、JavaScript 构建高性能的 Web 应用程序
- 能够熟练运用至少一款主流的 JS 框架,具有良好的代码风格、接口设计与程序架构
- 掌握 Node.js 开发框架,了解或实践过 Electron 跨平台 Web 开发框架的优先
- 掌握至少一门服务器端程序设计语言,对 OOP 有一定的认识
产品经理
岗位职责:
- 需求的挖掘和分析,与市场,销售工作人员协调工作,明确产品的市场作用
- 推动产品目标的实现,结合市场调查与竞争对手调查,确定产品功能与形态,完善用户体验,设计产品,协调设计师与工程师的工作
岗位要求:
- 本科以上学历,2 年以上一线互联网公司产品经理经验
- 具有代表性案例
- 具备一定技术与资料分析背景,可以根据市场需求制定产品,并明确开发工作量 具备一定的领导力
请问编译nodejs后,那些lib下的.js文件去哪里了?=。= 不会是打包进node二进制里面了吧?
在nodejs的安装目录下没找到这些js文件。。求个大神来说一下。。谢了
[北京]ofo 小黄车内推,部门直招,急缺前端 25K+,安卓 20K+, ios20K+
部门急缺前端、安卓和IOS的小伙伴,有兴趣的朋友可以发简历到liuye#ofo.com
。坑位很多,大家有兴趣的话可以抽时间过来聊聊,如果可以的话,马上过来最好,当然,回去等一波年终奖再来也行。大家想要了解更多的话,也可以直接加我QQ 517224043
正在找工作的朋友请注意(手动@万达小伙伴们),真的是急缺,面试效率贼高,面试当天出结果,下周即可入职,欢迎大家投简历。
其他岗位有兴趣的可以直接给我简历,可以帮忙推到其他部门。
贴一下jd,仅供参考。工作年限和框架都不是硬性要求,基础扎实是重点,其他都是加分项。
高级前端开发工程师
1、本科及以上学历,计算机、通信及相关专业,3年以上Web前端开发经验
2、前端 HTML、CSS、JavaScript 基础扎实,熟悉 vue.js 全家桶 或 React.js 全家桶
3、精通HTML/XHTML、CSS等网页制作技术,熟悉页面架构和布局
4、精通JavaScript、Ajax等Web开发技术,熟练运用至少一款主流的JS框架
5、熟悉W3C标准,对表现与数据分离、Web语义化等有较为深刻的理解;能熟练编写具有良好风格规范的代码
6、熟悉用户交互设计理论,热衷分析并改善产品的用户体验
7、独立负责过前端开发
9、熟悉react相关技术优先考虑
9、熟悉团队开发流程,熟练使用git工具进行代码管理
10、对下一代Web技术(如HTML5、Node.js)有强烈兴趣,有良好的学习能力和强烈的进取心
11、有较强的学习能力和较高的学习热情;具备积极向上的创业心态,良好的职业素质,具备成熟的团队协作意识
前端开发工程师
1、本科及以上学历,计算机、通信及相关专业
2、前端 HTML、CSS、JavaScript 基础扎实
安卓开发工程师
1、本科以上学历,计算机或相关专业;
2、2 年以上从事android 开发工作经验,良好的 java、C/C++ 编程能力,熟练掌握android 平台上app 性能优化的工具和方法;
3、熟悉面向对象的程序设计方法,熟悉设计模式,基础扎实,精通常用数据结构与算法;
4、熟悉 android 高级界面开发,掌握一到多款绘制引擎和图形库;
5、熟悉 TCP/IP 协议和网络编程相关知识;
6、了解数据库,熟悉 SQL 的使用;
7、具备良好的学习能力和分析解决问题能力,强烈的责任心和团队精神,具有良好的沟通能力
IOS开发工程师
工作职责:
-负责 ofo 产品 iOS 端的开发、架构设计、性能优化 、产品体验优化
任职资格:
1、计算机相关专业本科以上学历
2、精通编程语言:Object C, Swift
3、精通 iOS 开发环境,有其他平台开发经验尤佳。
4、有良好的数据结构及算法基础功底、熟悉多线程编程和网络编程
5、对 Socket 通信,TCP/IP 和 HTTP 有深刻理解和经验,有 PHP Server 端开发经验者优先
6、有比较强的产品意识,能够推动相关角色一起把产品做好
7、有敏锐的趋势判断和洞察能力,能够根据行业的发展方向,规划好产品的发展
8、踏实、勤奋的工作激情,敏锐全面的洞察力,清晰缜密的逻辑思维能力,以及独立的分析调研能力
9、良好的沟通能力和团队合作能力
10、能承担比较大的工作压力
11、有比较强的自我驱动和自我激励意识,也能影响和激发团队的氛围
高级测试开发工程师
工作职责:
1、根据需求设计设计测试用例,对需求有一定的敏感度,
2、编写自动化测试测试脚本;参与 app/接口的相关测试,并且分析测试结果,跟踪并推动 Bug 的合理解决;
3、包括服务端的功能、自动化、性能测试等;
4、能够独立进行项目测试,包括参与需求分析、设计评审,编写测试方案和测试用例,搭建测试环境,跟进测试结果和线上反馈等;
5、能够根据项目需求设计、开发测试工具提高测试效率,或者对研发或测试流程提出优化建议
任职资格:
1、有三年(含)以上测试经验,有性能测试或 Web 以及 app 测试经验者优先;
2、熟悉 Java/python/php 等至少一种编程语言,熟练使用一种脚本语言;
3、熟悉 Linux 或类 unix 的系统,能够熟练使用常见的命令,对操作系统原理熟悉优先;
4、熟悉数据库原理,能够熟练使用常见的 SQL 语句,对 Mysql 性能优化有经验优先;
5、有良好的团队合作意识、沟通表达能力和协调能力,有较强的学习能力并热心分享与积累文档。能够承受一定压力
后端开发工程师
工作职责:
1、负责公司平台产品的基础架构的设计与实现
2、负责公司后端核心模块的设计与开发
3、负责技术攻关和创新技术引用,解决产品开发过程中的高性能,高并发和高可靠性问题
4、有贡献开源代码或维护自己博客的优先
任职资格:
1、3 年以上互联网开发经验,有大规模后端服务的设计和开发方面经验,对 Unix/Linux 有深入了解
2、 出色的编码能力,精通如下三种语言之一( Nodejs,PHP,Java ),尤其使用它们做过 web 服务。
3、对后端程序性能优化有丰富经验
4、掌握 Redis、Mongodb,Mysql 等存储系统原理及应用。
5、 熟悉常用的算法,熟悉开源框架,解决问题能力强。
6、对分布式计算,分布式存储的架构设计理念和动机有一定理解
7、 对 HTTP 协议有深刻的理解,熟悉 TCP/IP 等网络协议
自己练手的一个项目
vue-node-blog
vue-node-blog
描述
一个练手的vue+node的博客,实现的功能有: 发文章、修改文章、文章的喜欢及收藏、关注用户、评论及二级评论、评论点赞、历史记录、我的文章、修改个人资料、一些删除功能等, 登录、注册,以及接入了github第三方登录,并抓取了一些掘金的文章,方便看效果 第一次发的小项目,不足之处以及写的不好的地方,欢迎大家提出来,一起交流!觉得好的话,也欢迎star!<br> 因为用是国外的服务,所以获取数据的时会慢一点。<br> 线上地址: https://vue-node-blog.herokuapp.com<br> github地址: https://github.com/Kim09AI/vue-node-blog
技术栈
vue、vue-router、vuex、stylus、ES6、nodejs、express、mongodb、Ajax使用axios(前后端共用)
运行
开发模式
需全局安装supervisor和cross-env<br>
在项目根目录 npm install
启动mongodb再进入server运行 npm run dev
然后在项目根目录运行 npm run dev
本地以生产模式运行
在项目根目录 npm run build
然后启动mongodb再进入server运行 npm run pro
,
npm run pro
之前需要在server/config
添加production.js
,可参考如下配置,如需其他配置可自行配置
module.exports = {
homeUrl: '/',
}
目录结构
vue-node-blog
├─src
| ├─App.vue
| ├─main.js
| ├─store
| ├─router
| ├─components // 业务组件
| ├─common // js、css等
| ├─base // 与业务不相关的组件
| ├─assets
| ├─api // 前端获取数据的接口
├─server
| ├─index.js // 主程序
| ├─package.json
| ├─routes // 路由文件
| ├─public // 静态资源
| ├─models // 存放操作数据库的文件
| ├─middlewares // 中间件
| ├─logs
| ├─lib
| ├─grabData // 文章抓取程序
| ├─config // server配置文件
// 开发环境使用cors跨域
if (isDev) {
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", config.homeUrl)
res.header("Access-Control-Allow-Credentials", true) // 携带cookie
res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With")
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
if (req.method == "OPTIONS") {
res.send(200) // 让options请求快速返回
} else {
next()
}
})
}
// 前端部分需要设置withCredentials为true才能传输cookie
axios.defaults.withCredentials = true
// 生产环境配合vue-router使用history模式
if (!isDev) {
app.get('*', (req, res, next) => {
res.sendFile(path.join(__dirname, '../../dist/index.html'))
})
}
前端登录状态判断,需要登录才能进行的操作未登录就弹出登录弹窗
// 在路由的meta 添加 meta: {shouldLogin: true}
// 全局的页面级别的登录状态判断
router.beforeEach((to, from, next) => {
let shouldLogin = to.meta.shouldLogin
let isLogin = store.getters.isLogin
if (shouldLogin && !isLogin) {
store.dispatch('setSignPopup', 'signin')
next({
path: from.fullPath,
query: {redirect: to.fullPath}
})
} else {
next()
}
})
// 在根节点绑定一个捕获的click事件,全局处理
// <div id="app" @click.capture="shouldCheckLoigin">
// 以及在需要检测的元素上添加一个自定义属性 data-shouldLogin="true"
shouldCheckLoigin(e) { // 全局的检查页面中需要登录才能执行的操作,如关注、收藏、评论按钮等
let shouldLogin = e.target.getAttribute('data-shouldLogin') === 'true'
if (shouldLogin && !this.isLogin) {
// 弹窗并阻止事件传播
this.setSignPopup('signin')
e.stopPropagation()
}
},
使用keep-alive时的数据获取
// 检测浏览器的前进后退
// 因为使用了keep-alive,会缓存组件在内存中,重新进入时可以在activated中获取数据
// 但是前进后退时我不想获取数据,点击时才需要
// 通过提交mutations来保存是否是前进后退
// 原本是打算在app.vue根组件的created绑定popstate事件,但是vue-router内部也绑定了popstate事件,也就是说vue-router绑定的事件会先执行
// 执行顺序就会是这样 vue-router的popstate ==> 根组件watch $route ==> 子组件的activated ==> 我绑定的popstate,子组件的activated都已经执行了,那我提交的mutations就没有意义了
// 所以需要在new Router({}) 前绑定
window.addEventListener('popstate', () => {
store.commit('SET_POP_STATE', true)
})
// 进入子组件是会根据isPopState判断是否需要重新获取数据,intoPageCount判断是否是首次进入组件,初始为0
activated() {
if (this.isPopState || !this.intoPageCount++) return
// 获取数据
...
}
// 执行完activated应该重置isPopState,不然点击的时候isPopState还是true
// 通过在app.vue根组件watch $route
// 因为$route先于子组件的activated执行,通过setTimeout设成异步执行,就可以在activated后执行了
watch: {
'$route'() {
if (this.isPopState) {
// 在同步代码后执行,即组件的activated后面,等待activated判断完成,重置isPopState
setTimeout(() => {
this.setPopState(false)
}, 0)
}
}
}
密码处理
// 使用bcrypt加密,简单的封装成promise的方式使用
exports.getBcrypt = function(password, saltRounds = 10) {
return new Promise((resolve, reject) => {
bcrypt.genSalt(saltRounds, (err, salt) => {
if (err) {
return reject(err)
}
bcrypt.hash(password, salt, (err, hash) => {
if (err) {
return reject(err)
}
resolve(hash)
})
})
})
}
// 验证
exports.bcryptCompare = function(password, hash) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, hash, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
}
封装了上拉加载数据和时间格式化的mixin,简单的处理了一下
// 上拉加载
export const pullupMixin = {
data() {
return {
pullupFunc: null
}
},
methods: {
pullup(fn) {
this.pullupFunc = debounce(() => {
let scorllToBottom = checkScrollToBottom()
scorllToBottom && fn()
})
window.addEventListener('scroll', this.pullupFunc)
}
},
activated() {
this.pullupFunc && window.addEventListener('scroll', this.pullupFunc)
},
deactivated() {
this.pullupFunc && window.removeEventListener('scroll', this.pullupFunc)
},
beforeDestroy() {
// 解绑事件
this.pullupFunc && window.removeEventListener('scroll', this.pullupFunc)
}
}
// 格式化时间成 刚刚、一分钟...
export const timeFormatMixin = {
methods: {
timeFormat(list) {
return list.map((item) => {
item.time = timeFormat(item.created_at)
return item
})
}
}
}
// 格式化代码
const timeFormatArr = [0, 60, 3600, 86400, 2592000, 946080000, Number.MAX_VALUE]
const timeUnit = ['刚刚', '分钟前', '小时前', '天前', '月前', '年前']
export function timeFormat(dateStr) {
let dateTime = new Date(dateStr).getTime()
let now = new Date().getTime()
let time = (now - dateTime) / 1000
let index = timeFormatArr.findIndex((item, index) => {
return item <= time && timeFormatArr[index + 1] > time
})
if (index === 0) {
return timeUnit[0]
}
time = time / timeFormatArr[index] | 0
return time + timeUnit[index]
}
文章抓取
// 文章列表获取是比较方便的,主要还是文章获取那里,以下是主要代码
// token也是会过期的,要是失败了,token等参数等可自行替换
// 获取一篇文章
function getPostById(postId, type = 'entryView') {
let url = 'https://post-storage-api-ms.juejin.im/v1/getDetailData'
let data = Object.assign({}, commonConfig, {
postId,
type
})
return axios.get(url, {
params: data
})
.then(({data}) => {
return toMarkdown(data.d.content)
})
.catch((err) => {
// 错误处理
})
}
// 以async await + promise的方式,一篇一篇文章的获取
await new Promise((resolve, reject) => {
(async function next(i, len) {
if (i < len) {
let post = postList[i]
let content = await getPostById(post.postId)
if (typeof content === 'string') {
post.content = content
}
await sleep(100)
next(++i, len)
} else {
resolve()
}
})(0, postList.length)
})
Build Setup
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
For a detailed explanation on how things work, check out the guide and docs for vue-loader.
excel单元格中存在文字和图片,如何解析成json或者数组?
node里面的node-xlsx模块好像不行,图片自动丢失,只会读取文本内容,难道是我使用的方式不对吗?
enumerable:True传入数据后,新增的不可被枚举出来。
在社区也学了一段时间,等在成长点多点反辅社区,做点输出。
有一个问题是这样的javascript enumerable:True可以被枚举
,但根据下面的代码,发现不可被枚举。但是可通过
javascript person.hp
被找到。
//JSON对象
var person = {
name: "chendamao",
work: "Tester",
age: 35,
school:"jialidun"
}
var keys =Object.keys(person);//获取对象属性的数组
console.log(keys);
var value=[];
var len=keys.length
for(var i=0;i<len;i++){
var key =keys[i];
console.log(keys[i])//==>获取key
console.log(person[key]);//==>获取value person数组[key]拿到value
value[i]=person[keys[i]];//赋予1个变量
console.log('person --',key,":",value[i]);//遍历
}
console.log(person['name']) //console.log(person.name)
//enumerable:true可以被遍历
Object.defineProperty(person,"hp",{value:1500,enumerable:true}); //绑定hp和参数,但不和person共内存
console.log(person['hp']); //person.hp
console.log(keys) //没有遍历出来
this,call和apply(这三个东西,如何牢牢记住)
这三个东西虽然一直再用,也用的很顺手,知道它的用法,也知道它的区别,但是最近在攻克设计模式这个高地时总感觉缺点什么,没得办法,就只好重新学习一下。并总结了些许个人心得,分享给大家。
this
跟别的语言不太一样,JavaScript的this总是指向一个对象,而具体指向那个对象又是基于函数的执行环境(有人理解为上下文)动态绑定的,不是函数被声明的环境,而是函数被引用的环境。
this指向
这个在‘度娘’上一搜文章多的是,但是存在这样的问题,要么是总结的不全,要么是谈的是自己的理解,一个用途的总结就能写上几段不适用,没得办法只好翻墙Google一下,找到几篇不错的文章,文章链接请转到文章结尾参考资料部分。
个人感觉Google搜索出来的学习资料价值更高,国内的搜索引擎厂商估计都把精力用在如何赚用户的钱上了。
我们知道this指向对象,所以相对来说它的含义就比较丰富,它可以是全局对象,当前对象,或者任意对象,这完全取决于函数的调用方式。JavaScript中函数的调用有以下几种方式。
- 作为普通函数调用(全局对象)
- 作为对象的方法调用(当前对象)
- 作为构造函数调用(任意对象)
- Function.prototype.apply或Function.prototype.call的调用(任意对象)
1.作为对象的方法调用
window.name = 'globalName';
var myObj = {
name: 'localName',
getName: function(){
return this.name;
}
};
myObj.getName(); // localName
2.作为普通函数调用
window.name = 'globalName';
var getName = function(){
return this.name;
};
getName(); // globalName
我们将对象的方法调用赋值给一个变量,这样将它转化为普通的方法,就会出现下面的结果
window.name = 'globalName';
var myObj = {
name: 'localName',
getName: function(){
return this.name;
}
};
var getName = myObj.getName;
getName(); // globalName
相信大家都遇到过这样的情况,我们在操作dom节点时,一个局部的callback方法被当作不同函数使用时callback中的this指向window,而在实际的开发中,我们希望的是它能够指向window,我们往往这样做。
html
<div id="div">this is a div</div>
js
window.id = 'window';
document.getElementById('div').onclick = function(){
var that = this; // 保存 div 的引用
// 被转换为不同函数的callback
var callback = function() {
alert ( that.id ); // 'div'
}
// 调用callback
callback();
};
3.作为构造函数调用
众所周知,JavaScript时一门面向对象的语言,但与其他的面向对象编程语言不同,它并没有类(class)这个感念,取而代之的是基于原型(prototype)的继承方式。JavaScript的构造函数也很特别,如果没有new关键字,就和不同函数没有区别,在在开发的过程中构造函数的名字以大写字母开头,这里有些人注意到了,有些人没有注意到,或者注意到了也不知道为什么,虽然你写成小写也一样使用,但是这恰恰说明你的业余,在这里大写首字母就是为了协作开发,统一标准,同时也是再提醒调用者使用正确的方式调用,this才会绑定到新创建的对象上。
大部分的JavaScript函数都可以被当作构造器来使用。构造器的外表和普通函数一样,区别在于被调用的方式(new)。
我们应该注意构造函数经new调用后,如果不指定返回对象类型,那么默认返回this,如果指定了,就返回该指定的对象类型。返回指定类型对象的情况,我相信写过插件的童鞋都应该有或多或少的了解。
var MyClass = function(){
this.name = 'lisi';
// 显式地返回一个对象
return {
name: 'lzb'
}
};
var obj = new MyClass();
obj.name; // lzb
注:显示的返回的数据类型需保证是对象,其他的类型没有用。
apply/call的调用
相信大家对JavaScript有了解的同学都知道‘JavaScript中一切皆对象’这句话。因此,函数也是对象,是对象就有方法,而apply和cal就是函数对象的方法,这两个方法很强大,它可以实现切换函数的执行环境(上下文),也即是可以改变this绑定的对象,这样的方法被广泛应用。
function MyObj() {
this.name = 'lisi';
this.getName = function(){
return this.name;
}
};
var obj = new MyObj();
var obj2 = {name: 'lzb'}
// 将this指向的obj改为obj2
obj.getName.apply(obj2); // lzb
殊途同归
其实,作为开发人员的我不喜欢这样的总结,这不是原理的理解,而是把凡是所有涉及到this的使用进行了分类总结,看过Understanding JavaScript Function Invocation and “this”文章后有点小收获,作者将apply/call作为函数调用的基本方式,其它的3种方式都是在这一基础上演变而来的,也就是我们常说的语法糖。
函数调用过程就是this的绑定过程,这4种情况都要完成这样一个绑定,不同的是作为一般函数调用时,this绑定的是全局对象;作为对象的方法调用时,this绑定到该方法所属的对象。
找不到this
我们都见过这样的实现通过document.getElementById(‘div’)获得元素对象,但是我们又嫌它使用起来太麻烦,所以,我们会将他进行封装用一个短函数来代替,如下所示:
var getId = function(id) {
return document.getElementById(id);
};
getId('div');
但是,有没有人这样用如下的方式实现过:
var getId = document.getElementById;
getId('div');
我想在没有接触第一种实现方法之前,有人肯定这样想过,这个方法肯定有人用过,不过之后还是使用了第一种,很明显这种方法实现是不可行,学过以上的知识我们知道document.getElementById 方法的内部实现要用到this,而在第二种中getId作为一个普通函数它的this指向window,所以getELementById中this指向window,但是我们知道getElementById方法是document对象的方法,方法要想正常使用应该指向document,而第二种方法指向了window,所以,第二种方法有问题,但是我们可以使用apply来修正,把document当作this传入getId,代码如下所示:
document.getElementById = (function(fn) {
return function () {
return fn.apply(document. arguments)
}
})(document.getElementById);
var getId = document.getElementById;
getId('div');
这个可以参考dojo中的lang.hitch实现
button.onclick = lang.hitch(myObject, myObject.handler);
call 和 apply
在函数式编程风格的代码编写中,call和apply被广泛应用,在JavaScript的设计模式中这两个方法也是应用广泛。
call vs apply
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带有下表的集合(它可以是数组,也可以是类数组),apply把集合中的元素作为参数传递给被调用的函数。
call接受的参数不固定,第一个参数与apply作用一样,剩余的参数传递给被调用的函数。
当一个函数被调用是,JavaScript的解释器并不会计较形参和实参在数量、类型、顺序上的差别,在js的参数内部是使用一个数组来标识的(apply的使用效率比call要高)。
在我们使用call/apply的时候如果第一个参数为null,函数体内的this指向全局对象,但是在严格模式下返回的还是null。
可以在控制台输入如下代码:
var func = function(a) {
console.log(this === window)
}
func.apply(null) // true
// 严格模式
var func = function(a) {
"use strict";
console.log(this === null)
}
func.apply(null) // true
call/apply 的用途
通过上面的例子我们已经知道了他们的第一个用途就是改变函数内部的this的指向
我们在做html的交互工作时,所写的js都要求功能和实现的分离。
var oDiv = document.getElementById('div');
oDiv.onClick = function() {
func.apply(this)
};
function alertId() {
alert(this.id)
}
Function.prototype.bind()
这个方法自大多数高版本浏览器中都实现了,它的作用就是用来指定函数内部的this指向,虽然存在兼容性,但是在我们知道了call/apply 的作用之后,我们可以自己写一个,代码如下:
Function.prototype.bind = function (context) {
var that = this; // 保存原函数的this
return function() {
return that.apply(context, arguments) // 执行函数前会将context传入函数体内当作this
}
};
var obj = {
name: 'lzb'
};
var getName = function() {
console.log(this.name)
}.bind(obj);
getName(); // lzb
在这里我么们还可以向传递其他参数,代码修改如下:
Function.prototype.bind = function() {
var that = this;
var context = [].shift.apply(arguments);
var args = [].slice.apply(arguments);
return function() {
return that.apply( context, [].concat.apply( [].slice.apply( arguments ), args) )
}
}
var obj = {
name: 'lzb'
};
var setName = function(name) {
this.name = name;
}.bind(obj, 'xxx'); // 设置setName 的默认参数
var getName = function() {
console.log(this.name)
}.bind(obj);
setName(); // 默认为 xxx
// setName('snalv');
getName(); // sanlv
这里我们可以用bind实现更多更复杂的操作,这里就不一一介绍了。 相信到这里你应该对this,call/apply有了深刻的理解吧。
参考资料
Understanding the “this” keyword in JavaScript
Understanding JavaScript Function Invocation and “this”
文章后续更新请关注我的GitHub
前端的未来走向在哪里?
现在,成为一个前端要求门槛有点高,不像后端那样你只用熟悉一两个框架就行了,而前端不是,再加上现在前端的变化太快、太多,作为一个刚入行又有想法的前端发现前端的水比较深呀,各位大神有什么好的建议。万望指教!
koa 请求不返回
为什么我这请求不返回
exports.signup = async (ctx)=>{
const data = ctx.request.body;
const user = await User.save(data);
console.log(user)
ctx.response.body = {
success:true,
data:'user'
};
}
如果我注释掉 const user = await User.save(data); 这一行就能正确返回?