节流
节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数
时间戳
特点:第一次就执行
function throttle(fn,wait){
//记录上一次函数触发的时间
var lastTime = 0
return function(){
// 记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime - lastTime > wait){
fn.call(this)
lastTime = nowTime;
}//闭包是为了避免执行的时候每次都初始化lastTime
}
}
定时器非立即执行
当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
特点:delay时间后执行
适用场景:
- 一段时间内只执行最后一次拖拽
- 一段时间呢浏览器只缩放一次
- 一点时间内只执行一次动画
- 滚动和缩放
- 鼠标不断点击触发,点击事件在规定时间内只触发一次(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle_1(fn,wait){
var flag = true;
return function(){
if(flag)
flag = false
var timer = setTimeout(() => {
fn.apply(this,arguments)
flag = true
},wait)
}
}
}
时间戳+非立即执行
//时间戳+定时器版: 实现第一次触发可以立即响应,结束触发后也能有响应 (该版才是最符合实际工作需求)
//该版主体思路还是时间戳版,定时器的作用仅仅是执行最后一次回调
function throttle(fun, delay = 500) {
let timer = null;
let previous = 0;
return function(args) {
let now = Date.now();
let remaining = delay - (now - previous); //距离规定时间,还剩多少时间
let that = this;
let _args = args;
clearTimeout(timer); //清除之前设置的定时器
if (remaining <= 0) {
fun.apply(that, _args);
previous = Date.now();
} else {
timer = setTimeout(function(){
fun.apply(that, _args)
}, remaining); //因为上面添加的clearTimeout.实际这个定时器只有最后一次才会执行
}
}
}
定时器立即执行
//节流(立即执行)
function throttle_2(fn,wait){
var flag = true;
var timer = null;
return function(){
if(flag) {
fn.apply(this,arguments);
flag = false;
timer = setTimeout(() => {
flag = true
},wait)
}
}
}
立即执行+非立即执行
//节流(合并)
function throttle_merge(fn,wait = 500,isImmediate = false){
var flag = true;
var timer = null;
if(isImmediate){
return function(){
if(flag) {
fn.apply(this,arguments);
flag = false;
timer = setTimeout(() => {
flag = true
},wait)
}
}
}
return function(){
if(flag == true){
flag = false
var timer = setTimeout(() => {
fn.apply(this,arguments)
flag = true
},wait)
}
}
}
防抖
防抖:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!
非立即执行版本
适用场景:
- 按钮提交只执行最后一次
- 输入框只执行输入的最后一次
- 按钮点击:收藏,点赞,心标等
//防抖(非立即执行)
function debounce_1(fn,wait){
var timerId = null;
return function(){
clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this,arguments)
},wait)
}
}
立即执行
//防抖(立即执行)
function debounce_2(fn,wait){
var timerId = null;
var flag = true;
return function(){
clearTimeout(timerId);
if(flag){
fn.apply(this,arguments);
flag = false
}
timerId = setTimeout(() => { flag = true},wait)
}
}
两者结合
//防抖(合并版)
function debounce_merge(fn,wait = 500,isImmediate = false){
var timerId = null;
var flag = true;
if(isImmediate){
return function(){
clearTimeout(timerId);
if(flag){
fn.apply(this,arguments);
flag = false
}
timerId = setTimeout(() => { flag = true},wait)
}
}
return function(){
clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this,arguments)
},wait)
}
}
继承
组合继承
function Parent() {
this.name = 'parent'
this.arr = [1,2,3]
}
function Child() {
Parent.call(this);
this.type = 'child'
}
Child.prototype = new Parent();
- 子类构造函数中使用Parent.call(this);的方式可以继承写在父类构造函数中this上绑定的各属性和方法; 使用Child.prototype = new Parent()的方式可以继承挂在在父类原型上的各属性和方法
- 组合继承最大的缺点是会调用两次父构造函数。
- 特点:方法公用,属性私有
原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:属性值始终都会共享相应的值,这点跟原型链继承一样
寄生组合式继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constrctor = Child;
var child1 = new Child('kevin', '18');
console.log(child1);
封装一个原生的继承方法
/**
* 继承
* @param Parent
* @param Child
*/
function extendsClass(Parent, Child) {
function F() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constrctor = Child
return Child
}
class继承
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的 constructor(name)
this.age = age;
}
}
var child1 = new Child('kevin', '18');
console.log(child1);
- super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。
- 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
- 也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。
两条继承链
Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。
(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。
- 父类的静态方法,可以被子类继承
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod(); // 'hello'
(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。
class继承原型链
原理是使用的寄生组合式继承以及多一条子类直接父类。
es5继承原型链(寄生组合式)
new实现
function objectFactory() {
debugger;
var obj = new Object(),
Constructor = [].shift.call(arguments);//取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
obj.__proto__ = Constructor.prototype;//将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
Constructor.apply(obj, arguments);//使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
return obj;
};
call实现
Function.prototype.mycall = function(content){
var context = content || window;
content.fn = this;//将函数设为对象的属性
var args = [...arguments].slice(1);
let result = content.fn(...args);//执行该函数
delete content.fn;//删除该函数
return result;
}
拓展运算符
apply实现
Function.prototype.myapply = function(content){
var context = content || window;
content.fn = this;//将函数设为对象的属性
var result;
if(arguments[1]){
result = content.fn(...arguments[1]);//执行该函数
}else{
result = content.fn();
}
delete content.fn;//删除该函数
return result;
}
bind实现
Function.prototype.bind = function(oThis){
if(typeof this !=='function'){
throw new TypeError('what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments,1),
fToBind = this,
fNOP = function(){},
fBound = function(){
return fToBind.apply(this instanceof fNOP ? this:oThis,aArgs.concat(aArgs,Array.prototype.slice.call(arguments)))
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();//上面两句等于fBound.prototype = Object.create(this.prototype);
return fBound;
}
柯里化
函数柯里化
//柯里化:1.调用另一个函数 2。为他传入要柯里化的函数和必要参数
//将被返回函数的参数进行排序
function curry1(fn){
//var args = Array.prototype.slice.call(arguments,1);//返回第二个人参数开始的所有参数
var args = [...arguments].slice(1)
console.log(args)
return function(){
//var innerArgs = Array.prototype.slice.call(arguments);
var innerArgs = [...arguments]
var finalArgs = args.concat(innerArgs);
return fn.apply(null,finalArgs)
}
}
函数柯里化绑定this
function bindd(fn,context){
var args = Array.prototype.slice.call(arguments,2);//返回第三个参数开始的所有参数
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context,finalArgs)
}
}
柯里化相关实现1
function curry(func) {
// 存储已传入参数
let _args = []
// 做一个闭包
function _curry(...args) {
// 把参数合并
_args = _args.concat(args)
// 如果参数够了就执行
if (_args.length >= func.length) {
//ES6指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数
const result = func(..._args)
_args = []
return result;
}
// 继续返回此函数
else {
return _curry
}
}
return _curry
}
function add1(...a, b, c) {
return a + b + c
}
let testAdd = curry(add1)
console.log(testAdd(1)(2)(3)) //6
console.log(testAdd(1, 2)(3,4)) //6
柯里化相关实现2
function add2() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
//debugger;
//var _args = Array.prototype.slice.call(arguments);
var _args = [...arguments]
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var fn = function() {
_args.push(...arguments);
return fn;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
fn.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
//console.log(_adder)
return fn;
}
//add2(1)(2)(3) // 6
var s = add2(1, 2, 3)(4);console.log(s) // 10
//add2(1)(2)(3)(4)(5) // 15
//add2(2, 6)(1) // 9
函数组合compose
/*
**
compose([a, b, c])('参数')
=>
a( b( c('参数') ) )
***
*/
function compose(funcs) {
debugger;
var len = funcs.length;
var index = len - 1;
for(let i = 0; i < len; i ++) {
if(typeof funcs[i] !== 'function') {
throw new TypeError('Expected function');
}
}
return function (...args) {
let result = funcs[index](...args) // 第一次
while(--index >= 0) {
result = funcs[index](result)
}
return result;
}
}
function a (str) {
return `a ${str}`
}
function b (str) {
return `b ${str}`
}
function c (str) {
return `c ${str}`
}
const abc = compose([a, b, c])
console.log(abc('guhy'))
数组
reduce实现map
Array.prototype._map = function(fn, callbackThis) {
// 最终返回的新数组
let res = [];
// 定义回调函数的执行环境
// call第一个参数传入null,则 this指向全局对象,同 map的规则
let CBThis = callbackThis || null;
this.reduce((brfore, after, idx, arr) => {
// 传入map回调函数拥有的参数
// 把每一项的执行结果push进res中
res.push(fn.call(CBThis, after, idx, arr));
}, null);
return res;
};
数组去重
数组去重的方法有很多种,如果要是手写的话,一般我都会写下面这种。也会顺便说一下ES6的set方法。
function removeDup(arr){
var result = [];
var hashMap = {};
for(var i = 0; i < arr.length; i++){
var temp = arr[i]
if(!hashMap[temp]){
hashMap[temp] = true
result.push(temp)
}
}
return result;
}
Array.from(new Set(arr))
[...new Set(arr)]
var arr1 = [123, {a: 1}, {a: {b: 1}}, {a: "1"}, {a: {b: 1}}, "meili"];
var arr3 = [123, "meili", "123", "mogu", 123];
var arr2 = [123, [1, 2, 3], [1, "2", 3], [1, 2, 3], "meili"];
function unique(arr) {
let b=[]
let hash={}
for(let i=0;i<arr.length;i++){
if(!hash[JSON.stringify(arr[i])]){
hash[JSON.stringify(arr[i])]=true
b.push(arr[i])
}
}
return b
}
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3))
数组合并
数组展平
数组flat方法是ES6新增的一个特性,可以将多维数组展平为低维数组。如果不传参默认展平一层,传参可以规定展平的层级。
// 展平一级
function flat(arr){
var result = [];
for(var i = 0; i < arr.length; i++){
if(Array.isArray(arr[i])){
result = result.concat(flat(arr[i]))
}else{
result.push(arr[i]);
}
}
return result;
}
//展平多层
function flattenByDeep(array,deep){
var result = [];
for(var i = 0 ; i < array.length; i++){
if(Array.isArray(array[i]) && deep > 1){
result = result.concat(flattenByDeep(array[i],deep -1))
}else{
result.push(array[i])
}
}
return result;
}
参考:www.cnblogs.com/wind-lanyan…
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
数组排序
字符串
字符串翻转
var str = "smile at life";
//翻转字符串
console.log(str.split(" ").reverse().join(" "))//life at smile join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。
console.log(str.split("").reverse().join(""))//efil ta elims 这种方法是错误的,所以记住要使用上面一种形式
//替换空格
//方法一: 先转成字符数组,再把数组中的所有字符放入一个字符串
console.log(str.split(" ").join("%20"))
//方法二:
console.log(str.replace(/\s/g,'%20'))
/*
字符串注意点:
split() 方法用于把一个字符串分割成字符串数组。
两个参数,第一个是以什么元素进行分割,第二个是保留的
如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
*/
var str1="How are you doing today?"
document.write(str1.split(" ") + "<br />")//How,are,you,doing,today?
document.write(str1.split("") + "<br />")//H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,?
document.write(str1.split(" ",3))//How,are,you
//join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。
//替换空格
大数相加
//大数相加
function sumStrings(a,b){
var res='', c=0;
a = a.split('');
b = b.split('');
while (a.length || b.length || c){
c += ~~a.pop() + ~~b.pop();//对于浮点数,~~value可以代替parseInt(value),而且前者效率更高些
res = c % 10 + res;
c = c>9;
}
return res.replace(/^0+/,'');
}
var sunm1 = sumStrings('9007199254740993', '11')
console.log(sunm1)
获取url参数
function GetQueryString(sUrl,name)
{
var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
var left= sUrl.indexOf("?") + 1
var right= sUrl.lastIndexOf("#")
var parasString = sUrl.slice(left, right)
//var r = window.location.search.substr(1).match(reg);
console.log(parasString)
var r = parasString.match(reg);
console.log(r)
if(r!=null)return unescape(r[2]); return null;
}
var aaa = GetQueryString( 'https://www.nowcoder.com?key=1&key=2&key=3&test=4#hehe ', 'key')
console.log(aaa)
异步
串行ajax
promise实现
const PENDING = 1;
const FULFILLED = 2;
const REJECTED = 3;
function MyPromise(executor) {
let self = this;
this.resolveQueue = [];
this.rejectQueue = [];
this.state = PENDING;
this.val = undefined;
function resolve(val) {
if (self.state === PENDING) {
setTimeout(() => {
self.state = FULFILLED;
self.val = val;
self.resolveQueue.forEach(cb => cb(val));
});
}
}
function reject(err) {
if (self.state === PENDING) {
setTimeout(() => {
self.state = REJECTED;
self.val = err;
self.rejectQueue.forEach(cb => cb(err));
});
}
}
try {
// 回调是异步执行 函数是同步执行
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
MyPromise.prototype.then = function(onResolve, onReject) {
let self = this;
// 不传值的话默认是一个返回原值的函数
onResolve = typeof onResolve === 'function' ? onResolve : (v => v);
onReject = typeof onReject === 'function' ? onReject : (e => { throw e });
if (self.state === FULFILLED) {
return new MyPromise(function(resolve, reject) {
setTimeout(() => {
try {
let x = onResolve(self.val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}
if (self.state === REJECTED) {
return new MyPromise(function(resolve, reject) {
setTimeout(() => {
try {
let x = onReject(self.val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}
if (self.state === PENDING) {
return new MyPromise(function(resolve, reject) {
self.resolveQueue.push((val) => {
try {
let x = onResolve(val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
self.rejectQueue.push((val) => {
try {
let x = onReject(val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}
}
MyPromise.prototype.catch = function(onReject) {
return this.then(null, onReject);
}
MyPromise.all = function(promises) {
return new MyPromise(function(resolve, reject) {
let cnt = 0;
let result = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
result[i] = res;
if (++cnt === promises.length) resolve(result);
}, err => {
reject(err);
})
}
});
}
MyPromise.race = function(promises) {
return new MyPromise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}
MyPromise.resolve = function(val) {
return new MyPromise(function(resolve, reject) {
resolve(val);
});
}
MyPromise.reject = function(err) {
return new MyPromise(function(resolve, reject) {
reject(err);
})
}
简单路由实现(hash,history)
倒计时
<script>
window.onload = function() {
setInterval(function() {
var nowTime = new Date();//获取当前时间
//创建目标日期
var endTime = new Date("2019-9-1 00:00:00");
var seconds = parseInt((endTime.getTime() - nowTime.getTime()) / 1000);//两个时间点的时间差(秒)
var d = parseInt(seconds / 3600 / 24);//得到天数
var h = parseInt(seconds / 3600 % 24);//小时
var m = parseInt(seconds / 60 % 60);//分钟
var s = parseInt(seconds % 60);//秒
document.getElementById("djs").innerHTML = "距离开学还有" + d +"天" + h + "小时" + m + "分钟" + s + "秒";
}, 1000);
}
</script>
实现计算器
实现eventEmitter
观察者模式是我们工作中经常能接触到的一种设计模式。用过 jquery 的应该对这种设计模式都不陌生。eventEmitter 是 node 中的核心,主要方法包括on、emit、off、once。
class EventEmitter {
constructor(){
this.events = {}
}
on(name,cb){
if(!this.events[name]){
this.events[name] = [cb];
}else{
this.events[name].push(cb)
}
}
emit(name,...arg){
if(this.events[name]){
this.events[name].forEach(fn => {
fn.call(this,...arg)
})
}
}
off(name,cb){
if(this.events[name]){
this.events[name] = this.events[name].filter(fn => {
return fn != cb
})
}
}
once(name,fn){
var onlyOnce = () => {
fn.apply(this,arguments);
this.off(name,onlyOnce)
}
this.on(name,onlyOnce);
return this;
}
}
实现jsonp
jsonp 的作用是跨域。原理是通过动态插入script标签来实现跨域,因为script脚本不受同源策略的限制。它由两部分组成:回调函数和数据。举例:
function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " +response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild);
}
根据上面的例子,下面来实现一个通用的JSONP函数
function jsonp(obj) {
const {url,data} = obj;
if (!url) return
return new Promise((resolve, reject) => {
const cbFn = `jsonp_${Date.now()}`
data.callback = cbFn
const head = document.querySelector('head')
const script = document.createElement('script')
const src = `${url}?${data2Url(data)}`
console.log('scr',src)
script.src = src
head.appendChild(script)
window[cbFn] = function(res) {
res ? resolve(res) : reject('error')
head.removeChild(script)
window[cbFn] = null
}
})
}
function data2Url(data) {
return Object.keys(data).reduce((acc, cur) => {
acc.push(`${cur}=${data[cur]}`)
return acc
}, []).join('&')
参考:segmentfault.com/a/119000002…
设计模式
发布订阅者模式
//把发布—订阅的功能提取出来,放在一个单独的对象内
var event = {
clientList: [],
listen: function(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); //订阅的信息添加进缓存
},
trigger: function() {
var key = Array.prototype.shift.call(arguments);
var fns = this.clientList[key];
if (!fns || fns.length === 0) { //如果没有绑定对应的消息
return false;
}
for (var i=0,fn;fn=fns[i++];) {
fn.apply(this, arguments);
}
}
}
//再定义一个installEvent函数,这个函数可以给所有的对象都动态安装发布—订阅者功能:
function installEvent(obj) {
for (var i in event) {
obj[i] = event[i];
}
}
//测试,给售楼处对象salesOffices动态添加发布—订阅功能:
var salesOffices = {};
installEvent(salesOffices);
salesOffices.listen('squareMeter88', function(price) {
console.log('price=' + price);
});
salesOffices.listen('squareMeter100', function(price) {
console.log('price=' + price);
});
salesOffices.trigger('squareMeter88',200000);
salesOffices.trigger('squareMeter100',300000);
树相关
二叉树
层次遍历
//层次遍历
function printFromTopToBottom(root){
//debugger;
let res = [];
let queue = [];
if(root == null){
return res
}
queue.push(root)
while(queue.length){
let node = queue.shift();
res.push(node.val);
if(node.left != null){
queue.push(node.left);
}
if(node.right != null){
queue.push(node.right);
}
}
return res;
}
先序遍历
//前序
function preOrderTraversal(root){
// debugger;
let res = [];
let stack = [];
if(root == null){
return res
}
stack.push(root)
while(stack.length){
let node = stack.pop();
res.push(node.val);
if(node.right){
stack.push(node.right);
}
if(node.left){
stack.push(node.left);
}
}
return res
}
中序遍历
//中序
function inOrderTraversal(root){
let cur = root;
let res = [];
let stack = [];
while(cur != null || stack.length){
if(cur){
stack.push(cur);
cur= cur.left;
}else{
cur = stack.pop();
res.push(cur.val);
cur = cur.right;
}
}
return res
}
后序遍历
//后序
function postOrderTraversal(root){
//debugger;
let res = [];
let stack = [];
if(root == null){
return res
}
stack.push(root);
while(stack.length){
let top = stack.pop();
res.unshift(top.val);
if(top.left){
stack.push(top.left);
}
if(top.right){
stack.push(top.right);
}
}
return res
}
深拷贝与浅拷贝
深拷贝数组
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
// 木易杨
function cloneDeep2(source) {
if (!isObject(source)) return source; // 非对象返回自身
var target = Array.isArray(source) ? [] : {};
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep2(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}
// 木易杨
function isObject(obj) {
return typeof obj === 'object' && obj != null;
}
循环引用
使用哈希表
// 木易杨
function cloneDeep3(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
var target = Array.isArray(source) ? [] : {};
hash.set(source, target); // 新增代码,哈希表设值
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
} else {
target[key] = source[key];
}
}
}
return target;
}
使用数组
// 木易杨
function cloneDeep3(source, uniqueList) {
if (!isObject(source)) return source;
if (!uniqueList) uniqueList = []; // 新增代码,初始化数组
var target = Array.isArray(source) ? [] : {};
// ============= 新增代码
// 数据已经存在,返回保存的数据
var uniqueData = find(uniqueList, source);
if (uniqueData) {
return uniqueData.target;
};
// 数据不存在,保存源数据,以及对应的引用
uniqueList.push({
source: source,
target: target
});
// =============
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], uniqueList); // 新增代码,传入数组
} else {
target[key] = source[key];
}
}
}
return target;
}
// 新增方法,用于查找
function find(arr, item) {
for(var i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
Symbol
当然可以,不过 Symbol 在 ES6 下才有,我们需要一些方法来检测出 Symble 类型。
方法一:Object.getOwnPropertySymbols(...)
方法二:Reflect.ownKeys(...)
Object.getOwnPropertySymbols(...)
思路就是先查找有没有 Symbol 属性,如果查找到则先遍历处理 Symbol 情况,然后再处理正常情况,多出来的逻辑就是下面的新增代码
// 木易杨
function cloneDeep4(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source);
let target = Array.isArray(source) ? [] : {};
hash.set(source, target);
// ============= 新增代码
let symKeys = Object.getOwnPropertySymbols(source); // 查找
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (isObject(source[symKey])) {
target[symKey] = cloneDeep4(source[symKey], hash);
} else {
target[symKey] = source[symKey];
}
});
}
// =============
for(let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep4(source[key], hash);
} else {
target[key] = source[key];
}
}
}
return target;
}
Reflect.ownKeys(...)
// 木易杨
function cloneDeep4(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source);
let target = Array.isArray(source) ? [] : {};
hash.set(source, target);
Reflect.ownKeys(source).forEach(key => { // 改动
if (isObject(source[key])) {
target[key] = cloneDeep4(source[key], hash);
} else {
target[key] = source[key];
}
});
return target;
}
// 测试已通过
破解递归爆栈
function cloneDeep5(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 广度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
手写dom深度遍历(广度和深度)
相关实现1
/*
const o = {
a:{
b:{
c:1
}
}
}
get(o,'a.b.c') //1
*/
var obj = {
x: 1,
y: {
a: 2,
b: {
c: 3,
d: 4
}
}
};
//str='[{"a.b":1,"a.b.c":2}]'
//console.log(JSON.parse(str));
//获取对象所有的key值
function getKeys(obj, path) {
debugger;
if(Object.prototype.toString.call(obj) === "[object Object]") {
var arrKeyValue = {};
(function getKeysFn(o, char) {
for(var key in o) {
// debugger
//判断对象的属性是否需要拼接".",如果是第一层对象属性不拼接,否则拼接"."
var newChar = char == "" ? key : char + "." + key;
if(Object.prototype.toString.call(o[key]) === "[object Object]") {
//如果属性对应的属性值仍为可分解的对象,使用递归函数继续分解,直到最里层
getKeysFn(o[key],newChar);
} else {
arrKeyValue[newChar] = o[key]
}
}
})(obj,"");
} else {
console.log("传入的不是一个真正的对象哦!");
}
return arrKeyValue[path]
}
console.log(getKeys(obj,'y.a'));
相关实现2
function bfs(target, id) {
debugger;
const quene = [...target]
do {
const current = quene.shift()
if (current.children) {
quene.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
}
if (current.id == id) {
return current.path
}
} while(quene.length)
return undefined
}
function dfs(target, id) {
debugger;
const stask = [...target]
do {
const current = stask.pop()
if (current.children) {
stask.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
}
if (current.id == id) {
return current.path
}
} while(stask.length)
return undefined
}
// 公共的搜索方法,默认bfs
function commonSearch(target, id, mode) {
//debugger;
const staskOrQuene = [...target]
do {
const current = staskOrQuene[mode === 'dfs' ? 'pop' : 'shift']()
if (current.children) {
staskOrQuene.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
}
if (current.id === id) {
return current
}
} while(staskOrQuene.length)
return undefined
}
let target = [
{
id:'1',
name:"广东省",
children:[
{
id:'11',
name:"深圳市",
children:[
{
id:'111',
name:"苏州市",
},
{
id:'112',
name:"南京市",
}
]
}
]
}
]
console.log(dfs(target, 111))//1-11-111
相关实现3
/*
const o = {
a:{
b:{
c:1
}
}
}
get(o,'a.b.c') //1
*/
var obj = {
x: 1,
y: {
a: 2,
b: {
c: 3,
d: 4
}
}
};
//str='[{"a.b":1,"a.b.c":2}]'
//console.log(JSON.parse(str));
//获取对象所有的key值
function getKeys(obj, path) {
debugger;
if(Object.prototype.toString.call(obj) === "[object Object]") {
var arrKeyValue = {};
(function getKeysFn(o, char) {
for(var key in o) {
// debugger
//判断对象的属性是否需要拼接".",如果是第一层对象属性不拼接,否则拼接"."
var newChar = char == "" ? key : char + "." + key;
if(Object.prototype.toString.call(o[key]) === "[object Object]") {
//如果属性对应的属性值仍为可分解的对象,使用递归函数继续分解,直到最里层
getKeysFn(o[key],newChar);
} else {
arrKeyValue[newChar] = o[key]
}
}
})(obj,"");
} else {
console.log("传入的不是一个真正的对象哦!");
}
return arrKeyValue[path]
}
console.log(getKeys(obj,'y.a'));//2
相关实现4
var entry = {
'a.b.c.dd': 'abcdd',
'a.d.xx': 'adxx',
'a.e': 'ae'
}
// 要求转换成如下对象
var output = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}
实现代码
function map(entry) {
debugger;
const obj = Object.create(null);
for (const key in entry) {
const keymap = key.split('.');
set(obj, keymap, entry[key])
}
return obj;
}
function set(obj, map, val) {
let tmp;
if (!obj[map[0]]) obj[map[0]] = Object.create(null);
tmp = obj[map[0]];
for (let i = 1; i < map.length; i++) {
if (!tmp[map[i]]) tmp[map[i]] = map.length - 1 === i ? val : Object.create(null);
tmp = tmp[map[i]];
}
}
相关实现5
var entry = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}
// 要求转换成如下对象
var output = {
'a.b.c.dd': 'abcdd',
'a.d.xx': 'adxx',
'a.e': 'ae'
}
实现代码
//展平对象
function flatObj(obj, parentKey = "", result = {}) {
debugger;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
let keyName = `${parentKey}${key}`;
if (typeof obj[key] === 'object')
flatObj(obj[key], keyName+".", result)
else
result[keyName] = obj[key];
}
}
return result;
}
重复数组转为树形结构
const fn = arr => {
debugger;
const res = []
// const map = arr.reduce(function(res,item){
// res[item.id] = item
// return res
// },{})
//可以简写为
const map = arr.reduce((res, item) => ((res[item.id] = item), res), {})
for (const item of Object.values(map)) {
if (!item.pId) {
res.push(item)
} else {
const parent = map[item.pId]
parent.child = parent.child || []
parent.child.push(item)
}
}
return res
}
const arr = [{id: 1}, {id:2, pId: 1}, {id: 3, pId: 2}, {id: 4}, {id:3, pId: 2}, {id: 5, pId: 4}]
console.log(fn(arr))
//fn(arr) => [{id: 1, child: [{id: 2, pId: 1, child: [{ id: 3, pId: 2}]}]}, {id: 4, child: [{id: 5, pId: 4}]}]
sleep
const sleep = time => {
return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
console.log(1)
})
function* sleepGenerator(time) {
yield new Promise(function(resolve,reject){
setTimeout(resolve,time);
})
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})
function sleep(time) {
return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
let out = await sleep(1000);
console.log(1);
return out;
}
output();
function sleep(callback,time) {
if(typeof callback === 'function')
setTimeout(callback,time)
}
function output(){
console.log(1);
}
sleep(output,1000);
lazyman
class LazyManClass {
constructor(name) {
this.taskList = [];
this.name = name;
debugger;
console.log(`Hi I am ${this.name}`);
setTimeout(() => {
this.next();
}, 0);
}
eat (name) {
var that = this;
var fn = (function (n) {
return function () {
console.log(`I am eating ${n}`)
that.next();
}
})(name);
this.taskList.push(fn);
return this;
}
sleepFirst (time) {
var that = this;
var fn = (function (t) {
return function () {
setTimeout(() => {
console.log(`等待了${t}秒...`)
that.next();
}, t * 1000);
}
})(time);
this.taskList.unshift(fn);
return this;
}
sleep (time) {
var that = this
var fn = (function (t) {
return function () {
setTimeout(() => {
console.log(`等待了${t}秒...`)
that.next();
}, t * 1000);
}
})(time);
this.taskList.push(fn);
return this;
}
next () {
var fn = this.taskList.shift();
fn && fn();
}
}
function LazyMan(name) {
return new LazyManClass(name);
}
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(4).eat('junk food');
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了4秒...
// I am eating junk food
随机分组
// 得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
debugger;
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
// 随机生成10个整数数组, 排序, 去重
let initArr = Array.from({ length: 10 }, (v) => { return getRandomIntInclusive(0, 99) });
initArr.sort((a,b) => { return a - b });
initArr = [...(new Set(initArr))];
// 放入hash表
let obj = {};
initArr.map((i) => {
const intNum = Math.floor(i/10);
if (!obj[intNum]) obj[intNum] = [];
obj[intNum].push(i);
})
// 输出结果
const resArr = [];
for(let i in obj) {
resArr.push(obj[i]);
}
console.log(resArr);
每三个数加一个逗号
function transform(num)
{
debugger;
var nStr = num.toString();
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
1 }
// JS最大准确数为16位,超过自动截取
console.log(transform(45646465734.2358745));
过程中的小收获
将不定长的参数传递给函数呢?有三种办法:eval,apply,ES6的解构语法
eval('obj.fn('+args+'));
obj.fn.apply(obj,args);
obj.fn(...args);