实现 Vue reactive 响应式
// Dep module
class Dep {static stack = []
static target = null
deps = null
constructor() {this.deps = new Set()
}
depend() {if (Dep.target) {this.deps.add(Dep.target)
}
}
notify() {this.deps.forEach(w => w.update())
}
static pushTarget(t) {if (this.target) {this.stack.push(this.target)
}
this.target = t
}
static popTarget() {this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {if (o && typeof o === 'object') {Object.keys(o).forEach(k => {defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {let dep = new Dep()
Object.defineProperty(obj, k, {get() {dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()}
})
if (val && typeof val === 'object') {reactive(val)
}
}
// watcher
class Watcher {constructor(effect) {
this.effect = effect
this.update()}
update() {Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 测试代码
const data = reactive({msg: 'aaa'})
new Watcher(() => {console.log('===> effect', data.msg);
})
setTimeout(() => {data.msg = 'hello'}, 1000)
应用 Promise 封装 AJAX 申请
// promise 封装实现:function getJSON(url) {
// 创立一个 promise 对象
let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
// 新建一个 http 申请
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
// 当申请胜利或失败时,扭转 promise 的状态
if (this.status === 200) {resolve(this.response);
} else {reject(new Error(this.statusText));
}
};
// 设置谬误监听函数
xhr.onerror = function() {reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置申请头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 申请
xhr.send(null);
});
return promise;
}
实现数组元素求和
- arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
- arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) {if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21
JSONP
script 标签不遵循同源协定,能够用来进行 跨域申请,长处就是兼容性好但仅限于 GET 申请
const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
let dataSrc = '';
for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
}
return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {resolve(data);
document.removeChild(scriptEle);
}
})
}
实现一个双向绑定
defineProperty 版本
// 数据
const data = {text: 'default'};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变动 --> 批改视图
set(newVal) {
input.value = newVal;
span.innerHTML = newVal;
}
});
// 视图更改 --> 数据变动
input.addEventListener('keyup', function(e) {data.text = e.target.value;});
proxy 版本
// 数据
const data = {text: 'default'};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {set(target, key, value) {target[key] = value;
// 数据变动 --> 批改视图
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data, handler);
// 视图更改 --> 数据变动
input.addEventListener('keyup', function(e) {proxy.text = e.target.value;});
实现一个拖拽
<style>
html, body {
margin: 0;
height: 100%;
}
#box {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
top: 100px;
left: 100px;
}
</style>
<div id="box"></div>
window.onload = function () {var box = document.getElementById('box');
box.onmousedown = function (ev) {
var oEvent = ev || window.event; // 兼容火狐, 火狐下没有 window.event
var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区右边的间隔 - box 到页面右边的间隔
var distanceY = oEvent.clientY - box.offsetTop;
document.onmousemove = function (ev) {
var oEvent = ev || window.event;
var left = oEvent.clientX - distanceX;
var top = oEvent.clientY - distanceY;
if (left <= 0) {left = 0;} else if (left >= document.documentElement.clientWidth - box.offsetWidth) {left = document.documentElement.clientWidth - box.offsetWidth;}
if (top <= 0) {top = 0;} else if (top >= document.documentElement.clientHeight - box.offsetHeight) {top = document.documentElement.clientHeight - box.offsetHeight;}
box.style.left = left + 'px';
box.style.top = top + 'px';
}
box.onmouseup = function () {
document.onmousemove = null;
box.onmouseup = null;
}
}
}
参考 前端进阶面试题具体解答
小孩报数问题
有 30 个小孩儿,编号从 1 -30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈,而后下一个小孩 从新报数 1、2、3,问最初剩下的那个小孩儿的编号是多少?
function childNum(num, count){let allplayer = [];
for(let i = 0; i < num; i++){allplayer[i] = i + 1;
}
let exitCount = 0; // 来到人数
let counter = 0; // 记录报数
let curIndex = 0; // 以后下标
while(exitCount < num - 1){if(allplayer[curIndex] !== 0) counter++;
if(counter == count){allplayer[curIndex] = 0;
counter = 0;
exitCount++;
}
curIndex++;
if(curIndex == num){curIndex = 0};
}
for(i = 0; i < num; i++){if(allplayer[i] !== 0){return allplayer[i]
}
}
}
childNum(30, 3)
实现 Ajax
步骤
- 创立
XMLHttpRequest
实例 - 收回 HTTP 申请
- 服务器返回 XML 格局的字符串
- JS 解析 XML,并更新部分页面
- 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。
理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。
实现 Array.isArray 办法
Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};
console.log(Array.myIsArray([])); // true
实现 async/await
剖析
// generator 生成器 生成迭代器 iterator
// 默认这样写的类数组是不能被迭代的,短少迭代办法
let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}
// // 应用迭代器使得能够开展数组
// // Symbol 有很多元编程办法,能够改 js 自身性能
// likeArray[Symbol.iterator] = function () {// // 迭代器是一个对象 对象中有 next 办法 每次调用 next 都须要返回一个对象 {value,done}
// let index = 0
// return {// next: ()=>{
// // 会主动调用这个办法
// console.log('index',index)
// return {
// // this 指向 likeArray
// value: this[index],
// done: index++ === this.length
// }
// }
// }
// }
// let arr = [...likeArray]
// console.log('arr', arr)
// 应用生成器返回迭代器
// likeArray[Symbol.iterator] = function *() {
// let index = 0
// while (index != this.length) {// yield this[index++]
// }
// }
// let arr = [...likeArray]
// console.log('arr', arr)
// 生成器 碰到 yield 就会暂停
// function *read(params) {
// yield 1;
// yield 2;
// }
// 生成器返回的是迭代器
// let it = read()
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())
// 通过 generator 来优化 promise(promise 的毛病是不停的链式调用)const fs = require('fs')
const path = require('path')
// const co = require('co') // 帮咱们执行 generator
const promisify = fn=>{return (...args)=>{return new Promise((resolve,reject)=>{fn(...args, (err,data)=>{if(err) {reject(err)
}
resolve(data)
})
})
}
}
// promise 化
let asyncReadFile = promisify(fs.readFile)
function * read() {let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8')
let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8')
return content2
}
// 这样写太繁琐 须要借助 co 来实现
// let re = read()
// let {value,done} = re.next()
// value.then(data=>{
// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值
// let {value,done} = re.next(data)
// value.then(d=>{// let {value,done} = re.next(d)
// console.log(value,done)
// })
// }).catch(err=>{// re.throw(err) // 手动抛出谬误 能够被 try catch 捕捉
// })
// 实现 co 原理
function co(it) {// it 迭代器
return new Promise((resolve,reject)=>{
// 异步迭代 须要依据函数来实现
function next(data) {
// 递归得有停止条件
let {value,done} = it.next(data)
if(done) {resolve(value) // 间接让 promise 变成胜利 用以后返回的后果
} else {// Promise.resolve(value).then(data=>{// next(data)
// }).catch(err=>{// reject(err)
// })
// 简写
Promise.resolve(value).then(next,reject)
}
}
// 首次调用
next()})
}
co(read()).then(d=>{console.log(d)
}).catch(err=>{console.log(err,'--')
})
整体看一下构造
function asyncToGenerator(generatorFunc) {return function() {const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {function step(key, arg) {
let generatorResult
try {generatorResult = gen[key](arg)
} catch (error) {return reject(error)
}
const {value, done} = generatorResult
if (done) {return resolve(value)
} else {return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
}
}
step("next")
})
}
}
剖析
function asyncToGenerator(generatorFunc) {
// 返回的是一个新的函数
return function() {
// 先调用 generator 函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, arguments)
// 返回一个 promise 因为内部是用.then 的形式 或者 await 的形式去应用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 外部定义一个 step 函数 用来一步一步的跨过 yield 的妨碍
// key 有 next 和 throw 两种取值,别离对应了 gen 的 next 和 throw 办法
// arg 参数则是用来把 promise resolve 进去的值交给下一个 yield
function step(key, arg) {
let generatorResult
// 这个办法须要包裹在 try catch 中
// 如果报错了 就把 promise 给 reject 掉 内部通过.catch 能够获取到谬误
try {generatorResult = gen[key](arg)
} catch (error) {return reject(error)
}
// gen.next() 失去的后果是一个 { value, done} 的构造
const {value, done} = generatorResult
if (done) {
// 如果曾经实现了 就间接 resolve 这个 promise
// 这个 done 是在最初一次调用 next 后才会为 true
// 以本文的例子来说 此时的后果是 {done: true, value: 'success'}
// 这个 value 也就是 generator 函数最初的返回值
return resolve(value)
} else {// 除了最初完结的时候外,每次调用 gen.next()
// 其实是返回 {value: Promise, done: false} 的构造,// 这里要留神的是 Promise.resolve 能够承受一个 promise 为参数
// 并且这个 promise 参数被 resolve 的时候,这个 then 才会被调用
return Promise.resolve(
// 这个 value 对应的是 yield 前面的 promise
value
).then(
// value 这个 promise 被 resove 的时候,就会执行 next
// 并且只有 done 不是 true 的时候 就会递归的往下解开 promise
// 对应 gen.next().value.then(value => {// gen.next(value).value.then(value2 => {// gen.next()
//
// // 此时 done 为 true 了 整个 promise 被 resolve 了
// // 最内部的 test().then(res => console.log(res))的 then 就开始执行了
// })
// })
function onResolve(val) {step("next", val)
},
// 如果 promise 被 reject 了 就再次进入 step 函数
// 不同的是,这次的 try catch 中调用的是 gen.throw(err)
// 那么天然就被 catch 到 而后把 promise 给 reject 掉啦
function onReject(err) {step("throw", err)
},
)
}
}
step("next")
})
}
}
Object.is
Object.is
解决的次要是这两个问题:
+0 === -0 // true
NaN === NaN // false
const is= (x, y) => {if (x === y) {
// + 0 和 - 0 应该不相等
return x !== 0 || y !== 0 || 1/x === 1/y;
} else {return x !== x && y !== y;}
}
滚动加载
原理就是监听页面滚动事件,剖析 clientHeight、scrollTop、scrollHeight三者的属性关系。
window.addEventListener('scroll', function() {
const clientHeight = document.documentElement.clientHeight;
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
if (clientHeight + scrollTop >= scrollHeight) {
// 检测到滚动至页面底部,进行后续操作
// ...
}
}, false);
Promise
// 模仿实现 Promise
// Promise 利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定
// 2. 返回值穿透
// 3. 谬误冒泡
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已胜利
const REJECTED = 'REJECTED'; // 已失败
class Promise {constructor(exector) {
// 初始化状态
this.status = PENDING;
// 将胜利、失败后果放在 this 上,便于 then、catch 拜访
this.value = undefined;
this.reason = undefined;
// 胜利态回调函数队列
this.onFulfilledCallbacks = [];
// 失败态回调函数队列
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有进行中状态能力更改状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 胜利态函数顺次执行
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
}
const reject = reason => {
// 只有进行中状态能力更改状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 失败态函数顺次执行
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
try {
// 立刻执行 executor
// 把外部的 resolve 和 reject 传入 executor,用户可调用 resolve 和 reject
exector(resolve, reject);
} catch(e) {
// executor 执行出错,将谬误内容 reject 抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected :
reason => {throw new Error(reason instanceof Error ? reason.message : reason) }
// 保留 this
const self = this;
return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {
// try 捕捉谬误
try {
// 模仿微工作
setTimeout(() => {const result = onFulfilled(self.value);
// 分两种状况:// 1. 回调函数返回值是 Promise,执行 then 操作
// 2. 如果不是 Promise,调用新 Promise 的 resolve 函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {setTimeout(() => {const result = onRejected(self.reason);
// 不同点:此时是 reject
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
})
} else if (self.status === FULFILLED) {
try {setTimeout(() => {const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
});
} catch(e) {reject(e);
}
} else if (self.status === REJECTED) {
try {setTimeout(() => {const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
}
});
}
catch(onRejected) {return this.then(null, onRejected);
}
static resolve(value) {if (value instanceof Promise) {
// 如果是 Promise 实例,间接返回
return value;
} else {
// 如果不是 Promise 实例,返回一个新的 Promise 对象,状态为 FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {return new Promise((resolve, reject) => {reject(reason);
})
}
static all(promiseArr) {
const len = promiseArr.length;
const values = new Array(len);
// 记录曾经胜利执行的 promise 个数
let count = 0;
return new Promise((resolve, reject) => {for (let i = 0; i < len; i++) {// Promise.resolve()解决,确保每一个都是 promise 实例
Promise.resolve(promiseArr[i]).then(
val => {values[i] = val;
count++;
// 如果全副执行完,返回 promise 的状态就能够扭转了
if (count === len) resolve(values);
},
err => reject(err),
);
}
})
}
static race(promiseArr) {return new Promise((resolve, reject) => {
promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),
err => reject(err),
)
})
})
}
}
实现类的继承
实现类的继承 - 简版
类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。
// 寄生组合继承
function Parent(name) {this.name = name}
Parent.prototype.say = function() {console.log(this.name + ` say`);
}
Parent.prototype.play = function() {console.log(this.name + ` play`);
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.name = name
}
/**
1. 这一步不必 Child.prototype = Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类
2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性
3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(this.name + ` say`);
}
// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;
// 测试
var parent = new Parent('parent');
parent.say()
var child = new Child('child');
child.say()
child.play(); // 继承父类的办法
ES5 实现继承 - 具体
第一种形式是借助 call 实现继承
function Parent1(){this.name = 'parent1';}
function Child1(){Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);
这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法
第二种形式借助原型链实现继承:
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3]
}
function Child2() {this.type = 'child2';}
Child2.prototype = new Parent2();
console.log(new Child2());
看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
明明我只扭转了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象
第三种形式:将前两种组合:
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。然而这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(
Child3.prototype = new Parent3()
;)。这是咱们不愿看到的。那么如何解决这个问题?
第四种形式: 组合继承的优化 1
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4() {Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下
var s3 = new Child4();
var s4 = new Child4();
console.log(s3)
子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。
第五种形式(最举荐应用):优化 2
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
这是最举荐的一种形式,靠近完满的继承。
实现简略路由
// hash 路由
class Route{constructor(){
// 路由存储对象
this.routes = {}
// 以后 hash
this.currentHash = ''
// 绑定 this,防止监听时 this 指向扭转
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()}
}
实现一下 hash 路由
根底的 html
代码:
<html>
<style>
html, body {
margin: 0;
height: 100%;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
}
.box {
width: 100%;
height: 100%;
background-color: red;
}
</style>
<body>
<ul>
<li>
<a href="#red"> 红色 </a>
</li>
<li>
<a href="#green"> 绿色 </a>
</li>
<li>
<a href="#purple"> 紫色 </a>
</li>
</ul>
</body>
</html>
简略实现:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
window.onhashchange = function (e) {const color = hash.slice(1)
box.style.background = color
}
</script>
封装成一个 class:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
class HashRouter {constructor (hashStr, cb) {
this.hashStr = hashStr
this.cb = cb
this.watchHash()
this.watch = this.watchHash.bind(this)
window.addEventListener('hashchange', this.watch)
}
watchHash () {let hash = window.location.hash.slice(1)
this.hashStr = hash
this.cb(hash)
}
}
new HashRouter('red', (color) => {box.style.background = color})
</script>
手写常见排序
冒泡排序
冒泡排序的原理如下,从第一个元素开始,把以后元素和下一个索引元素进行比拟。如果以后元素大,那么就替换地位,反复操作直到比拟到最初一个元素,那么此时最初一个元素就是该数组中最大的数。下一轮反复以上操作,然而此时最初一个元素曾经是最大数了,所以不须要再比拟最初一个元素,只须要比拟到
length - 1
的地位。
function bubbleSort(list) {
var n = list.length;
if (!n) return [];
for (var i = 0; i < n; i++) {
// 留神这里须要 n - i - 1
for (var j = 0; j < n - i - 1; j++) {if (list[j] > list[j + 1]) {var temp = list[j + 1];
list[j + 1] = list[j];
list[j] = temp;
}
}
}
return list;
}
疾速排序
快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值比照大小。比基准值小的放数组右边,大的放左边,比照实现后将基准值和第一个比基准值大的值替换地位。而后将数组以基准值的地位分为两局部,持续递归以上操作
ffunction quickSort(arr) {if (arr.length<=1){return arr;}
var baseIndex = Math.floor(arr.length/2);// 向下取整,选取基准点
var base = arr.splice(baseIndex,1)[0];// 取出基准点的值,// splice 通过删除或替换现有元素或者原地增加新的元素来批改数组, 并以数组模式返回被批改的内容。此办法会扭转原数组。// slice 办法返回一个新的数组对象, 不会更改原数组
// 这里不能间接 base=arr[baseIndex], 因为 base 代表的每次都删除的那个数
var left=[];
var right=[];
for (var i = 0; i<arr.length; i++){
// 这里的 length 是变动的,因为 splice 会扭转原数组。if (arr[i] < base){left.push(arr[i]);// 比基准点小的放在右边数组,}
}else{right.push(arr[i]);// 比基准点大的放在左边数组,}
return quickSort(left).concat([base],quickSort(right));
}
抉择排序
function selectSort(arr) {
// 缓存数组长度
const len = arr.length;
// 定义 minIndex,缓存以后区间最小值的索引,留神是索引
let minIndex;
// i 是以后排序区间的终点
for (let i = 0; i < len - 1; i++) {
// 初始化 minIndex 为以后区间第一个元素
minIndex = i;
// i、j 别离定义以后区间的上下界,i 是左边界,j 是右边界
for (let j = i; j < len; j++) {
// 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j
if (arr[j] < arr[minIndex]) {minIndex = j;}
}
// 如果 minIndex 对应元素不是目前的头部元素,则替换两者
if (minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
// console.log(selectSort([3, 6, 2, 4, 1]));
插入排序
function insertSort(arr) {for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
实现节流函数(throttle)
防抖函数原理: 规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {if (!flag) return;
flag = false;
setTimeout(() => {fn.apply(this, args);
flag = true;
}, delay);
};
};
实用场景:
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器 resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
实现模板字符串解析性能
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的构造
}
return template; // 如果模板没有模板字符串间接返回
}
reduce 用法汇总
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);
/*
total: 必须。初始值, 或者计算完结后的返回值。currentValue:必须。以后元素。currentIndex:可选。以后元素的索引;arr:可选。以后元素所属的数组对象。initialValue: 可选。传递给函数的初始值,相当于 total 的初始值。*/
reduceRight()
该办法用法与reduce()
其实是雷同的,只是遍历的程序相同,它是从数组的最初一项开始,向前遍历到第一项
1. 数组求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num, 10); // 以 10 为初始值求和
// 对象数组求和
var result = [{ subject: 'math', score: 88},
{subject: 'chinese', score: 95},
{subject: 'english', score: 80}
];
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0);
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除 10 分
2. 数组最大值
const a = [23,123,342,12];
const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
3. 数组转对象
var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];
var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
4. 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
var res = arr.reduce((x, y) => x.concat(y), []);
5. 数组去重
实现的基本原理如下:① 初始化一个空数组
② 将须要去重解决的数组中的第 1 项在初始化数组中查找,如果找不到(空数组中必定找不到),就将该项增加到初始化数组中
③ 将须要去重解决的数组中的第 2 项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中
④ ……
⑤ 将须要去重解决的数组中的第 n 项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中
⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) {prev.indexOf(cur) === -1 && prev.push(cur);
return prev;
},[]);
6. 对象数组去重
const dedup = (data, getKey = () => {}) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)
if (!pre[key]) {pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}
7. 求字符串中字母呈现的次数
const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{pre[next] ? pre[next]++ : pre[next] = 1
return pre
},{})
// 后果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 1
8. compose 函数
redux compose
源码实现
function compose(...funs) {if (funs.length === 0) {return arg => arg;}
if (funs.length === 1) {return funs[0];
}
return funs.reduce((a, b) => (...arg) => a(b(...arg)))
}