防抖,即短时间内大量触发同一事件,只会执行一次函数
,实现原理为设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到xx毫秒内无第二次操作
,防抖常用于搜索框/滚动条的监听事件处理,如果不做防抖,每输入一个字/滚动屏幕,都会触发事件处理,造成性能浪费。
分解需求:
细节处理:
this
的指向event
对象function debounce(func, wait){
let timeout
return function(){
let context = this
let args = arguments
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(this,args)
},wait)
}
}
防抖是延迟执行
,而节流是间隔执行
,函数节流即每隔一段时间就执行一次
,和防抖的区别在于,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器。目前有两种方式实现节流,一种是使用时间戳,另一种是使用定时器
使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
function throttle(func,awit){
let context,args
let previous = 0
return function(){
context = this
args = arguments
let now = +new Date()
//判断当前时间-之前时间如果大于时间周期,则执行
if(now - previous > awit){
func.apply(context,args)
previous = now
}
}
}
这种方法是
事件首次触发就会执行
事件停止后会立刻停止执行
当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
function throttle(func,wait){
let context,args,timeout
return function(){
context = this
args = arguments
if(!timeout){
timeout = setTimeout(function(){
timeout = null
func.apply(context,args)
},wait)
}
}
}
这种方法是
由于这两种方法会有不一样的效果,我们可以将两者混合一起使用,这样会得到两者共同的特点
首次触发会执行,并且也会有最后一次执行
function throttle(func,wait){
let context,args,timeout
let previous = 0
//定时器延迟执行的函数
let later = function(){
previous = +new Date()
timeout = null
func.apply(context,args)
}
let throttled function(){
context = this
args = arguments
let now = +new Date()
let remaing = wait - (now - previous)
//判断是否有剩余时间,也就是判断是否是首次触发和是否还有剩余时间
if(remaing <= 0){
if(timeout){
clearTimeout(timeout)
timeout = null
}
func.apply(context,args)
previous = +new Date()
}
//判断有剩余时间,再判断是否有定时器,如果没有则设置定时器,也就是最后一次执行
else if(!timeout){
timeout = setTimeout(later,remaing)
}
}
return throttled
}
call
、apply
、bind
先上终版实现代码:
//在函数对象原型链上增加mycall属性
Function.prototype.mycall = function(context){
var context = context || window; //判断传过来的对象是否为空,为空则指向全局执行上下文
context.fn = this //将调用者赋给 context 的一个属性
var args = [] //定义一个用来存放传过来参数的类数组对象
for(let i=1;i<arguments.length;i++){//将类数组对象arguments除第一个外其他放进数组里
args.push(arguments[i])
}
var result=context.fn(...args) //执行调用者函数,并接收返回参数
delete context.fn //删除调用者的函数
return result //返回结果
}
举个例子:
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
注意两点:
那么我们该怎么模拟实现这两个效果呢?
试想当调用 call 的时候,把 foo 对象改造成如下:
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
这个时候 this 就指向了 foo,是不是很简单呢?
但是这样却给 foo 对象本身添加了一个属性,这可不行呐!
不过也不用担心,我们用 delete 再删除它不就好了~
所以我们模拟的步骤可以分为:
以上个例子为例,就是:
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。
apply
与call
并没有太多不同,只是在参数方面,call
是一个一个传参,而apply
是多个参数的数组传参(或者类数组对象)。
终版代码:
Function.prototype.myCall = function(context = window, ...args) {
let fn = Symbol("fn");
context[fn] = this;
let res = context[fn](...args);//重点代码,利用this指向,相当于context.caller(...args)
delete context[fn];
return res;
}
最终代码:
Function.prototype.mybind = function(context){
if(typeof this !==‘function‘){
throw new TypeError(‘Errror‘)
}
const _this = this;
const args = [...arguments].slice(1);
return function F(){
return res = this instanceof F ? new _this(...args,...arguments)
: _this.apply(context,args.concat(...arguments))
}
}
bind
与call
、apply
同为更改this指向的方法,但bind
同时也需要执行以下的任务:
改变this
指向
由于需要延迟执行,需要返回一个函数
参数传入可分两次传入
当返回的函数作为构造器时,需要使的原有的this失效而让this返回指向实例
需要返回的函数原型与调用相同
我们看new
都做了什么:
创建一个新对象,并继承其构造函数的prototype
,这一步是为了继承构造函数原型上的属性和方法
执行构造函数,方法内的this
被指定为该新实例,这一步是为了执行构造函数内的赋值操作
返回新对象(规范规定,如果构造方法返回了一个对象,那么返回该对象,否则返回第一步创建的新对象)
上代码:
function objectFactory() {
//使用一个新的对象,用于接收原型并返回
var obj = new Object(),
//将第一个参数(也就是构造函数)进行接收
Constructor = [].shift.call(arguments);
//将原型赋给新对象的_proto_
obj.__proto__ = Constructor.prototype;
//利用构造函数继承将父函数的属性借调给子函数
var ret = Constructor.apply(obj, arguments);
//如果构造函数已经返回对象则返回他的对象
//如果构造函数未返回对象,则返回我们的新对象
return ret instanceof Object ? ret : obj;
};
方法比较繁琐点,但兼容性好点,不失为一种方法。
const unique = function(arr){
let newarr = []
let isrepeat
for(let i =0;i<arr.length;i++){
isrepeat=false
for(let j=0;j<newarr.length;j++){
if(arr[i] === newarr[j]){
isrepeat=true
break
}
}
if(!isrepeat) newarr.push(arr[i])
}
return newarr
}
基本思路:如果索引不是第一个索引,说明是重复值。
const unique = function(arr){
let res
return res = arr.filter((item,index) => {
return arr.indexOf(item) === index
})
return res
}
得益于Map的数据结构,查询速度极快,所以所消耗时间也极少
const unique = function(arr){
const newarr = []
const map = new Map()
for(let i =0;i<arr.length;i++){
if1(!map.get(arr[i])){
map.set(arr[i],1)
newarr.push(arr[i])
}
}
return newarr
}
甚至可以一行代码实现
const unique = function(arr){
return [...new Set(arr)]
}
对于[1, [1,2], [1,2,3]]
这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 3]
这样的一维数组呢:
对于这种树状结构,最方便的方式就是用递归
function flatten(arr) {
let res = []
for(let i =0;i<arr.length;i++){
if(Array.isArray(arr[i])){
res=res.concat(flatten(arr[i]))
}else{
res.push(arr[i])
}
}
return res
}
function flatten(arr) {
return arr.reduce((prev,next) => {
return prev.concat(next instanceof Array ? flatten(next) : next)
},[])
}
const arr = [1, [1,2], [1,2,3]]
arr.flat(Infinity) // [1, 1, 2, 1, 2, 3]
深浅拷发生在JavaScript的引用数据类型中。
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
以下几个方法都可以实现浅拷贝。
const returnedTarget = Object.assign(target, source);
以上方法都是实现浅拷贝的方法,他们对于首层元素都会一一复制属性,但是如果是多层引用的话,也只会复制地址,不会复制值。
以下用concat()
做示例
const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2;
cloneArray.push(6);
console.log(originArray); // [1,[1,2,3,4],{a:2}]
console.log(cloneArray) // [1,[1,2,3,4],{a:2},[6]]
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
实现深拷贝的方法有两种:
JSON
对象中的parse
和stringify
JSON.stringify
:是将一个 JavaScript
值转成一个 JSON
字符串。
JSON.parse
:是将一个 JSON
字符串转成一个 JavaScript
值或对象。
const originArray = [1,2,3,4,5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false
const originObj = {a:‘a‘,b:‘b‘,c:[1,2,3],d:{dd:‘dd‘}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = ‘aa‘;
cloneObj.c = [1,1,1];
cloneObj.d.dd = ‘doubled‘;
console.log(cloneObj); // {a:‘aa‘,b:‘b‘,c:[1,1,1],d:{dd:‘doubled‘}};
console.log(originObj); // {a:‘a‘,b:‘b‘,c:[1,2,3],d:{dd:‘dd‘}};
确实实现深拷贝,但是却也是有着缺陷:
该方法转换时undefined
、function
、symbol
会在转换过程中被忽略。
递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值
的操作
function cloneDeep(source,hash = new WeakMap()){
if(! typeof source === ‘object‘) return source
//如果hash 中存在就直接返回,避免循环引用
if(hash.has(source)) return hash.get(source)
const targetObj = Array.isArray(source) ? [] : {}
//在 hash 存储复制的对象
hash.set(source,targetObj)
//将循环复制对象里的属性
for(let key in source){
//对原型上的属性不进行处理
if(source.hasOwnProperty(key)){
//判断 遍历的是不是一个对象
if(source[key] && typeof source[key] === ‘object‘){
// targetObj[keyt] = Array.isArray(source[key]) ? [] : {}
//递归深拷贝
targetObj[key] = cloneDeep(source[key],hash)
}else{
targetObj[key] = source[key]
}
}
}
return targetObj
}
//主要使用递归的方式进行模拟
let i =0
function newSetTime(func,mine){
function insed(){
i++
func()
setTimeout(insed,mine)
}
setTimeout(insed,mine)
}
function like(){
console.log(i)
}
newSetTime(like,1000)
function getType(obj){
if(obj === null) return obj;
return typeof obj == ‘object‘ ? Object.prototype.toString.call(obj).replace(‘[object ‘,‘‘).replace(‘]‘,‘‘).toLowerCase():typeof obj;
}
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4)
function add() {
let args = [].slice.call(arguments);
let fn = function(){
let fn_args = [].slice.call(arguments)
return add.apply(null,args.concat(fn_args))
}
fn.toString = function(){
return args.reduce((a,b)=>a+b)
}
return fn
}
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
console.log(add(1)(2, 3)(4)); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
const arr = [1,2,3,4,5,6,7,8,9,10];
const shuffle = ([...arr]) => {
let m = arr.length;
while (m) {
const i = Math.floor(Math.random() * m--);
[arr[m], arr[i]] = [arr[i], arr[m]];
}
return arr;
};
console.log(shuffle(arr))
// [10, 9, 7, 5, 6, 4, 1, 2, 8, 3]
原文:https://www.cnblogs.com/chuncode/p/13489625.html