JavaScript 手写常用代码
2021-03-25 09:28
标签:github issue lower 结构 实例 指定 efi var 链式 防抖,即 分解需求: 细节处理: 防抖是 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。 这种方法是 事件首次触发就会执行 事件停止后会立刻停止执行 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。 这种方法是 由于这两种方法会有不一样的效果,我们可以将两者混合一起使用,这样会得到两者共同的特点 首次触发会执行,并且也会有最后一次执行 先上终版实现代码: 举个例子: 注意两点: 那么我们该怎么模拟实现这两个效果呢? 试想当调用 call 的时候,把 foo 对象改造成如下: 这个时候 this 就指向了 foo,是不是很简单呢? 但是这样却给 foo 对象本身添加了一个属性,这可不行呐! 不过也不用担心,我们用 delete 再删除它不就好了~ 所以我们模拟的步骤可以分为: 以上个例子为例,就是: fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。 终版代码: 最终代码: 改变 由于需要延迟执行,需要返回一个函数 参数传入可分两次传入 当返回的函数作为构造器时,需要使的原有的this失效而让this返回指向实例 需要返回的函数原型与调用相同 我们看 创建一个新对象,并继承其构造函数的 执行构造函数,方法内的 返回新对象(规范规定,如果构造方法返回了一个对象,那么返回该对象,否则返回第一步创建的新对象) 上代码: 方法比较繁琐点,但兼容性好点,不失为一种方法。 基本思路:如果索引不是第一个索引,说明是重复值。 得益于Map的数据结构,查询速度极快,所以所消耗时间也极少 甚至可以一行代码实现 对于 对于这种树状结构,最方便的方式就是用递归 深浅拷发生在JavaScript的引用数据类型中。 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 以下几个方法都可以实现浅拷贝。 以上方法都是实现浅拷贝的方法,他们对于首层元素都会一一复制属性,但是如果是多层引用的话,也只会复制地址,不会复制值。 以下用 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。 实现深拷贝的方法有两种: 确实实现深拷贝,但是却也是有着缺陷: 该方法转换时 递归的思想就很简单了,就是对每一层的数据都实现一次 【进阶4-3期】面试题之如何实现一个深拷贝 JavaScript基础心法--深浅拷贝 带你彻底搞清楚深拷、浅拷贝、循环引用 JavaScript 手写常用代码 标签:github issue lower 结构 实例 指定 efi var 链式 原文地址:https://www.cnblogs.com/chuncode/p/13489625.html手写防抖
短时间内大量触发同一事件,只会执行一次函数
,实现原理为设置一个定时器,约定在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)
}
}
手写节流
延迟执行
,而节流是间隔执行
,函数节流即每隔一段时间就执行一次
,和防抖的区别在于,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器。目前有两种方式实现节流,一种是使用时间戳,另一种是使用定时器使用时间戳
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
手写
call
、apply
、bind
实现call
//在函数对象原型链上增加mycall属性
Function.prototype.mycall = function(context){
var context = context || window; //判断传过来的对象是否为空,为空则指向全局执行上下文
context.fn = this //将调用者赋给 context 的一个属性
var args = [] //定义一个用来存放传过来参数的类数组对象
for(let i=1;i
call实现了什么
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
模拟实现思路
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
实现apply
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;
}
实现bind
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实现了什么
bind
与call
、apply
同为更改this指向的方法,但bind
同时也需要执行以下的任务:
this
指向new的实现
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
indexOf() + filter()
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
Set
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
reduce + 递归
function flatten(arr) {
return arr.reduce((prev,next) => {
return prev.concat(next instanceof Array ? flatten(next) : next)
},[])
}
ES6的flat()
const arr = [1, [1,2], [1,2,3]]
arr.flat(Infinity) // [1, 1, 2, 1, 2, 3]
深浅拷贝
浅拷贝
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/parse方法
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
}
setTimeout模拟实现setInterval
//主要使用递归的方式进行模拟
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
实现多参数的链式调用
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]
搬运文章