朋也的博客 » 首页 » 文章
作者:朋也
日期:2021-02-24
类别:javascript学习笔记
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
节流函数一般用于事件上的,比如浏览器的滚动事件
window.addEventListener("scroll", function(e) {
console.log("123123");
})
当滚动浏览器的时候,这个回调会被执行非常多次,这就不太好了,可以通过节流来限制在一段时间内只执行一次
定义一个节流的方法
function throttle(callback, wait) {
let start = 0;
return function (e) {
let now = Date.now();
if (now - start > wait) {
callback.call(this, e);
start = now;
}
}
}
然后改造一下滚动事件对象
window.addEventListener("scroll", throttle(function(e) {
console.log("123123");
}), 500); // 500ms才执行一次这个滚动事件
防抖的使用场景一般是在自动完成的地方,当在输入框里输入想搜索的内容时,如果不加防抖,那么每输入一个字符,就会发送一次请求获取一下要自动显示的内容,这显然是不合理的
防抖函数的原理是当触发一个事件时,它会等待一会(设定的时间)在等待的这段时间里,如果再次触发了事件,那么这个时间会重置,当在等待的时间内没有触发事件且时间也过了,才会触发事件
上面的节流函数是当事件触发时会立即执行,然后记录一个时间,下次再触发时会判断时间过没有,过了才会再次触发
function debounce(callback, time) {
let timeId = null;
return function (e) {
if (timeId != null) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
callback.call(this, e);
timeId = null;
}, time);
}
}
给一个input输入框绑定上输入事件
<input type="text" id="input">
<script>
document.getElementById("input").addEventListener("keypress", debounce(function (e) {
console.log("asdasd");
}, 1000));
</script>
浅拷贝
let obj = { a: 1, b: 2, c: [1, 2, 3] };
// 浅拷贝
let obj1 = Object.assign({}, obj);
console.log(obj1);
console.log(obj === obj1);
obj1.c[1] = 'a';
console.log(obj, obj1);
执行结果如下
深拷贝
function deepClone(obj) {
if (typeof obj === 'object' && obj !== null) {
let isArray = Array.isArray(obj);
const newObj = isArray ? [] : {};
if (isArray) {
obj.forEach((item, index) => {
newObj[index] = deepClone(obj[index]);
});
} else {
Object.keys(obj).forEach(item => {
newObj[item] = deepClone(obj[item]);
});
}
return newObj;
} else {
return obj;
}
}
let obj = { a: 1, b: 2, c: [1, 2, 3] };
// 浅拷贝
// let obj1 = Object.assign({}, obj);
// 深拷贝
let obj1 = deepClone(obj);
console.log(obj1);
console.log(obj === obj1);
obj1.c[1] = 'a';
console.log(obj, obj1);
运行结果如下
上面方法有个问题,当对象里有循环引用时,它就傻了,一个劲的循环直到内存溢出,比如
let obj = { a: 1, b: 2, c: [1, 2, 3], d: { x: 1, y: 2 } };
// 添加循环引用
obj.d.z = obj.c;
obj.c[3] = obj.d;
// 浅拷贝
// let obj1 = Object.assign({}, obj);
// 深拷贝
let obj1 = deepClone(obj);
console.log(obj1);
console.log(obj === obj1);
obj1.c[1] = 'a';
console.log(obj, obj1);
用浅拷贝是没有问题的,但深拷贝就报错了
解决办法是在 deepClone() 方法里加一个缓存,用于存放一下递归到引用类型的属性,这样在下一次递归到这个引用的时候,直接取就是了,而不是重新创建一个
function deepClone(obj, map = new Map()) {
if (typeof obj === 'object' && obj !== null) {
// 先从map里获取一下,有就直接返回map中已经存在的值
let cache = map.get(obj);
if (cache) return cache;
let isArray = Array.isArray(obj);
const newObj = isArray ? [] : {};
// 上面判断没有的会走到这,就把当前的值存在map里
map.set(obj, true);
if (isArray) {
obj.forEach((item, index) => {
newObj[index] = deepClone(obj[index], map);
});
} else {
Object.keys(obj).forEach(item => {
newObj[item] = deepClone(obj[item], map);
});
}
return newObj;
} else {
return obj;
}
}
关于深拷贝还有一种很傻瓜的方法,方法是使用JSON对象的 parse() 和 stringify() 两个方法,先将原对象转成字符串,然后再将字符串转成对象,这样出来的新对象就是一个深拷贝的对象了,且这种方法同样能解决循环引用的拷贝
但是当对象中有 function 时,就不灵了,所以只限于对数组或者对象的拷贝
let obj = { a: 1, b: 2, c: [1, 2, 3], d: { x: 1, y: 2 } };
let obj1 = JSON.parse(JSON.stringify(obj));
// // 添加循环引用
obj.d.z = obj.c;
obj.c[3] = obj.d;
console.log(obj1);
console.log(obj === obj1);
obj1.c[1] = 'a';
console.log(obj, obj1);
js里通过 addEventListener(type, listener [, options]) 方法给元素添加事件时,有三个参数
js里事件的执行顺序为 捕获事件1 -> 捕获事件2 -> 冒泡事件2 -> 冒泡事件1 就是先一层一层的往里触发(捕获过程),然后再从最里层往外一层一层的触发(冒泡过程)
比如下图中,外面的大圆是 parent-box , 里面的小圆是 child-box
代码:
<style type="text/css">
#parent-box {
width: 300px;
height: 300px;
background-color: darkcyan;
margin: 0 auto;
border-radius: 150px;
}
#child-box {
position: relative;
left: 50px;
top: 50px;
width: 200px;
height: 200px;
background-color: aquamarine;
vertical-align: middle;
border-radius: 100px;
}
</style>
<div id="parent-box">
<div id="child-box"></div>
</div>
当分别给它们添加上事件后
document.getElementById("parent-box").addEventListener("click", function (e) {
console.log("parent-box clicked! 捕获事件");
}, true);
document.getElementById("child-box").addEventListener("click", function (e) {
console.log("child-box clicked! 捕获事件");
}, true);
document.getElementById("parent-box").addEventListener("click", function (e) {
console.log("parent-box clicked! 冒泡事件");
});
document.getElementById("child-box").addEventListener("click", function (e) {
console.log("child-box clicked! 冒泡事件");
});
点击大圆执行结果如下
点击小圆执行结果如下
注:当addEventListener() 方法的第三个参数不传时,默认为冒泡事件
当只传一个参数时,不管传啥,true, false, 1, 2, undefined, null… 统统都是捕获事件
如果想传多个参数时,可以这样写:注:当addEventListener(“click”, function(){}, {capture: true, once: ture, passive: true});
后端程序员写js的时候,基本上都碰到过这种问题:页面上有些输入框是通过点击某个按钮添加到页面上的,然后在js里定义的给这种元素添加事件的事件方法就不生效了。
这种情况可以用事件委托的方式来解决,原理是通过对父元素添加事件来管理子元素的事件
封装方法如下:
/**
* el: 父元素选择器
* type: 事件类型
* cb: 事件回调
* targetEl: 子元素选择器
*/
function addEventDelegate(el, type, cb, targetEl) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
if (!targetEl) {// 如果被代理的事件元素没传,则给父级绑上相应的事件
el.addEventListener(type, cb);
} else {
el.addEventListener(type, function (e) {
const target = e.target;
if (target.matches(targetEl)) {
cb.call(target, e);
}
});
}
}
用法:
<div id="parent-box">
<p class="item">java</p>
<p class="item">javascript</p>
<p>nodejs</p>
</div>
<script type="text/javascript">
addEventDelegate("#parent-box", "click", function (e) {
console.log(this.innerHTML);
}, ".item");
</script>
当点击有 class=”item” 类样式的元素时,会在控制台里打印出元素的内容