防抖和节流
防抖:
你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行!
//防抖函数:抖动停止后的时间超过设定的时间时执行一次函数--某固定时间内最多执行一次
function debounce(func, delay) {
var timeout;
//返回的函数在一个抖动结束后的delay毫秒内执行func函数
return function() {
var context = this, args = arguments; //保存函数调用时的上下文和参数
clearTimeout(timeout); //触发func前清除定时器
timeout = setTimeout(function() {
func.apply(context, args); //如果不这么做,this指向为window,参数为undefined
}, delay); //用户停止某个连续动作delay毫秒后执行func
};
}
function realfun(){
console.log(111)
}
window.addEventListener("resize",debounce(realfun,1000))
防抖优化:
我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。因此加个 immediate 参数判断是否是立刻执行。
// 防抖优化
function debounce(func, delay, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, delay)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, delay);
}
}
}
window.addEventListener("resize", debounce(realFunc, 500));
window.addEventListener("resize", debounce(realFunc, 500,true));
节流:
每隔一段时间执行一次,目的是降低频率
使用时间戳:
使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
// 使用时间戳
function throttle(func, wait) {
var context, args;
var previous = 0;
return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
使用定时器
// 使用定时器
function throttle(func, wait) {
var timeout;
return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
function realfun(){
console.log(111)
}
window.addEventListener("resize", throttle(realFunc, 500));
比较两个方法: 1. 使用时间戳会立刻执行,使用定时器会在 n 秒后第一次执行 2. 使用时间戳停止触发后没有办法再执行事件,使用定时器停止触发后依然会再执行一次事件
双剑合璧:
我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!
// 双剑合璧版
function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
//简写版
function throttle(func, delay, mustRun) {
var timeout;
var starttime = new Date(); //起始的时间
return function() {
var context = this, args = arguments;
var curtime = new Date(); //当前时间
clearTimeout(timeout);
//如果达到规定的触发时间间隔,触发func
if(curtime - starttime >= mustRun) {
func.apply(context, args);
starttime = curtime;
}
//没有达到触发时间,重新设定计时器
else {
timeout = setTimeout(func, delay);
}
}
}
数组去重
对于
var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];
//优化后的键值对方法
//但是JSON.stringify 任何一个正则表达式的结果都是 {},所以这个方法并不适用于处理正则表达式去重
//因此先处理正则
var array = [1,1,'1','1',NaN,NaN,undefined,undefined,null,null,/a/,/a/,/b/,{value: '1'},{value: 1}, {value: 1}, {name: 2},{value: 2}];
function unique(array) {
//提取正则到arr
let arr=[]
for(let a of array){
if(a instanceof RegExp){
arr.push(a)
}
}
//去重正则
var o={}
arr=arr.filter(function(item, index, array){
return o.hasOwnProperty(typeof item + item) ? false : (o[typeof item + item] = true)
})
//去重除正则外的(去重后会含第一个正则)
var obj = {};
array=array.filter(function(item, index, array){
//return obj.hasOwnProperty(item) ? false : (obj[item] = true) //此做法无法区分1和‘1’
//return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) // 无法区分{value: 1} 和 {value: 2},因为 typeof item + item 的结果都会是 object[object Object]
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
//拼接(去除第一个已有的正则)
return array.concat(arr.slice(1))
}
console.log(unique(array)); // [1,,'1',NaN,undefined,null,/a/,{value: '1'},{value: 1},{name: 2},{value: 2},/b/];
事件委托
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<script type="text/javascript">
// 补全代码
//此处onclick不能写成onClick
document.querySelector('ul').onclick=function(e){
//兼容
e=e||window.e //注意是||不是|
if(e.target.nodeName.toLowerCase()==='li'){ //必须有.toLowerCase()
e.target.innerHTML+='.'
}
}
</script>
</body>
</html>
比较版本号
var compareVersion = function(version1, version2) {
const v1=version1.split('.')
const v2=version2.split('.')
for(let i=0;i<v1.length || i<v2.length;i++){
//需要另外设置x y来保存未定义时令其为0
let x = 0, y = 0;
if (i < v1.length) {
x = parseInt(v1[i]);
}
if (i < v2.length) {
y = parseInt(v2[i]);
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
return 0
};
//另一种写法
var compare = (version1,version2) =>{
const newversion1=`${version1}`.split('.').length>=3?`${version1}`:`${version1}`.concat('.0')
const newversion2=`${version2}`.split('.').length>=3?`${version2}`:`${version2}`.concat('.0')
var res1=newversion1.split('.')
var res2=newversion2.split('.')
for(let i=0;i<res1.length;i++){
//+号是为了字符串转数字
if(+res1[i]<+res2[i]){
return -1
}else if(+res1[i]>+res2[i]){
return 1
}
if(i===res1.length-1 && +res1[i]===+res2[i]){
return 0
}
}
}
console.log(compare('0.1', '1.1.1')) //-1
console.log(compare('13.37', '1.2 ')) //1
console.log(compare('1.1', '1.1.0')) //0
function compareVersion(version1, version2) {
const newVersion1 = `${version1}`.split('.').length < 3 ? `${version1}`.concat('.0') : `${version1}`;
const newVersion2 = `${version2}`.split('.').length < 3 ? `${version2}`.concat('.0') : `${version2}`;
//计算版本号大小,转化大小
function toNum(a){
const c = a.toString().split('.'); //[0,1,0]
const num_place = ["", "0", "00", "000", "0000"],//设x=4
r = num_place.reverse();
for (let i = 0; i < c.length; i++){
const len=c[i].length;
c[i]=r[len]+c[i]; //拼串
}
return c.join(''); //0000 0001 0000
}
//检测版本号是否需要更新
function checkPlugin(a, b) {
const numA = toNum(a);
const numB = toNum(b);
return numA > numB ? 1 : numA < numB ? -1 : 0;
}
return checkPlugin(newVersion1 ,newVersion2);
}
compareVersion('0.1', '1.1.1'); // -1 000000010000 000100010001
compareVersion('13.37', '1.2 '); // 1
compareVersion('1.1', '1.1.0'); // 0
生成随机字符串
var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
function generateMixed(n) {
var res = "";
for(var i = 0; i < n ; i ++) {
var id = Math.ceil(Math.random()*35);
res += chars[id];
}
return res;
}
or
<script language="javascript">
function randomString(len) {
len = len || 32;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
var maxPos = $chars.length;
var pwd = '';
for (i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
document.write(randomString(32));
</script>
下划线与驼峰的转换
// 下划线转换驼峰
function toHump(name) {
return name.replace(/_(\w)/g, function(all, letter){ //letter为()里的内容
return letter.toUpperCase();
});
}
// 驼峰转换下划线
function toLine(name) {
return name.replace(/([A-Z])/g,"_$1").toLowerCase(); //$1指以()为组,每一个()对应一个$
}
// 测试
let a = 'a_b2_345_c2345';
console.log(toHump(a)); //aB2345C2345
let b = 'aBdaNf';
console.log(toLine(b)); //a_bda_nf
金钱格式化
/*
*Array.prototype.reverse() Array→String
*Array.prototype.join() Array→String
*String.prototype.split() String→Array
*String.prototype.match() String→Array
*/
function transferToMoney(money) {
if(money && money != null) {
money = String(money);
var left = money.split('.')[0], right = money.split('.')[1];
right = right ? (right.length >= 2 ? '.' + right.substr(0, 2) : '.' + right + '0') : '.00';
var tmp = left.split('').reverse().join('').match(/\d{1,3}/g);
left = tmp.join(',').split('').reverse().join('');
return (Number(money) < 0 ? '-' : '') + left + right;
}
else if(money === 0) {
return '0.00';
}
else {
return '';
}
}
合法的url
//仅验证是否是http(s)
const _isUrl = url => {
// 补全代码
//正则 /^(http:\/\/|https:\/\/)?([\w-]+.)+[\w-]+(/[\w- ./?%&=]*)?/
return /^((http|https)://)?(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+).)+([A-Za-z]+)(:\d+)?(/.*)?(?.*)?(#.*)?$/.test(url)
}
//完整版
function isURL(str_url) {// 验证url
var strRegex = "^((https|http|ftp|rtsp|mms)?://)"
+ "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" // ftp 的user@
+ "(([0-9]{1,3}.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184
+ "|" // 允许IP和DOMAIN(域名)
+ "([0-9a-z_!~*'()-]+.)*" // 域名- www.
+ "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]." // 二级域名
+ "[a-z]{2,6})" // first level domain- .com or .museum
+ "(:[0-9]{1,4})?" // 端口- :80
+ "((/?)|" // a slash isn't required if there is no file name
+ "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
var re = new RegExp(strRegex);
return re.test(str_url);
}
数组的扁平化
reduce
利用reduce需要递归,有一定的性能消耗
//利用reduce实现
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7]],{name:'jack',age:[12,34,23]}]
function flatten(arr) {
return arr.reduce(function (prev, next) {
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]
扩展运算符
通过扩展运算符确实可以避免递归,但是却要使用到循环,如果数组层级过高,循环的消耗也不小
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7]],{name:'jack',age:[12,34,23]}]
//利用扩展运算符
function flatten(arr) {
var arr;
while (arr.some(v => Array.isArray(v))) {
arr = [].concat(...arr); //一次... 数组降一维
console.log(arr)
}
return arr;
}
console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]
正则
正则处理不了 对象里又含数组的情况
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{age:23}]
function flatten(arr) {
let str = JSON.stringify(arr).replace(/[|]/g, '');
return JSON.parse(Array.of('[' + str + ']')[0]);
//return JSON.parse(`[${JSON.stringify(arr).replace(/[|]/g,'')}]`);
//JSON.stringify使数组序列化为字符串,否则对象会是object object
//JSON.parse将数据转换为 JavaScript 对象
//Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型
}
console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{age:23}]
flat
兼容性不好
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{name:'jack',age:[12,34,23]}]
console.log(array.flat(Infinity)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]
generator
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{name:'jack',age:[12,34,23]}]
function* flatten(arr) {
if (!Array.isArray(arr)) yield arr;
else for (let el of arr) yield* flatten(el);
}
let flattened = [...flatten(array)]
console.log(flattened) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]
函数柯里化
柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。
// 第一版
var curry = function (fn) {
var args = [].slice.call(arguments, 1);
return function() {
var newArgs = args.concat([].slice.call(arguments));
return fn.apply(this, newArgs);
};
};
// 第二版
function sub_curry(fn) {
var args = [].slice.call(arguments, 1);
return function() {
return fn.apply(this, args.concat([].slice.call(arguments)));
};
}
function curry(fn, length) {
//如果传入的参数不够,就继续执行 curry 函数接收参数,如果参数达到个数,就执行柯里化了的函数。
length = length || fn.length;
var slice = Array.prototype.slice;
return function() {
if (arguments.length < length) {
var combined = [fn].concat(slice.call(arguments));
return curry(sub_curry.apply(this, combined), length - arguments.length);
} else {
return fn.apply(this, arguments);
}
};
}
偏函数
偏函数则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。
如经常需要实现1+n的功能,则可以把1固定,以后只传n
// 第二版
var _ = {};
function partial(fn) {
var args = [].slice.call(arguments, 1);
return function() {
var position = 0, len = args.length;
for(var i = 0; i < len; i++) {
args[i] = args[i] === _ ? arguments[position++] : args[i]
}
while(position < arguments.length) args.push(arguments[position++]);
return fn.apply(this, args);
};
};
惰性函数
我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。
接下来的使用方式都不会发生改变的时候,想想是否可以考虑使用惰性函数。
//利用闭包可以解决变量污染全局的问题,但是解决不了每次调用都要进行一次判断
var foo = (function() {
var t;
return function() {
if (t) return t;
t = new Date();
return t;
}
})();
//惰性函数就是解决每次都要进行判断的这个问题,就是重写函数
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
};
应用
// 为了兼容现代浏览器和 IE 浏览器,
function addEvent (type, el, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if(window.attachEvent){
el.attachEvent('on' + type, fn);
}
}
//不想要每次都判断,而是只判断一次,可使用惰性函数
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
addEvent(type, el, fn)
}
//或者闭包
var addEvent = (function(){
if (window.addEventListener) {
return function (type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent){
return function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
})();
函数组合
function compose() {
var args = arguments;
var start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
//从右到左
while (i--) result = args[i].call(this, result);
return result;
};
};
函数记忆
// 第一版 (来自《JavaScript权威指南》)
function memoize(f) {
var cache = {};
return function(){
var key = arguments.length + Array.prototype.join.call(arguments, ",");
if (key in cache) {
return cache[key]
}
else {
return cache[key] = f.apply(this, arguments)
}
}
}
// 第二版 (来自 underscore 的实现)
var memoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!cache[address]) {
cache[address] = func.apply(this, arguments);
}
return cache[address];
};
memoize.cache = {};
return memoize;
};
应用
斐波那契,斐波那契的调用次数会大大减少
尾递归
采用与不采用尾递归的区别:
不采用:执行上下文栈会有很多上下文
采用:上一个执行上下文先pop,下一个才会push
function fibonacci(n){
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(5)) // 1 1 2 3 5
function factorial(n, res) {
if (n == 1) return res;
return factorial(n - 1, n * res)
}
console.log(factorial(4, 1)) // 24
数组乱序
遍历数组元素,然后将当前元素与以后随机位置的元素进行交换
function shuffle(a) {
for (let i = a.length; i; i--) {
//Math.random()生成的随机数,是可以包括0,但是不包括1!
let j = Math.floor(Math.random() * i);
[a[i - 1], a[j]] = [a[j], a[i - 1]];
}
return a;
}
var a=[1,2,3]
shuffle(a)
//demo
var times = 100000;
var res = {};
for (var i = 0; i < times; i++) {
var arr = shuffle([1, 2, 3]);
var key = JSON.stringify(arr);
res[key] ? res[key]++ : res[key] = 1;
}
// 为了方便展示,转换成百分比
for (var key in res) {
res[key] = res[key] / times * 100 + '%'
}
console.log(res)
数组实现栈
function Stack(){
this.items=[]
Stack.prototype.push=function(val){
this.items.push(val)
}
Stack.prototype.pop=function(){
return this.items.pop()
}
Stack.prototype.size=function(){
return this.items.length
}
Stack.prototype.peek=function(){
return this.items[this.items.length-1]
}
Stack.prototype.isEmpty=function(){
return this.items.length==0
}
// 将栈结构的内容以字符形式返回toString()
Stack.prototype.toString = function(){
var str = '';
for(var i =0;i<this.items.length;i++){
str += this.items[i] + ' ';
}
return str;
}
}
实现两栏布局
左边固定,右边自适应
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
display: flex;
}
.left{
width: 300px;
height: 100%;
position: fixed;
background-color: red;
}
.main{
position: absolute;
right:0;
height:1800px;
}
</style>
</head>
<body>
<div class="box">
<div class="left">left</div>
<div class="main">main</div>
</div>
</body>
</html>
实现三栏布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
display: flex;
min-height: 100vh;
}
.left{
width: 300px;
background-color: red;
}
.right{
width: 300px;
background-color: yellow;
}
.main{
flex:1;
//overflow:hidden;
}
</style>
</head>
<body>
<div class="box">
<div class="left">left</div>
<div class="main">main</div>
<div class="right">right</div>
</div>
</body>
</html>
图片懒加载
方案一:clientHeight、scrollTop 和 offsetTop
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<img src="Go.png" data-src="./lifecycle.jpeg" alt="">
<script>
let num = document.getElementsByTagName('img').length;
let img = document.getElementsByTagName("img");
let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); //页面载入完毕加载可是区域内的图片
window.onscroll = lazyload;
function lazyload() { //监听页面滚动事件
let seeHeight = document.documentElement.clientHeight; //可见区域高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (let i = n; i < num; i++) {
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "Go.png") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
</script>
</body>
</html>
方案二:getBoundingClientRect
图片上边距离到可视区上边的距离(可为负数) < 浏览器可视高度: element.getBoundingClientRect().top < clientHeight
现在我们用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 getBoundingClientRect API。
上述的 lazyload 函数改成下面这样:
function lazyload() {
for(let i = count; i <num; i++) {
// 元素现在已经出现在视口中
if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
if(img[i].getAttribute("src") !== "default.jpg") continue;
img[i].src = img[i].getAttribute("data-src");
count ++;
}
}
}
方案三: IntersectionObserver
目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1 ,完全不可见时小于等于 0
这是浏览器内置的一个API,实现了监听window的scroll事件、判断是否在视口中以及节流三大功能。
我们来具体试一把:
let img = document.getElementsByTagName("img");
const observer = new IntersectionObserver(changes => {
//changes 是被观察的元素集合
for(let i = 0, len = changes.length; i < len; i++) {
let change = changes[i];
// 通过这个属性判断是否在视口中
if(change.isIntersecting) {
const imgElement = change.target;
imgElement.src = imgElement.getAttribute("data-src");
observer.unobserve(imgElement);
}
}
})
Array.from(img).forEach(item => observer.observe(item));
优化
通过以下css可以提高性能
# 之所以使用visibility而不是display是因为
# visibility不会触发重绘(repaint)和重排(reflow)
img {
visibility: hidden;
}
img[src] {
visibility: visible;
}
因为scroll事件的触发频率很高,频繁操作dom结点会造成很大的性能问题,所以需要做节流和防抖设计,减少scroll事件的触发频率
浅拷贝
自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。
- Object.assign()
const _shallowClone = target => {
// 补全代码
//Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
return Object.assign({},target);
}
注意点:
- 它不会拷贝对象的继承属性;
- 它不会拷贝对象的不可枚举的属性;
- 可以拷贝 Symbol 类型的属性。
-
扩展运算符
和使用Object.assign功能相同,其注意事项也相同,两者在使用上基本是可以相互转换
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
- Array.prototype.concat()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
- Array.prototype.slice()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
- 手动
//手动
const _shallowClone = target => {
if(typeof target === 'object' && target !== null) {
const constructor = target.constructor
if(/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return target
const cloneTarget = Array.isArray(target) ? [] : {}
for(prop in target) {
//for...in会遍历到原型链,所以此处用hasOwnProperty判断一下是不是自身拥有
if(target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop]
}
}
return cloneTarget
} else {
// 基础类型 直接返回
return target
}
}
深拷贝
- lodash.cloneDeep()
const _ = require('lodash');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
- jQuery.extend()
const $ = require('jquery');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
- JSON.parse()
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
console.log(new_arr);
注意:
但是这种方式存在弊端,会忽略`undefined`、`symbol`和`函数`
- 手动
//简易版
//1. 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中]
//2. 无需考虑循环引用问题
const _sampleDeepClone = target => {
if(typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {}
for(prop in target) {
if(target.hasOwnProperty(prop)) {
cloneTarget[prop] = _sampleDeepClone(target[prop])
}
}
return cloneTarget
} else {
return target
}
}
//完整版
//1. 需要考虑函数、正则、日期、ES6新对象
//2. 需要考虑循环引用问题
const _completeDeepClone = (target, map = new Map()) => {
// 补全代码
//参数如果为空
if(target===null) return target;
//参数如果不是对象类型,而是基本数据类型
if(typeof target!=='object') return target;
//参数为其他数据类型
const cons = target.constructor;
if(/^(Function|RegExp|Date|Map|Set)$/i.test(cons)) return new cons(target);
const cloneTarget = Array.isArray(target)? []:{};
//如果存在循环引用,直接返回当前循环引用的值,否则,将其加入map,
if(map.get(target)) return map.get(target);
map.set(target,cloneTarget);
for(let key in target){
cloneTarget[key] = _completeDeepClone(target[key],map)
}
return cloneTarget;
}
判断两个数相等
var toString = Object.prototype.toString;
function isFunction(obj) {
return toString.call(obj) === '[object Function]'
}
function eq(a, b, aStack, bStack) {
// === 结果为 true 的区别出 +0 和 -0
if (a === b) return a !== 0 || 1 / a === 1 / b;
// typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
if (a == null || b == null) return false;
// 判断 NaN
if (a !== a) return b !== b;
// 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
// 更复杂的对象使用 deepEq 函数进行深度比较
return deepEq(a, b, aStack, bStack);
};
function deepEq(a, b, aStack, bStack) {
// a 和 b 的内部属性 [[class]] 相同时 返回 true
var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
}
var areArrays = className === '[object Array]';
// 不是数组
if (!areArrays) {
// 过滤掉两个函数的情况
if (typeof a != 'object' || typeof b != 'object') return false;
var aCtor = a.constructor,
bCtor = b.constructor;
// aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
return false;
}
}
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
// 检查是否有循环引用的部分
while (length--) {
if (aStack[length] === a) {
return bStack[length] === b;
}
}
aStack.push(a);
bStack.push(b);
// 数组判断
if (areArrays) {
length = a.length;
if (length !== b.length) return false;
while (length--) {
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
}
// 对象判断
else {
var keys = Object.keys(a),
key;
length = keys.length;
if (Object.keys(b).length !== length) return false;
while (length--) {
key = keys[length];
if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
aStack.pop();
bStack.pop();
return true;
}
console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false
console.log(eq(NaN, NaN)); // true
console.log(eq(Number(NaN), Number(NaN))); // true
console.log(eq('Curly', new String('Curly'))); // true
console.log(eq([1], [1])); // true
console.log(eq({ value: 1 }, { value: 1 })); // true
var a, b;
a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;
console.log(eq(a, b)) // true
call
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
Function.prototype._call=function(obj,...args){
//obj可能为null
obj= obj|| window
obj.fun=this
const res = obj['fun'](...args); //obj.fun不行
delete obj['fun']
return res
}
</script>
</body>
</html>
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
//eval会把字符串当成函数执行
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
apply
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
bind
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
Function.prototype._bind = function(obj,...args){
obj=obj || window
let self=this
return function(...args1){
return self.call(obj,...args,...args1)
}
}
</script>
</body>
</html>
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("error");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
new
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _new = function(constructor) {
// 补全代码\n
let obj = {};
obj.__proto__ = constructor.prototype;
const ret=constructor.call(obj)
return typeof ret === 'object' ? ret : obj;
}
//or
const _new = function (constructor, ...args) {
// new关键字做了4件事
// 1. 创建一个新对象
const obj = {};
// 2. 为新对象添加属性__proto__,将该属性链接至构造函数的原型对象
obj.__proto__ = constructor.prototype;
// 3. 执行构造函数,this被绑定在新对象上
const ret=constructor.apply(obj, args);
// 4. 返回对象
return typeof ret === 'object' ? ret : obj;
};
</script>
</body>
</html>
// 第二版的代码
function objectFactory() {
var obj = new Object(),
//删除并拿到arguments的第一项
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
instanceof
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _instanceof = (target, Fn) => {
let proto = target.__proto__
let prototype = Fn.prototype
while(true) {
if(proto === Fn.prototype) return true
if(proto === null) return false
proto = proto.__proto__
}
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _instanceof = (target, Fn) => {
return target instanceof Fn
}
</script>
</body>
</html>
map
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
Array.prototype._map = function(Fn) { //不要用箭头函数
if (typeof Fn !== 'function') return
const array = this
const newArray = new Array(array.length)
for (let i=0; i<array.length; i++) {
let result = Fn.call(arguments[1], array[i], i, array)
newArray[i] = result
}
return newArray
}
//or
Array.prototype._map = function (fn){
if(typeof fn !== 'function') return;
let newArr = [];
for(let i = 0;i<this.length ;i++){
newArr[i] = fn(this[i])
}
return newArr;
}
</script>
</body>
</html>
filter
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
Array.prototype._filter = function(Fn){
if(typeof Fn!=='function') return
const res=[]
for(let i=0;i<this.length;i++){
if(Fn(this[i])){
res.push(this[i])
}
}
return res
}
</script>
</body>
</html>
reduce
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
Array.prototype._reduce = function(Fn,init){
if(typeof Fn!=='function') return
for(let i=0;i<this.length;i++){
if(init===undefined){
init=Fn(this[i],this[i+1])
++i
}else{
init=Fn(init,this[i])
}
}
return init
}
//or
Array.prototype._reduce = function(Fn){
if(typeof Fn!=='function') return
var total=0
for(let i=0;i<this.length;i++)
total = Fn(total,this[i]);
return total;
}
</script>
</body>
</html>
findindex
Array.prototype.findIndex=function(predicate) {
for (var i = 0; i < this.length; i++) {
if (predicate.call(this, this[i], i, this)) return i;
}
return -1;
}
console.log([1, 2, 3, 4].findIndex(function(item, i, array){
if (item == 3) return true;
})) // 2
findlastindex
Array.prototype.findLastIndex=function(predicate) {
for (var i = this.length-1; i>=0; i++) {
if (predicate.call(this, this[i], i, this)) return i;
}
return -1;
}
console.log([1, 2, 3, 4].findLastIndex(function(item, i, array){
if (item == 3) return true;
})) // 2
合并findindex和findlastindex
function createIndexFinder(dir) {
return function(predicate) {
var length = this.length;
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (predicate.call(this, this[index], index, this)) return index;
}
return -1;
}
}
Array.prototype.findIndex = createIndexFinder(1);
Array.prototype.findLastIndex = createIndexFinder(-1);
console.log([1, 2, 3, 4].findLastIndex(function(item, i, array){
if (item == 3) return true;
})) // 2
合并indexof和lastindexof
function createIndexOfFinder(dir) {
return function(item){
var length = this.length;
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (this[index] === item) return index;
}
return -1;
}
}
Array.prototype.indexof= createIndexOfFinder(1);
Array.prototype.lastindexof = createIndexOfFinder(-1);
var result = [1, 2, 3, 4, 5].indexOf(2);
console.log(result) // 1
// 进阶版:支持从某个索引开始找和NaN的查找
function createIndexOfFinder(dir, predicate, sortedIndex) {
return function(array, item, idx){
var length = array.length;
var i = 0;
if (typeof idx == "number") {
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(length + idx, 0);
}
else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
}
else if (sortedIndex && idx && length) {
idx = sortedIndex(array, item);
// 如果该插入的位置的值正好等于元素的值,说明是第一个符合要求的值
return array[idx] === item ? idx : -1;
}
// 判断是否是 NaN
if (item !== item) {
idx = predicate(array.slice(i, length), isNaN)
return idx >= 0 ? idx + i: -1;
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
}
}
var indexOf = createIndexOfFinder(1, findIndex, sortedIndex);
var lastIndexOf = createIndexOfFinder(-1, findLastIndex);
Set
class mySet {
constructor() {
this.items={};//js对象不允许一个键指向两个不同的属性→保证集合里元素都是唯一的
}
//判断集合中是否存在val元素
has(val) {
return this.items.hasOwnProperty(val);
}
//向集合中添加元素
add(val) {
if(!this.has(val)) {
this.items[val] = val;
return true;
}
else {
return false;
}
}
//删除集合中的指定元素
remove(val) {
if(this.has(val)) {
delete this.items[val];
}
}
//清空集合中的元素
clear() {
this.items={};
}
//集合的大小
size() {
/*Object.keys()返回给定对象所有可枚举属性的字符串数组*/
return Object.keys(this.items).length;
}
//获取集合中的所有元素
values(){
let res=[];
Object.keys(this.items).forEach(item=>{
res.push(this.items[item]);
})
return res;
}
}
var set = new mySet();
set.add(3);
set.add(1);
set.add(7);
set.add(0);
console.log(set.size()); //输出4
set.remove(3);
console.log(set.values()); //输出[ 0, 1, 7 ]
console.log(set.has(5)); //输出false
set.clear();
console.log(set.size()); //输出0
promise相关
class Promise{
//构造方法
constructor(executor){
//添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
//声明属性
this.callbacks = [];
//保存实例对象的 this 的值
const self = this;// self _this that
//resolve 函数
function resolve(data){
//判断状态
if(self.PromiseState !== 'pending') return;
//1. 修改对象的状态 (promiseState)
self.PromiseState = 'fulfilled';// resolved
//2. 设置对象结果值 (promiseResult)
self.PromiseResult = data;
//调用成功的回调函数
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data);
});
});
}
//reject 函数
function reject(data){
//判断状态
if(self.PromiseState !== 'pending') return;
//1. 修改对象的状态 (promiseState)
self.PromiseState = 'rejected';//
//2. 设置对象结果值 (promiseResult)
self.PromiseResult = data;
//执行失败的回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data);
});
});
}
try{
//同步调用『执行器函数』
executor(resolve, reject);
}catch(e){
//修改 promise 对象状态为『失败』
reject(e);
}
}
//then 方法封装
then(onResolved,onRejected){
const self = this;
//判断回调函数参数
if(typeof onRejected !== 'function'){
onRejected = reason => {
throw reason;
}
}
if(typeof onResolved !== 'function'){
onResolved = value => value;
//value => { return value};
}
return new Promise((resolve, reject) => {
//封装函数
function callback(type){
try{
//获取回调函数的执行结果
let result = type(self.PromiseResult);
//判断
if(result instanceof Promise){
//如果是 Promise 类型的对象
result.then(v => {
resolve(v);
}, r=>{
reject(r);
})
}else{
//结果的对象状态为『成功』
resolve(result);
}
}catch(e){
reject(e);
}
}
//调用回调函数 PromiseState
if(this.PromiseState === 'fulfilled'){
setTimeout(() => {
callback(onResolved);
});
}
if(this.PromiseState === 'rejected'){
setTimeout(() => {
callback(onRejected);
});
}
//判断 pending 状态
if(this.PromiseState === 'pending'){
//保存回调函数
this.callbacks.push({
onResolved: function(){
callback(onResolved);
},
onRejected: function(){
callback(onRejected);
}
});
}
})
}
//catch 方法
catch(onRejected){
return this.then(undefined, onRejected);
}
//添加 resolve 方法
static resolve(value){
//返回promise对象
return new Promise((resolve, reject) => {
if(value instanceof Promise){
value.then(v=>{
resolve(v);
}, r=>{
reject(r);
})
}else{
//状态设置为成功
resolve(value);
}
});
}
//添加 reject 方法
static reject(reason){
return new Promise((resolve, reject)=>{
reject(reason);
});
}
//添加 all 方法
static all(promises){
//返回结果为promise对象
return new Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
//遍历
for(let i=0;i<promises.length;i++){
//
promises[i].then(v => {
//得知对象的状态是成功
//每个promise对象 都成功
count++;
//将当前promise对象成功的结果 存入到数组中
arr[i] = v;
//判断
if(count === promises.length){
//修改状态
resolve(arr);
}
}, r => {
reject(r);
});
}
});
}
//添加 race 方法
static race (promises){
return new Promise((resolve, reject) => {
for(let i=0;i<promises.length;i++){
promises[i].then(v => {
//修改返回对象的状态为 『成功』
resolve(v);
},r=>{
//修改返回对象的状态为 『失败』
reject(r);
})
}
});
}
}
promise.all限制并发数
Promise.asyncStep = function (promises) {
return new Promise((resolve, reject) => {
let index = 0
let stepCount = 10 // 并行多个
let result = []
let count = promise.length
if (promise.length === 0) {
resolve(result)
} else {
function runPromise () {
if (index === promise.length) {
resolve(result);
} else {
stepCount = Math.min(count, stepCount)
for (let i = 0; i < stepCount; i++) {
--count;
Promise.resolve(promise[index]).then(data => {
result[index] = data;
++index;
}, err => {
reject(err)
});
}
runPromise();
}
}
runPromise()
}
})
}
冒泡排序
时间复杂度:O(N^2);空间复杂度:O(1)
function BubbleSort(arr) {
if(arr == null || arr.length <= 0){
return [];
}
var len = arr.length;
for(var end = len - 1; end > 0; end--){
for(var i = 0; i < end; i++) {
if(arr[i] > arr[i + 1]){
swap(arr, i, i + 1);
}
}
}
return arr;
}
//这里必须传arr,否则是传值
function swap(arr, i, j){
// var temp = arr[i];
// arr[i] = arr[j];
// arr[j] = temp;
//交换也可以用异或运算符
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
插入排序
插入排序思路:将一个新的数,插入已排好序数组中;时间复杂度:O(N^2);空间复杂度:O(1)
//利用哨兵
var InsertSort=function(array){
if(array.length===0) return []
let len=array.length
for(let i=1;i<len;i++){
if(array[i]<array[i-1]){
var tmp=array[i]
for(var j=i-1;tmp<array[j];j--){
array[j+1]=array[j]
}
array[j+1]=tmp
}
}
return array
}
function insertSort(arr) {
if(arr == null || arr.length <= 0){
return [];
}
var len = arr.length;
for(var i = 1; i < len; i++) {
for(var j = i - 1; arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
return arr;
}
function swap(arr, i, j){
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
选择排序
选择排序的实现思路:遍历数组,选择最小的数加入已排好序数组;时间复杂度:O(N^2);空间复杂度:O(1)
function SelectionSort(arr) {
if(arr == null || arr.length < 0) {
return [];
}
for(var i = 0; i < arr.length - 1; i++) {
var minIndex = i;
for(var j = i + 1; j < arr.length; j++) {
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
return arr;
}
function swap(arr, i, j) {
if (i === j) {
return;
}
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
归并排序
归并排序的思路:
1.先左侧部分排好序
2.再右侧部分排好序
3.准备一个辅助数组,用外排的方式,小的开始填,直到有个结束,将另一个数组剩余部分拷贝到末尾
4.再将辅助数组拷贝回原数组
// 递归实现
function mergeSort(arr){
if(arr == null || arr.length <= 0){
return [];
}
sortProcess(arr, 0, arr.length - 1);
return arr;
}
function sortProcess(arr, L, R){
//递归的终止条件,就是左右边界索引一样
if(L == R){
return;
}
var middle = Math.floor((L+R)/2);//找出中间值
sortProcess(arr, L, middle);//对左侧部分进行递归
sortProcess(arr, middle + 1, R);//对右侧部分进行递归
merge(arr, L, middle, R);//然后利用外排方式进行结合
}
function merge(arr, L, middle, R){
var help = [];
var l = L;
var r = middle + 1;
//利用外排方式进行
while(l <= middle && r <= R){
help.push(arr[l] < arr[r] ? arr[l++] : arr[r++])
}
while(l <= middle){
help.push(arr[l++]);
}
while(r <= R){
help.push(arr[r++]);
}
//for(var i = 0; i < help.length; i++) {
//arr[L + i] = help[i];
//}
//arr=[...help] 这样不行
arr.splice(L, help.length, ...help);//这个利用了ES6的语法
}
快速排序
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _quickSort = array => {
if(array.length <= 1) return array
var pivotIndex = Math.floor(array.length / 2)
var pivot = array.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i=0 ; i<array.length ; i++){
if (array[i] < pivot) {
left.push(array[i])
} else {
right.push(array[i])
}
}
return _quickSort(left).concat([pivot], _quickSort(right))
}
</script>
</body>
</html>
堆排序
堆排序思路:
1.让数组变成大根堆
2.把最后一个位置和堆顶做交换
3.则最大值在最后,则剩下部分做heapify,则重新调整为大根堆,则堆顶位置和该部分最后位置做交换
4.重复进行,直到减完,则这样最后就调整完毕,整个数组排完序(为一个升序)
时间复杂度:O(N * logN) 空间复杂度:O(1)
function heapSort(arr) {
if(arr == null || arr.length <= 0) {
return [];
}
//首先是建立大顶堆的过程
for(var i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
var size = arr.length;//这个值用来指定多少个数组成堆,当得到一个排序的值后这个值减一
//将堆顶和最后一个位置交换
/**
* 当大顶堆建立完成后,然后不断将最后一个位置和堆顶交换;
* 这样最大值就到了最后,则剩下部分做heapify,重新调整为大根堆,则堆顶位置和倒数第二个位置交换,重复进行,直到全部排序完毕*/
//由于前面已经是大顶堆,所以直接交换
swap(arr, 0, --size);
while(size > 0) {
//重新变成大顶堆
heapify(arr, 0, size);
//进行交换
swap(arr, 0, --size);
}
}
//加堆过程中
function heapInsert(arr, index) {
//比较当前位置和其父位置,若大于其父位置,则进行交换,并将索引移动到其父位置进行循环,否则跳过
//结束条件是比父位置小或者到达根节点处
while(arr[index] > arr[parseInt((index - 1) / 2)]){
//进行交换
swap(arr, index, parseInt((index - 1) / 2));
index = parseInt((index - 1) / 2);
}
}
//减堆过程
/**
* size指的是这个数组前多少个数构成一个堆
* 如果你想把堆顶弹出,则把堆顶和最后一个数交换,把size减1,然后从0位置经历一次heapify,调整一下,剩余部分变成大顶堆*/
function heapify(arr, index, size) {
var left = 2 * index + 1;
while(left < size) {
var largest = (left + 1 < size && arr[left] < arr[left + 1]) ? left + 1 : left;
largest = arr[index] > arr[largest] ? index : largest;
//如果最大值索引和传进来索引一样,则该值到达指定位置,直接结束循环
if(index == largest) {
break;
}
//进行交换,并改变索引和其左子节点
swap(arr, index, largest);
index = largest;
left = 2 * index + 1;
}
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
桶排序
桶排序会经历三次遍历:准备一个数组、遍历一遍数组、重构一遍数组,是非基于比较的排序,下面以一个问题来阐述其思路。
**问题:**给定一个数组,求如果排序之后,相邻两个数的最大差值,要求时间复杂度O(N),且要求不能用基于比较的排序
思路:
1.准备桶:数组中有N个数就准备N+1个桶
2.遍历一遍数组,找到最大值max和最小值min 。若min = max,则差值=0;若min≠max,则最小值放在0号桶,最大值放在N号桶,剩下的数属于哪个范围就进哪个桶
3.根据鸽笼原理,则肯定有一个桶为空桶,设计该桶的目的是为了否定最大值在一个桶中,则最大差值的两个数一定来自于两个桶,但空桶两侧并不一定是最大值
4.所以只记录所有进入该桶的最小值min和最大值max和一个布尔值表示该桶有没有值
5.然后遍历这个数组,如果桶是空的,则跳到下一个数,如果桶非空,则找前一个非空桶,则最大差值=当前桶min - 上一个非空桶max,用全局变量更新最大值
时间复杂度:O(N)空间复杂度:O(N)
function maxGap(arr) {
if(arr == null || arr.length <= 0) {
return 0;
}
var len = arr.length;
var max = -Infinity, min = Infinity;
//遍历一遍数组,找到最大值max和最小值min
for(var i = 0; i < len; i++) {
max = max > arr[i] ? max : arr[i];
min = min > arr[i] ? arr[i] : min;
}
//若min = max,则差值为0;
if(min == max) {
return 0;
}
var hasNum = new Array(len + 1);
var mins = new Array(len + 1);
var maxs = new Array(len + 1);
var bid = 0;//指定桶的编号
for(var i = 0; i < len; i++) {
bid = bucket(arr[i], min, max, len);//获得该值是在哪个桶//由于有N+1个桶,所以间隔就是N个,所以此处除以的是len,然后通过这个函数得到应该放到哪个桶里
maxs[bid] = hasNum[bid] ? Math.max(arr[i], maxs[bid]) : arr[i];
mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i];
hasNum[bid] = true;
}
var res = 0;
var lastMax = maxs[0];
for(var i = 0; i < len + 1; i++) {
if(hasNum[i]) {
res = Math.max(mins[i] - lastMax, res);
lastMax = maxs[i];
}
}
return res;
}
//获得桶号
//这个函数用于判断在哪个桶中,参数分别为值、最小值、最大值、桶间隔
function bucket(value, min, max, len) {
return parseInt((value - min) / ((max - min) / len));
}
全排列
‘abc’的全排列等于 ('a'拼接上'bc'的全排列数组中的每一项) + ('b'拼接上'ac'的全排列数组的每一项) + ('c'拼接上'ab'的全排列数组的每一项)
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
// 补全代码
const _permute = string => {
if(string.length === 1) {
return [string]
}
const results = []
for(let s of string){
const arr = string.split('').filter(str =>str !== s)
_permute(arr.join('')).forEach(item => {
results.push(s + item)
})
}
return results
}
</script>
</body>
</html>
寄生组合式继承
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName=function(){
return this.name
}
function Chinese(name,age) {
Human.call(this,name)
this.age=age
this.color = 'yellow'
}
Chinese.prototype=new Human()
Chinese.prototype.constructor=Chinese
Chinese.prototype.getAge=function(){
return this.age
}
function inherit(parent,child){
function F(){}
F.prototype=parent.prototype
child.prototype=new F()
//这么写也行 child.prototype=Object.creat(parent.prototype)
child.prototype.constructor=child
}
function Parent(name){
this.name="doing"
this.age="18"
}
Parent.prototype.getName=function(){
console.log(this.name)
}
function Child(name,age){
Parent.call(this,name)
this.age="5"
this.friend=["jack"]
}
Child.prototype.getFriend=function(){
console.log(this.friend)
}
inherit(parent,child)
let child=new Child()
发布订阅模式
class EventEmitter {
// 补全代码
constructor(){
//记录当前被订阅的事件
this.event = {};
}
//订阅事件
on(e,fn){
//是新事件
if(!this.event[e]){
this.event[e] = [fn];
}else{
//旧事件添加新方法
this.event[e].push(fn);
}
}
//触发事件
emit(e){
if(this.event[e]){
this.event[e].forEach( fun =>fun());
}
}
}
闭包实现单例模式
var SingleTon = function(){
var instance;
class CreateSingleTon {
constructor (name) {
if(instance) return instance;
this.name = name;
this.getName();
return instance = this;
}
getName() {
return this.name;
}
}
return CreateSingleTon;
}();
var a = new SingleTon('instance1');
console.log(a.getName()); //输出instance1
var b = new SingleTon('instance2');
console.log(b.getName()); //输出instance1
console.log(a === b); //输出true