Open CodingMeUp opened 7 years ago
原理:
快速排序也是分治法思想的一种实现,他的思路是使数组中的每个元素与基准值(Pivot,通常是数组的首个值,A[0])比较,数组中比基准值小的放在基准值的左边,形成左部;大的放在右边,形成右部;接下来将左部和右部分别递归地执行上面的过程:选基准值,小的放在左边,大的放在右边。。。直到排序结束。
步骤:
let qsort = (list) => {
if( list.length === 0 ){
return []
}
let [x, ...xs] = list
return [...qsort(xs.filter(u=>u<=x)), x, ...qsort(xs.filter(u=>u>x))]
}
let list = [345345,3422,123,66,-2,444]
list = qsort(list)
list.forEach( i=> { console.log(i)}) // 从小到大 若 从那大小 就上面u x比较替换下顺序
//babel 转换后
"use strict";
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
var qsort = function qsort(list) {
if (list.length === 0) {
return [];
}
var _list = _toArray(list),
x = _list[0],
xs = _list.slice(1);
return [].concat(_toConsumableArray(qsort(xs.filter(function (u) {
return u <= x;
}))), [x], _toConsumableArray(qsort(xs.filter(function (u) {
return u > x;
}))));
};
var list = [345345, 3422, 123, 66, -2, 444];
list = qsort(list);
list.forEach(function (i) {
console.log(i);
});
方式1
var container = document.getElementById('list');
for (var i = 0; i < 3; i++) {
var item = document.createElement('li');
item.innerText = i + 1;
container.appendChild(item);
}
方式2
var container = document.getElementById('list');
var html = [];
for (var i = 0; i < 3; i++) {
html.push('<li>' + (i + 1) + '</li>');
}
container.innerHTML = html.join('');
这种问题 要做到以下加分 0-变量命名:节点类的变量,加上 nd 前缀,会更加容易辨识,当然,也有同学习惯借用 jquery 中的 $,关于变量命名的更多内容可以去阅读《可读代码的艺术》; 1-选择符命名:给 CSS 用和 JS 用的选择符分开,给 JS 用的选择符建议加上 js- 或 J- 前缀,提高可读性,还有没有其他好处,请思考; 2-容错能力:应该对节点的存在性做检查,这样代码才能更健壮,实际工作中,很可能你的这段代码会把其他功能搞砸,因为单个地方 JS 报错是可能导致后续代码不执行的,为啥要这样做?不理解的同学可以去看看防御性编程; 3-最小作用域原则:应该把代码段包在声明即执行的函数表达式(IIFE)里,不产生全局变量,也避免变量名冲突的风险,这是维护遗留代码必须谨记的。
(() => {
var ndContainer = document.getElementById('js-list');
if (!ndContainer) {
return;
}
for (var i = 0; i < 3; i++) {
var ndItem = document.createElement('li');
ndItem.innerText = i + 1;
// alert 出来的内容其实都是 3,而不是每个 <li> 的文本内容。上面两段代码都不能满足需求,因为 i 和 ndItem 的作用域范围是相同的。使用 ES6 的块级作用域能把问题解决( let i = 0 .... ):
// onclick addeventListen区别可以 转另外的事件类型
// ndItem.addEventListener('click', function () {
alert(i);
});
ndContainer.appendChild(ndItem);
}
})();
ndContainer.addEventListener('click', function (e) {
const target = e.target;
if (target.tagName === 'LI') {
alert(target.innerHTML);
}
});
另外页面也不太流畅 也会有卡顿感、好在现代浏览器提供了 requestAnimationFrame API 来解决非常耗时的代码段对渲染的阻塞问题,该技术在 React 和 Angular 里面都有使用,如果你理解了 requestAnimationFrame 的原理,就很容易理解最新的 React Fiber 算法。 综合上面的分析,可以从减少 DOM 操作次数、缩短循环时间两个方面减少主线程阻塞的时间。减少 DOM 操作次数的良方是 DocumentFragment;而缩短循环时间则需要考虑使用分治的思想把 10000 个 LI标签 分批次插入到页面中,每次插入的时机是在页面重新渲染之前。由于 requestAnimationFrame 并不是所有的浏览器都支持,Paul Irish 给出了对应的 polyfill,这个 Gist 也非常值得你学习。
(() => {
const ndContainer = document.getElementById('js-list');
if (!ndContainer) {
return;
}
const total = 30000;
const batchSize = 4; // 每批插入的节点次数,越大越卡
const batchCount = total / batchSize; // 需要批量处理多少次
let batchDone = 0; // 已经完成的批处理个数
function appendItems() {
const fragment = document.createDocumentFragment();
for (let i = 0; i < batchSize; i++) {
const ndItem = document.createElement('li');
ndItem.innerText = (batchDone * batchSize) + i + 1;
fragment.appendChild(ndItem);
}
// 每次批处理只修改 1 次 DOM
ndContainer.appendChild(fragment);
batchDone += 1;
doBatchAppend();
}
function doBatchAppend() {
if (batchDone < batchCount) {
window.requestAnimationFrame(appendItems);
}
}
// kickoff
doBatchAppend();
ndContainer.addEventListener('click', function (e) {
const target = e.target;
if (target.tagName === 'LI') {
alert(target.innerHTML);
}
});
})();
let printInfo = (node) => {
console.log(node.tagName, '.' + node.className)
function dfs (node) {
printInfo(node)
if(node.children.length){
Array.from(node.children).forEach(function (el) {
dfs(el)
})
}
}
dfs(root)
const traverse = (ndRoot) => {
const queue = [ndRoot];
while (queue.length) {
const node = queue.shift();
printInfo(node);
if (!node.children.length) {
continue;
}
Array.from(node.children).forEach(x => queue.push(x)); }
};
// kickoff
traverse(document.querySelector('.root'));
最优的数组去重算法是采用Map数据结构实现的算法 https://juejin.im/post/5b0284ac51882542ad774c45#heading-4
Array.prototype.unique = function () {
var res = [];
var json = {};
for (var i = 0; i < this.length; i++) {
// if (!json[this[i]])
if ( !json[this[i].obj ? this[i].obj._id : ''] ) {
res.push(this[i]);
json[this[i].obj._id] = 'diff';
}
}
return res;
}
Array.prototype.unique = function () {
const tmp = new Map();
return this.filter(item => {
return !tmp.has(item) && tmp.set(item, 1);
})
}
let S4 = () => {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
if(res.items && res.items.length > 0){
res.items.map(
(item)=>{
item.uuid = S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()
}
)
}
使用crypto API生成符合RFC4122版本4的UUID。
const uuid = _ =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
// uuid() -> '7982fcfe-5721-4632-bede-6000885be57d'
'use strict'
var Browser = require('nd-browser')
module.exports = {
download: function (options) {
var data = options.data
var type = options.type
var name = options.name
if (typeof data === 'string') {
console.log(1)
data = [data]
}
var blob = new Blob(data, {
type: type
})
if (Browser.browser === 'IE') {
window.navigator.msSaveBlob(blob, name)
} else {
var anc = document.createElement('a')
anc.href = window.URL.createObjectURL(blob)
anc.setAttribute('download', name)
anc.style.display = 'none'
anc.click()
anc.remove()
}
}
}
blob.download({
data: [res],
name: '导出日志' + new Date().getTime() + '.xlsx', // 本来是要xlsx的配arraybuffer 需求变更为csv
// name: '导出日志' + new Date().getTime() + '.csv',
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
if (typeof keyData === 'object') {
let content = JSON.stringify(keyData, null, 2)
blob.download({data: content, name: 'tenant.json', type: 'text/json'})
}
/**
* @module Browser
* @author lzhengms <lzhengms@gmail.com>
*/
'use strict';
module.exports = (function(navigator) {
var userAgent = navigator.userAgent,
temp,
match = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [],
match2 = userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|IEMobile/);
if (/trident/i.test(match[1])) {
temp = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
return {
browser: 'IE',
version: temp[1] || ''
};
}
if (match[1] === 'Chrome') {
temp = userAgent.match(/\bOPR\/(\d+)/);
if (temp !== null) {
return {
browser: 'Opera',
version: temp[1] || ''
};
}
}
// 针对 Edge
if (match[1] === 'Chrome') {
temp = userAgent.match(/\bedge\/(\d+)/i);
if (temp !== null) {
return {
browser: 'IE',
version: temp[1] || ''
};
}
}
match = match[2] ? [match[1], match[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((temp = userAgent.match(/version\/(\d+)/i)) !== null) {
match.splice(1, 1, temp[1]);
}
return {
browser: match[0] === 'MSIE' ? 'IE' : match[0],
version: match[1],
mobile: match2 && match2[0]
};
})(navigator);
前端时间,在共享平台开发者门户做前端开发时,有个需求:服务端要基于新框架进行接口改造,原来旧接口返回的JSON数据使用的是驼峰式命名法,格式如下:
buildType : 2
chineseName : "部署测试"
createTime : 1472489455000
creator : 339691
demandAnalysisUrl : "应用描述33"
desc : "应用描述22"
designAnalysisUrl : "应用描述44"
members : [
{
yourName : 'li',
yourAge : 23
}
]
现在新改版的v2.0接口,由于基于新的框架,JSON数据使用的是连字符命名法,格式如下:
build_type : 2
chinese_name : "部署测试"
create_time : 1472489455000
creator : 339691
demand_analysisUrl : "应用描述33"
desc : "应用描述22"
design_analysisUrl : "应用描述44"
members : [
{
your_name : 'li',
your_nge : 23
}
]
因此,Web端也要进行相应的修改。为了使改动量最小,需要实现一个适配函数,新接口返回结果经过该函数处理后,得到驼峰式命名的JSON数据,对该函数的要求如下:
也许以后的开发中,有同学也会遇到类似问题,需要实现类似功能,故在这里分享下该函数的实现源码:
/**
* 将对象的变量名由连字符式转为驼峰式,支持对象的深度遍历转换
* @param obj JSON对象
* @return JSON 驼峰式的JSON对象
*/
function obj2CamelCased(obj){
if (!(obj instanceof Object)) {
return obj;
}
var newObj = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) { //推荐在 for in 时,总是使用 hasOwnProperty 进行判断,没人可以保证运行的代码环境是否被污染过。
var camelProp = prop.replace(/_([a-z])/g, function (g) {
return g[1].toUpperCase();
});
if (obj[prop] instanceof Array) { //值为数组的处理
newObj[camelProp] = [];
var oldArray = obj[prop];
for (var k = 0, kLen = oldArray.length; k < kLen; k++) {
newObj[camelProp].push(arguments.callee(oldArray[k]));
}
} else if (obj[prop] instanceof Object) { //值为对象的处理
newObj[camelProp] = arguments.callee(obj[prop]);
} else { //值为字符串,或数字等的处理
newObj[camelProp] = obj[prop];
}
}
}
return newObj;
}
前一段时间的一个案子是开发一个有声课件,大致就是通过导入文档、图片等资源后,页面变为类似 PPT 的布局,然后选中一张图片,可以插入音频,有单页编辑和全局编辑两种模式。其中音频的导入方式有两种,一种是从资源库中导入,还有一种就是要提到的录音。
说实话,一开始都没接触过 HTML5 的 Audio API,而且要基于在我们接手前的代码中进行优化。当然其中也踩了不少坑,这次也会围绕这几个坑来说说感受(会省略一些基本对象的初始化和获取,因为这些内容不是这次的重点,有兴趣的同学可以自行查找 MDN 上的文档):
开始录音前,要先获取当前设备是否支持 Audio API。早期的方法 navigator.getUserMedia
已经被 navigator.mediaDevices.getUserMedia
所代替。正常来说现在大部分的现代浏览器都已经支持navigator.mediaDevices.getUserMedia
的用法了,当然MDN
上也给出了兼容性的写法
const promisifiedOldGUM = function (constraints) {
// First get ahold of getUserMedia, if present
const getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia)
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
// Older browsers might not implement mediaDevices at all, so we set an empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {}
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = promisifiedOldGUM
}
因为这个方法是异步的,所以我们可以对无法兼容的设备进行友好的提示
navigator
.mediaDevices
.getUserMedia(constraints)
.then(function (mediaStream) {
// 成功
}, function (error) {
// 失败
const { name } = error
let errorMessage
switch (name) {
// 用户拒绝
case 'NotAllowedError':
case 'PermissionDeniedError':
errorMessage = '用户已禁止网页调用录音设备'
break
// 没接入录音设备
case 'NotFoundError':
case 'DevicesNotFoundError':
errorMessage = '录音设备未找到'
break
// 其它错误
case 'NotSupportedError':
errorMessage = '不支持录音功能'
break
default:
errorMessage = '录音调用错误'
window.console.log(error)
}
return errorMessage
})
一切顺利的话,我们就可以进入下一步了。
(这里有对获取上下文的方法进行了省略,因为这不是这次的重点)
这里有个比较特别的点,就是需要添加一个中间变量来标识是否当前是否在录音。因为在火狐浏览器上,我们发现一个问题,录音的流程都是正常的,但是点击暂停时却发现怎么也暂停不了,我们当时是使用 disconnect
方法。这种方式是不行的,这种方法是需要断开所有的连接才可以。后来发现,应该增加一个中间变量 this.isRecording
来判断当前是否正在录音,当点击开始时,将其设置为true
,暂停时将其设置为false
。
当我们开始录音时,会有一个录音监听的事件 onaudioprocess
,如果返回 true
则会将流写入,如果返回 false
则不会将其写入。因此判断this.isRecording
,如果为 false
则直接 return
// 一些初始化
const audioContext = new AudioContext()
const sourceNode = audioContext.createMediaStreamSource(mediaStream)
const scriptNode = audioContext.createScriptProcessor(BUFFER_SIZE, INPUT_CHANNELS_NUM, OUPUT_CHANNELS_NUM)
sourceNode.connect(this.scriptNode)
scriptNode.connect(this.audioContext.destination)
// 监听录音的过程
scriptNode.onaudioprocess = event => {
if (!this.isRecording) return // 判断是否正则录音
this.buffers.push(event.inputBuffer.getChannelData(0)) // 获取当前频道的数据,并写入数组
}
当然这里也会有个坑,就是无法再使用,自带获取当前录音时长的方法了,因为实际上并不是真正的暂停,而是没有将流写入罢了。于是我们还需要获取一下当前录音的时长,需要通过一个公式进行获取
const getDuration = () => {
return (4096 * this.buffers.length) / this.audioContext.sampleRate // 4096为一个流的长度,sampleRate 为采样率
}
这样就能够获取正确的录音时长了。
结束录音的方式,我采用的是先暂停,之后需要试听或者其它的操作先执行,然后再将存储流的数组长度置为0。
getVoiceSize = analyser => {
const dataArray = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(dataArray)
const data = dataArray.slice(100, 1000)
const sum = data.reduce((a, b) => a + b)
return sum
}
具体可以参考https://developer.mozilla.org/zh-CN/docs/Web/API/AnalyserNode/frequencyBinCount
HTTPS
才允许使用这次遇到的大部分问题都是兼容性的问题,因此在上面踩了不少坑,尤其是移动端的问题,一开始还有出现因为获取录音时长写法错误的问题,导致直接卡死的情况。这次的经历也弥补了 HTML5 API上的一些空白,当然最重要的还是要提醒一下大家,这种原生的 API 文档还是直接查看 MDN 来的简单粗暴!
使用递归。对于给定字符串中的每个字母,为字母创建字谜。使用map()将字母与每部分字谜组合,然后使用reduce()将所有字谜组合到一个数组中,最基本情况是字符串长度等于2或1。
const anagrams = str => {
if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
return str.split('').reduce((acc, letter, i) =>
acc.concat(anagrams(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)), []);
};
// anagrams('abc') -> ['abc','acb','bac','bca','cab','cba']
使用reduce()将每个值添加到累加器,初始值为0,总和除以数组长度。
const average = arr => arr.reduce((acc, val) => acc + val, 0) / arr.length;
// average([1,2,3]) -> 2
使用replace()匹配每个单词的第一个字符,并使用toUpperCase()来将其大写。
const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
// capitalizeEveryWord('hello world!') -> 'Hello World!'
使用slice(0,1)和toUpperCase()大写第一个字母,slice(1)获取字符串的其余部分。 省略lowerRest参数以保持字符串的其余部分不变,或将其设置为true以转换为小写。(注意:这和上一个示例不是同一件事情)
const capitalize = (str, lowerRest = false) =>
str.slice(0, 1).toUpperCase() + (lowerRest ? str.slice(1).toLowerCase() : str.slice(1));
// capitalize('myName', true) -> 'Myname'
# 检查回文
将字符串转换为toLowerCase(),并使用replace()从中删除非字母的字符。然后,将其转换为tolowerCase(),将(’‘)拆分为单独字符,reverse(),join(’‘),与原始的非反转字符串进行比较,然后将其转换为tolowerCase()。
```js
const palindrome = str => {
const s = str.toLowerCase().replace(/[\W_]/g,'');
return s === s.split('').reverse().join('');
}
// palindrome('taco cat') -> true
每次遇到数组中的特定值时,使用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
使用递归。如果提供的参数(args)数量足够,则调用传递函数f,否则返回一个curried函数f。
const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
// curry(Math.pow)(2)(10) -> 1024
// curry(Math.min, 3)(10)(50)(2) -> 2
使用递归,使用reduce()来获取所有不是数组的元素,flatten每个元素都是数组。
const deepFlatten = arr =>
arr.reduce((a, v) => a.concat(Array.isArray(v) ? deepFlatten(v) : v), []);
// deepFlatten([1,[2],[[3],4],5]) -> [1,2,3,4,5]
从b创建一个Set,然后在a上使用Array.filter(),只保留b中不包含的值。
const difference = (a, b) => { const s = new Set(b); return a.filter(x => !s.has(x)); };
// difference([1,2,3], [1,2]) -> [3]
使用Math.hypot()计算两点之间的欧几里德距离。
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
// distance(1,1, 2,3) -> 2.23606797749979
使用replace()来转义特殊字符。
const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// escapeRegExp('(test)') -> \\(test\\)
使用递归。如果n小于或等于1,则返回1。否则返回n和n – 1的阶乘的乘积。
const factorial = n => n <= 1 ? 1 : n * factorial(n - 1);
// factorial(6) -> 720
创建一个特定长度的空数组,初始化前两个值(0和1)。使用Array.reduce()向数组中添加值,后面的一个数等于前面两个数相加之和(前两个除外)。
const fibonacci = n =>
Array(n).fill(0).reduce((acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i), []);
// fibonacci(5) -> [0,1,1,2,3]
将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]
使用reduce()来获取数组中的所有元素,并使用concat()来使它们flatten。
const flatten = arr => arr.reduce((a, v) => a.concat(v), []);
// flatten([1,[2],3,4]) -> [1,2,3,4]
使用Math.max()与spread运算符(…)结合得到数组中的最大值。
const arrayMax = arr => Math.max(...arr);
// arrayMax([10, 1, 5]) -> 10
如果已定义,请使用pageXOffset和pageYOffset,否则使用scrollLeft和scrollTop,可以省略el来使用window的默认值。
const getScrollPos = (el = window) =>
({x: (el.pageXOffset !== undefined) ? el.pageXOffset : el.scrollLeft,
y: (el.pageYOffset !== undefined) ? el.pageYOffset : el.scrollTop});
// getScrollPos() -> {x: 0, y: 200}
使用递归。基本情况是当y等于0时。在这种情况下,返回x。否则,返回y的GCD和x / y的其余部分。
const gcd = (x, y) => !y ? x : gcd(y, x % y);
// gcd (8, 36) -> 4
使用Array(end-start)创建所需长度的数组,使用map()来填充范围中的所需值,可以省略start使用默认值0。
const initializeArrayRange = (end, start = 0) =>
Array.apply(null, Array(end - start)).map((v, i) => i + start);
// initializeArrayRange(5) -> [0,1,2,3,4]
使用reduce()与map()结合来遍历元素,并将其组合成包含所有组合的数组。
const powerset = arr =>
arr.reduce((a, v) => a.concat(a.map(r => [v].concat(r))), [[]]);
// powerset([1,2]) -> [[], [1], [2], [2,1]]
使用Array(n)创建所需长度的数组,fill(v)以填充所需的值,可以忽略value使用默认值0。
const initializeArray = (n, value = 0) => Array(n).fill(value);
// initializeArray(5, 2) -> [2,2,2,2,2]
使用performance.now()获取函数的开始和结束时间,console.log()所花费的时间。第一个参数是函数名,随后的参数传递给函数。
const timeTaken = callback => {
console.time('timeTaken');
const r = callback();
console.timeEnd('timeTaken');
return r;
};
// timeTaken(() => Math.pow(2, 10)) -> 1024
// (logged): timeTaken: 0.02099609375ms
使用Array.reduce()来创建和组合键值对。
const objectFromPairs = arr => arr.reduce((a, v) => (a[v[0]] = v[1], a), {});
// objectFromPairs([['a',1],['b',2]]) -> {a: 1, b: 2}
使用Array.reduce()通过函数传递值。
const pipe = (...funcs) => arg => funcs.reduce((acc, func) => func(acc), arg);
// pipe(btoa, x => x.toUpperCase())("Test") -> "VGVZDA=="
使用Math.random()生成一个随机数并将其映射到所需的范围,使用Math.floor()使其成为一个整数。
const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
// randomIntegerInRange(0, 5) -> 2
# 范围内的随机数
使用Math.random()生成一个随机值,使用乘法将其映射到所需的范围。
```js
const randomInRange = (min, max) => Math.random() * (max - min) + min;
// randomInRange(2,10) -> 6.0211363285087005
使用sort()重新排序元素,利用Math.random()来随机排序。
const shuffle = arr => arr.sort(() => Math.random() - 0.5);
// shuffle([1,2,3]) -> [2,3,1]
使用window.location.href或window.location.replace()重定向到url。 传递第二个参数来模拟链接点击(true – default)或HTTP重定向(false)。
const redirect = (url, asLink = true) =>
asLink ? window.location.href = url : window.location.replace(url);
// redirect('https://google.com')
使用数组解构和Array.reverse()来颠倒字符串中的字符顺序。合并字符以使用join(‘‘)获取字符串。
const reverseString = str => [...str].reverse().join('');
// reverseString('foobar') -> 'raboof'
使用按位左移运算符(«)和toString(16),然后padStart(6,“0”)将给定的RGB参数转换为十六进制字符串以获得6位十六进制值。
const rgbToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
// rgbToHex(255, 165, 1) -> 'ffa501'
使用document.documentElement.scrollTop或document.body.scrollTop获取到顶部的距离。 从顶部滚动一小部分距离。使用window.requestAnimationFrame()来滚动。
const scrollToTop = _ => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
};
// scrollToTop()
使用Array.map()和Math.random()创建一个随机值的数组。使用Array.sort()根据随机值对原始数组的元素进行排序。
count shuffle = arr => {
let r = arr.map(Math.random);
return arr.sort((a, b) => r[a] - r[b]);
}
// shuffle([1, 2, 3] -> [2, 1, 3])
使用filter()移除不是values的一部分值,使用includes()确定。
const similarity = (arr, values) => arr.filter(v => values.includes(v));
// similarity([1,2,3], [1,2,4]) -> [1,2]
使用split(’‘)分割字符串,sort()使用localeCompare(),使用join(’‘)重新组合。
const sortCharactersInString = str =>
str.split('').sort((a, b) => a.localeCompare(b)).join('');
// sortCharactersInString('cabbage') -> 'aabbceg'
使用reduce()将每个值添加到累加器,初始化值为0。
const sum = arr => arr.reduce((acc, val) => acc + val, 0);
// sum([1,2,3,4]) -> 10
使用数组解构来交换两个变量之间的值。
[varA, varB] = [varB, varA];
// [x, y] = [y, x]
返回arr.slice(1)
const tail = arr => arr.length > 1 ? arr.slice(1) : arr;
// tail([1,2,3]) -> [2,3]
// tail([1]) -> [1]
使用ES6 Set和… rest操作符去掉所有重复值。
const unique = arr => [...new Set(arr)];
// unique([1,2,2,3,4,4,5]) -> [1,2,3,4,5]
使用match() 与适当的正则表达式来获得所有键值对,适当的map() 。使用Object.assign()和spread运算符(…)将所有键值对组合到一个对象中,将location.search作为参数传递给当前url。
const getUrlParameters = url =>
url.match(/([^?=&]+)(=([^&]*))/g).reduce(
(a, v) => (a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1), a), {}
);
// getUrlParameters('http://url.com/page?name=Adam&surname=Smith') -> {name: 'Adam', surname: 'Smith'}
使用!isNaN和parseFloat()来检查参数是否是一个数字,使用isFinite()来检查数字是否是有限的。
const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;
// validateNumber('10') -> true
//根据名字和年龄多元素筛选
export function multiFilter(array, filters) {
const filterKeys = Object.keys(filters)
// filters all elements passing the criteria
return array.filter((item) => {
// dynamically validate all filter criteria
return filterKeys.every(key => {
//ignore when the filter is empty Anne
if(!filters[key].length) return true
return !!~filters[key].indexOf(item[key])
})
})
}
/*
* 这段代码并非我原创,感兴趣的可以去原作者那里点个赞
* 作者是:@author https://gist.github.com/jherax
* 这段代码里我只加了一行,解决部分筛选条件清空时候整体筛选失效的问题
*/
var filters = {
name:['Leila', 'Jay'],
age:[]
}
/* 结果:
* [{name: "Leila", age: 16, gender: "female"},
* {name: "Jay", age: 19, gender: "male"}]
*/
/**
* 转换为二维数组
* @param nodes
* @param parentId
* @returns {Array}
*/
function toArray(nodes, parentId = '') {
if (!nodes) return []
var childKey = 'children'
var r = []
if (nodes instanceof Array) {
for (var item of nodes) {
var node = {}
for (var key in item) {
if (key != childKey) {
node[key] = item[key]
}
}
node['parent'] = parentId
r.push(node)
if (item[childKey]) {
r = r.concat(toArray(item[childKey], item.id))
}
}
} else {
r.push(nodes)
if (nodes[childKey]) {
r = r.concat(toArray(nodes[childKey]))
}
}
return r
}
/**
* 转换为树状结构
* @param data
* @param parent_id
* @returns {Array}
*/
// 线性数据转化为树。
function toTree(data, parent_id) {
var tree = []
var temp
for (var i = 0; i < data.length; i++) {
if (data[i].parent == parent_id || typeof data[i].parent == 'undefined') {
var obj = data[i]
temp = toTree(data, data[i].id)
if (temp.length > 0) {
obj.children = temp
}
tree.push(obj)
}
}
return tree
}
var treeData = [{
id: 'start',
shape: 'start',
children: [{
id: 'add',
shape: 'addNodeBtn',
children: [{
id: '2sdff',
shape: 'addNodeBtn'
}]
}]
}]
var n2a = toArray(treeData)
var a2n = toTree(n2a, '')
function Student(){
}
var student = new Student();
typeof Student === 'function' // true
student.constructor === Student;
Student.prototype.constructor === Student;
做了哪些事情呢
/**
* 模拟实现 new 操作符
* @param {Function} ctor [构造函数]
* @return {Object|Function|Regex|Date|Error} [返回结果]
* eg: var student = new Student() 替换为 newOperator(Student);
*/
function newOperator(ctor){
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
// ES6 new.target 是指向构造函数
newOperator.target = ctor;
// 1.创建一个全新的对象,
// 2.并且执行[[Prototype]]链接
// 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
var newObj = Object.create(ctor.prototype);
// ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments);
// 除去ctor构造函数的其余参数
var argsArr = [].slice.call(arguments, 1);
// 3.生成的新对象会绑定到函数调用的`this`。
// 获取到ctor函数返回结果
var ctorReturnResult = ctor.apply(newObj, argsArr);
// 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null
var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
var isFunction = typeof ctorReturnResult === 'function';
if(isObject || isFunction){
return ctorReturnResult;
}
// 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。
return newObj;
}
polyfill address https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
通过定义简单说一下call和apply方法,他们就是参数不同,作用基本相同。
直接看个简单的demo
var jawil = {
name: "jawil",
sayHello: function (age) {
console.log("hello, i am ", this.name + " " + age + " years old");
}
};
var lulin = {
name: "lulin",
};
jawil.sayHello(24);
// hello, i am jawil 24 years old
然后看看使用apply和call之后的输出:
jawil.sayHello.call(lulin, 24);// hello, i am lulin 24 years old
jawil.sayHello.apply(lulin, [24]);// hello, i am lulin 24 years old
结果都相同。从写法上我们就能看出二者之间的异同。相同之处在于,第一个参数都是要绑定的上下文,后面的参数是要传递给调用该方法的函数的。不同之处在于,call方法传递给调用函数的参数是逐个列出的,而apply则是要写在数组中。
总结一句话介绍call和apply
上面代码,我们注意到了两点:
这里默认大家都对this有一个基本的了解,知道什么时候this该指向谁,我们结合这两句话来分析这个通用函数:f.apply or call (o),我们直接看一本书对其中原理的解读,具体什么书,我也不知道,参数我们先不管,先了解其中的大致原理。
f.call(o)其原理就是先通过 o.m = f 将 f作为o的某个临时属性m存储,然后执行m,执行完毕后将m属性删除。
// 第一步
lulin.fn = jawil.sayHello
// 第二步
lulin.fn()
// 第三步
delete lulin.fn
Function.prototype.applyFour = function(context) {
var context = context || window
var args = arguments[1] //获取传入的数组参数
var fn = Symbol()
context[fn] = this //假想context对象预先不存在名为fn的属性
if (args == void 0) { //没有传入参数直接执行
return context[fn]()
}
var fnStr = 'context[fn]('
for (var i = 0; i < args.length; i++) {
//得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行
fnStr += i == args.length - 1 ? args[i] : args[i] + ','
}
fnStr += ')'
var returnValue = eval(fnStr) //还是eval强大
delete context[fn] //执行完毕之后删除这个属性
return returnValue
}
上面 参数改一下
return this.applyFour(([].shift.applyFour(arguments), arguments)
jawil.sayHello.bind(lulin)(24); //hello, i am lulin 24 years old
jawil.sayHello.bind(lulin)([24]); //hello, i am lulin 24 years old
bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象。(比如在回调中传入这个方法。)如果不做特殊处理的话,一般会丢失原来的对象。从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决这个问题
用一句话总结bind的用法:该方法创建一个新函数,称为绑定函数,绑定函数会以创建它时传入bind方法的第一个参数作为this,传入bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
//简单模拟bind函数
Function.prototype.bind = Function.prototype.bind || function (context) {
if(typeof this !== 'function'){
throw new TypeError(this + ' must be a function');
}
var me = this;
var args = Array.prototype.slice.callOne(arguments, 1);
// bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”。
var F = function () {};
F.prototype = this.prototype;
var bound = function () {
var innerArgs = Array.prototype.slice.callOne(arguments);
var finalArgs = args.concat(innerArgs);
return me.applyFour(this instanceof F ? this : context || this, finalArgs);
}
bound.prototype = new F();
return bound;
}
paserInt