1. 实现js的节流和防抖函数,两者的区别是什么?
区别
- 防抖:我们自定义规定条件内,频繁触发的模式下,只执行一次【可以执行第一次,或只执行最后一次】
节流:降低触发的频率。在频繁触发的模式下,间隔规定时间内执行一次
代码
```javascript /*
- debounce:函数防抖
- @params
- func「function,required」:最后要执行的函数
- wait「number」:设定的频发触发的频率时间,默认值是300
- immediate「boolean」:设置是否是开始边界触发,默认值是false
- @return
- func执行的返回结果 */ function debounce (func, wait, immediate) { // 1. 确保传入的参数 func 是函数 if (typeof func !== ‘function’) throw new TypeError(‘func must be required and be an function!’) // 2. 考虑到可能出现 debounce(func, false) 的情况 if (typeof wait === ‘boolean’) { immediate = wait wait = 300 } // 3. 确保传入的参数 wait、immediate 类型符合要求 if (typeof wait !== ‘number’) wait = 300 if (typeof immediate !== ‘boolean’) immediate = false
// 4. 定义 timer => 保存定时器;定义 result => 保留函数返回值 var timer = null, result
// 5. return function proxy () { var runNow = !timer && immediate, params = [].slice.call(arguments), self = this if (timer) clearTimeout(timer) //干掉之前的 timer = setTimeout(function () { if (timer) { //当最新的结束后,把没用的这个定时器也干掉「良好的习惯」 clearTimeout(timer) timer = null } !immediate ? (result = func.apply(self, params)) : null }, wait) runNow ? (result = func.apply(self, params)) : null return result } }
/*
- throttle:函数节流
- @params
- func「function,required」:最后要执行的函数
- wait「number」:设定的频发触发的频率时间,默认值是300
- @return
func执行的返回结果 */ function throttle (func, wait) { // 1. 确保传入参数 func 是函数 if (typeof func !== ‘function’) throw new TypeError(‘func must be required and be an function!’) // 2. 确保传入的参数 wait 是 Number 类型,否则设置默认值 if (typeof wait !== ‘number’) wait = 300
var timer = null, // timer => 保存定时器 previous = 0, // 上一次触发执行的时间 result // result => 保留函数返回值
// 3. return function proxy () { var now = +new Date(), remaining = wait - (now - previous), self = this, params = [].slice.call(arguments) if (remaining <= 0) { // 立即执行即可 if (timer) { clearTimeout(timer) timer = null } result = func.apply(self, params) previous = +new Date() } else if (!timer) { // 没有达到间隔时间,而且之前也没有设置过定时器,此时我们设置定时器,等到remaining后执行一次 timer = setTimeout(function () { if (timer) { clearTimeout(timer) timer = null } result = func.apply(self, params) previous = +new Date() }, remaining) } return result } } ```
2. 实现js中的深拷贝
```javascript // 拷贝目标 let obj = { url: ‘/api/list’, method: ‘GET’, cache: false, timeout: 1000, key: Symbol(‘KEY’), big: 10n, n: null, u: undefined, headers: { ‘Content-Type’: ‘application/json’, post: {
'X-Token': 'xxx'
} }, arr: [10, 20, 30], reg: /^\d+$/, time: new Date(), fn: function () { console.log(this); }, err: new Error(‘xxx’) }; obj.obj = obj; //================ 方法一(最便捷)================= let newObj = JSON.parse(JSON.stringify(obj));
//================ 方法二(实现数组和对象深拷贝)=================
function deepClone(obj, hash = new WeakMap()) {// 弱引用,不要用Map
// null 和 undefiend 是不需要拷贝的
if(obj == null) { return obj; }
if(obj instanceof RegExp) { return new RegExp(obj); }
if(obj instanceof Date) {return new Date(obj);}
// 函数是不需要拷贝
if(typeof obj !=’object’) return obj;
// 说明是一个对象类型
if(hash.get(obj)) return hash.get(obj)
let cloneObj = new obj.constructor; // [] {}
hash.set(obj, cloneObj);
for (let key in obj) { // in 会遍历当前对象上的属性和proto 上的属性
// 不拷贝 对象的proto 上的属性
if (obj.hasOwnProperty(key)) {
// 如果值还有可能是对象,就继续拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
// 区分对象和数组 Object.prototype.toString.call
}
return cloneObj
}
let newObj2 = deepClone(obj)// 深拷贝
**_备注:_**
- **方法一弊端:**
- 不允许出现套娃操作=>obj.obj = obj;
- 属性值不能是BigInt Uncaught TypeError: Do not know how to serialize a BigInt
- 丢失一些内容:只要属性值是 symbol/undefined/function 这些类型的
- 还有信息不准确的,例如:正则->空对象 Error对象->空对象 日期对象->字符串 ...
<a name="1hmXO"></a>
## 3. 手写call函数
```javascript
function change(context, ...params) {
// this->要执行的函数func context->要改变的函数中的this指向obj
// params->未来要传递给函数func的实参信息{数组} [100,200]
// 临时设置的属性,不能和原始对象冲突,所以我们属性采用唯一值处理
context == null ? context = window : null;
if (!/^(object|function)$/.test(typeof context)) context = Object(context);
let self = this,
key = Symbol('KEY'),
result;
context[key] = self;
result = context[key](...params);
delete context[key];
return result;
};
4. 手写apply函数
与 call 的区别就是传入的参数是数组
function myApply (context, params) {
// this->要执行的函数func context->要改变的函数中的this指向obj
// params->未来要传递给函数func的实参信息{数组} [100,200]
// 临时设置的属性,不能和原始对象冲突,所以我们属性采用唯一值处理
context == null ? (context = window) : null
if (!/^(object|function)$/.test(typeof context)) context = Object(context)
let self = this,
key = Symbol('KEY'),
result
context[key] = self
result = context[key](...params)
delete context[key]
return result
}
5. 手写bind函数
function bind (context, ...params) {
// this->func context->obj params->[100,200]
let self = this
return function proxy (...args) {
// args->事件触发传递的信息,例如:[ev]
params = params.concat(args)
return self.call(context, ...params)
}
}
6. 实现柯里化函数
const curring = () => {
let arr = [];
const add = (...params) => {
arr = arr.concat(params);
return add;
};
add.toString = () => {
return arr.reduce((total, item) => {
return total + item;
});
};
return add;
};
let add = curring();
let res = add(1)(2)(3);
console.log(res); //->6
add = curring();
res = add(1, 2, 3)(4);
console.log(res); //->10
add = curring();
res = add(1)(2)(3)(4)(5);
console.log(res); //->15
7. 手写一个观察者模式
https://gitee.com/jw-speed/jiagouke1-node/tree/master/1.promise
class Subject{ // 被观察者的类 被观察者 需要将观察者收集起来
constructor(name){
this.name = name;
this.state = '非常开心'
this.observers = [];
}
attach(o){ // 进行收集
this.observers.push(o); // on
}
setState(newState){
this.state = newState;
this.observers.forEach(o=>o.update(this.name,newState)) // emit
}
}
class Observer{ // 观察者
constructor(name){
this.name = name;
}
update(s,state){
console.log(this.name+":" + s + '当前'+state);
}
}
// vue 数据变了(状态) 视图要更新 (通知依赖的人)
let s = new Subject('小宝宝');
let o1 = new Observer('爸爸');
let o2 = new Observer('妈妈');
s.attach(o1)
s.attach(o2)
s.setState('不开心了')
s.setState('开心了')
8. 手动实现EventEmitter(发布订阅模式)
https://gitee.com/jw-speed/jiagouke1-node/tree/master/1.promise
// 事件中心
// 解耦合
const fs = require('fs'); // 发布订阅模式 核心就是把多个方法先暂存起来,最后一次执行
let events = {
_events:[],
on(fn){
this._events.push(fn)
},
emit(data){
this._events.forEach(fn=>fn(data))
}
}
// 订阅有顺序 可以采用数组来控制
fs.readFile('./a.txt','UTF8',function (err,data) {
events.emit(data);
})
fs.readFile('./b.txt','UTF8',function (err,data) {
events.emit(data);
})
events.on(()=>{
console.log('每读取一次 就触发一次')
});
let arr = [];
events.on((data)=>{
arr.push(JSON.stringify(data))
})
events.on((data)=>{
if(arr.length === 2){ // 最终结果还是计数器
console.log('读取完毕',arr)
}
})
// 观察者模式 vue2 基于发布订阅的 (发布订阅之间是没有依赖关系的)
// 对于我们的观察者模式 观察者 被观察者 vue3 中没有使用class
9. 手动实现jsonp
function jsonp({ url, params, cb }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
window[cb] = function (data) {
resolve(data);
document.body.removeChild(script);
}
params = { ...params, cb } // wd=b&cb=show
let arrs = [];
for (let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
});
}
// 只能发送get请求 不支持post put delete
// 不安全 xss攻击 不采用
jsonp({
url: 'http://localhost:3000/say',
params: { wd: '我爱你' },
cb: 'show'
}).then(data => {
console.log(data);
});
10. 手动实现new关键字
// (1)首先创建了一个新的空对象
// (2)设置原型,将对象的原型设置为函数的 prototype 对象。
// (3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
// (4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// 实现:
function objectFactory() {
let newObject = null,
constructor = Array.prototype.shift.call(arguments),
result = null;
// 参数判断
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag =
result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
// 使用方法
// objectFactory(构造函数, 初始化参数);
11. 手动实现 Object.assign
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
不支持 symbol 属性,因为ES5 中根本没有 symbol
if (typeof Object.myAssign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "myAssign", {
value: function myAssign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
let to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
let a = {
name: "advanced",
age: 18
}
let b = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let c = Object.myAssign(a, b);
console.log(c)
12. 实现 解析url参数为对象 的函数
方法一——常规
function queryURLParams () {
const res = {}
const search = location.search.split('?')[1] //去掉前面的?
const hashIndex = search.indexOf('#')
if (hashIndex) obj['HASH'] = wellText
search.split('&').forEach(paramStr => {
let [key, val] = paramStr.split('=')
res[key] = val
})
return res
}
方法二——利用A元素对象的相关属性「OOP」
function queryURLParams(attr) {
let self = this,
obj = {}
// A元素对象:hash/host/hostname/pathname/protocol/search...
// 基于这些私有属性即可获取到URL中每一部分的信息
let link = document.createElement('a')
link.href = self
let { search, hash } = link
link = null
if (hash) obj['HASH'] = hash.substring(1)
if (search) {
search
.substring(1)
.split('&')
.forEach(item => {
let [key, value] = item.split('=')
obj[key] = value
})
}
return typeof attr === 'undefined' ? obj : obj[attr] || ''
}
let str = 'http://www.xxx.com/?lx=0&from=weixin&n=100#video'
console.log(str.queryURLParams()) //=>{lx:0,from:'weixin',n:100,HASH:'video'}
console.log(str.queryURLParams('lx')) //=>0
方法三——正则
/*
* queryURLParams:获取URL地址问号和面的参数信息(可能也包含HASH值)
* @params
* attr:要获取的参数值的 key
* @return
* [object]把所有问号参数信息以键值对的方式存储起来并且返回
*/
function queryURLParams(attr) {
let self = this,
obj = {}
let reg1 = /([^?&=#]+)=([^?&=#]+)/g,
reg2 = /#([^?&=#]+)/g
self.replace(reg1, (_, key, value) => (obj[key] = value))
self.replace(reg2, (_, hash) => (obj['HASH'] = hash))
return typeof attr === 'undefined' ? obj : obj[attr] || ''
}
let str = 'http://www.xxx.com/?lx=0&from=weixin&n=100#video'
console.log(str.queryURLParams()) //=>{lx:0,from:'weixin',n:100,HASH:'video'}
console.log(str.queryURLParams('lx')) //=>0
方法四——字符串截取
function queryURLParams(attr) {
let self = this,
obj = {}
let askIndex = self.indexOf('?'),
wellIndex = self.indexOf('#'),
askText = '',
wellText = ''
askIndex === -1 ? (askIndex = self.length) : null
wellIndex === -1 ? (wellIndex = self.length) : null
if (askIndex < wellIndex) {
askText = self.substring(askIndex + 1, wellIndex)
wellText = self.substring(wellIndex + 1)
} else {
askText = self.substring(askIndex + 1)
wellText = self.substring(wellIndex + 1, askIndex)
}
// 井号获取信息了
if (wellText) obj['HASH'] = wellText
// 问号获取信息了
if (askText) {
askText.split('&').forEach(item => {
let [key, value] = item.split('=')
obj[key] = value
})
}
return typeof attr === 'undefined' ? obj : obj[attr] || ''
}
13. js格式化数字(每三位加逗号)
方法一
function toThousands(num) {
var num = (num || 0).toString(), result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) { result = num + result; }
return result;
}
方法二(正则):
function millimeter(num) {
return num.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',');
}
let num = "15628954"; //=>"15,628,954" 千分符
console.log(millimeter(num));
14. 手写instanceof关键字
function instance_of(example, classFunc) {
let classFuncPrototype = classFunc.prototype,
proto = Object.getPrototypeOf(example); // example.__proto__
while (true) {
if (proto === null) {
// Object.prototype.__proto__ => null
return false;
}
if (proto === classFuncPrototype) {
// 查找过程中发现有,则证明实例是这个类的一个实例
return true;
}
proto = Object.getPrototypeOf(proto);
}
}
// 实例.__proto__===类.prototype
let arr = [];
console.log(instance_of(arr, Array));
console.log(instance_of(arr, RegExp));
console.log(instance_of(arr, Object));
let myInstanceof = (target,origin) => {
while(target) {
if(target.__proto__===origin.prototype) {
return true
}
target = target.__proto__
}
return false
}
let a = [1,2,3]
console.log(myInstanceof(a,Array)); // true
console.log(myInstanceof(a,Object)); // true
https://mp.weixin.qq.com/s/F9InP5zW-1YDothP37jBKQ
15. 手写数组去重的方法?
//======================方法一(indexOf)====================
/*
* 新建数组temp,遍历传入数组
* 判断值在不在新数组:不在,push进temp;否则跳过继续下一个循环
*
* IE9以下不支持数组的indexOf方法
*/
function uniq(array){
var temp = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){
if(temp.indexOf(array[i]) == -1){
temp.push(array[i]);
}
}
return temp;
}
var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
console.log(uniq(arr));
// Array(7) [7, 2, 3, 9, 10, 5, 8]
//========================方法二( Set )======================
var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
arr = new Set(arr)
console.log(arr));
// Set(7) {7, 2, 3, 9, 10, 5, 8}
//====================方法三( 双重 for 循环 )===================
function uniq(array){
var temp = [];
var index = [];
var l = array.length;
for(var i = 0; i < l; i++) {
for(var j = i + 1; j < l; j++){
if (array[i] === array[j]){
i++;
j = i;
}
}
temp.push(array[i]);
index.push(i);
}
return temp;
}
var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
console.log(uniq(arr));