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: {

    1. '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)// 深拷贝

  1. **_备注:_**
  2. - **方法一弊端:**
  3. - 不允许出现套娃操作=>obj.obj = obj;
  4. - 属性值不能是BigInt Uncaught TypeError: Do not know how to serialize a BigInt
  5. - 丢失一些内容:只要属性值是 symbol/undefined/function 这些类型的
  6. - 还有信息不准确的,例如:正则->空对象 Error对象->空对象 日期对象->字符串 ...
  7. <a name="1hmXO"></a>
  8. ## 3. 手写call函数
  9. ```javascript
  10. function change(context, ...params) {
  11. // this->要执行的函数func context->要改变的函数中的this指向obj
  12. // params->未来要传递给函数func的实参信息{数组} [100,200]
  13. // 临时设置的属性,不能和原始对象冲突,所以我们属性采用唯一值处理
  14. context == null ? context = window : null;
  15. if (!/^(object|function)$/.test(typeof context)) context = Object(context);
  16. let self = this,
  17. key = Symbol('KEY'),
  18. result;
  19. context[key] = self;
  20. result = context[key](...params);
  21. delete context[key];
  22. return result;
  23. };

4. 手写apply函数

与 call 的区别就是传入的参数是数组

  1. function myApply (context, params) {
  2. // this->要执行的函数func context->要改变的函数中的this指向obj
  3. // params->未来要传递给函数func的实参信息{数组} [100,200]
  4. // 临时设置的属性,不能和原始对象冲突,所以我们属性采用唯一值处理
  5. context == null ? (context = window) : null
  6. if (!/^(object|function)$/.test(typeof context)) context = Object(context)
  7. let self = this,
  8. key = Symbol('KEY'),
  9. result
  10. context[key] = self
  11. result = context[key](...params)
  12. delete context[key]
  13. return result
  14. }

5. 手写bind函数

  1. function bind (context, ...params) {
  2. // this->func context->obj params->[100,200]
  3. let self = this
  4. return function proxy (...args) {
  5. // args->事件触发传递的信息,例如:[ev]
  6. params = params.concat(args)
  7. return self.call(context, ...params)
  8. }
  9. }

6. 实现柯里化函数

  1. const curring = () => {
  2. let arr = [];
  3. const add = (...params) => {
  4. arr = arr.concat(params);
  5. return add;
  6. };
  7. add.toString = () => {
  8. return arr.reduce((total, item) => {
  9. return total + item;
  10. });
  11. };
  12. return add;
  13. };
  14. let add = curring();
  15. let res = add(1)(2)(3);
  16. console.log(res); //->6
  17. add = curring();
  18. res = add(1, 2, 3)(4);
  19. console.log(res); //->10
  20. add = curring();
  21. res = add(1)(2)(3)(4)(5);
  22. console.log(res); //->15

7. 手写一个观察者模式

https://gitee.com/jw-speed/jiagouke1-node/tree/master/1.promise

  1. class Subject{ // 被观察者的类 被观察者 需要将观察者收集起来
  2. constructor(name){
  3. this.name = name;
  4. this.state = '非常开心'
  5. this.observers = [];
  6. }
  7. attach(o){ // 进行收集
  8. this.observers.push(o); // on
  9. }
  10. setState(newState){
  11. this.state = newState;
  12. this.observers.forEach(o=>o.update(this.name,newState)) // emit
  13. }
  14. }
  15. class Observer{ // 观察者
  16. constructor(name){
  17. this.name = name;
  18. }
  19. update(s,state){
  20. console.log(this.name+":" + s + '当前'+state);
  21. }
  22. }
  23. // vue 数据变了(状态) 视图要更新 (通知依赖的人)
  24. let s = new Subject('小宝宝');
  25. let o1 = new Observer('爸爸');
  26. let o2 = new Observer('妈妈');
  27. s.attach(o1)
  28. s.attach(o2)
  29. s.setState('不开心了')
  30. s.setState('开心了')

8. 手动实现EventEmitter(发布订阅模式)

https://gitee.com/jw-speed/jiagouke1-node/tree/master/1.promise

  1. // 事件中心
  2. // 解耦合
  3. const fs = require('fs'); // 发布订阅模式 核心就是把多个方法先暂存起来,最后一次执行
  4. let events = {
  5. _events:[],
  6. on(fn){
  7. this._events.push(fn)
  8. },
  9. emit(data){
  10. this._events.forEach(fn=>fn(data))
  11. }
  12. }
  13. // 订阅有顺序 可以采用数组来控制
  14. fs.readFile('./a.txt','UTF8',function (err,data) {
  15. events.emit(data);
  16. })
  17. fs.readFile('./b.txt','UTF8',function (err,data) {
  18. events.emit(data);
  19. })
  20. events.on(()=>{
  21. console.log('每读取一次 就触发一次')
  22. });
  23. let arr = [];
  24. events.on((data)=>{
  25. arr.push(JSON.stringify(data))
  26. })
  27. events.on((data)=>{
  28. if(arr.length === 2){ // 最终结果还是计数器
  29. console.log('读取完毕',arr)
  30. }
  31. })
  32. // 观察者模式 vue2 基于发布订阅的 (发布订阅之间是没有依赖关系的)
  33. // 对于我们的观察者模式 观察者 被观察者 vue3 中没有使用class

9. 手动实现jsonp

  1. function jsonp({ url, params, cb }) {
  2. return new Promise((resolve, reject) => {
  3. let script = document.createElement('script');
  4. window[cb] = function (data) {
  5. resolve(data);
  6. document.body.removeChild(script);
  7. }
  8. params = { ...params, cb } // wd=b&cb=show
  9. let arrs = [];
  10. for (let key in params) {
  11. arrs.push(`${key}=${params[key]}`);
  12. }
  13. script.src = `${url}?${arrs.join('&')}`;
  14. document.body.appendChild(script);
  15. });
  16. }
  17. // 只能发送get请求 不支持post put delete
  18. // 不安全 xss攻击 不采用
  19. jsonp({
  20. url: 'http://localhost:3000/say',
  21. params: { wd: '我爱你' },
  22. cb: 'show'
  23. }).then(data => {
  24. console.log(data);
  25. });

10. 手动实现new关键字

参考链接

  1. // (1)首先创建了一个新的空对象
  2. // (2)设置原型,将对象的原型设置为函数的 prototype 对象。
  3. // (3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. // (4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
  5. // 实现:
  6. function objectFactory() {
  7. let newObject = null,
  8. constructor = Array.prototype.shift.call(arguments),
  9. result = null;
  10. // 参数判断
  11. if (typeof constructor !== "function") {
  12. console.error("type error");
  13. return;
  14. }
  15. // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  16. newObject = Object.create(constructor.prototype);
  17. // 将 this 指向新建对象,并执行函数
  18. result = constructor.apply(newObject, arguments);
  19. // 判断返回对象
  20. let flag =
  21. result && (typeof result === "object" || typeof result === "function");
  22. // 判断返回结果
  23. return flag ? result : newObject;
  24. }
  25. // 使用方法
  26. // objectFactory(构造函数, 初始化参数);

11. 手动实现 Object.assign

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
不支持 symbol 属性,因为ES5 中根本没有 symbol

  1. if (typeof Object.myAssign != 'function') {
  2. // Must be writable: true, enumerable: false, configurable: true
  3. Object.defineProperty(Object, "myAssign", {
  4. value: function myAssign(target, varArgs) { // .length of function is 2
  5. 'use strict';
  6. if (target == null) { // TypeError if undefined or null
  7. throw new TypeError('Cannot convert undefined or null to object');
  8. }
  9. let to = Object(target);
  10. for (var index = 1; index < arguments.length; index++) {
  11. var nextSource = arguments[index];
  12. if (nextSource != null) { // Skip over if undefined or null
  13. for (let nextKey in nextSource) {
  14. // Avoid bugs when hasOwnProperty is shadowed
  15. if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
  16. to[nextKey] = nextSource[nextKey];
  17. }
  18. }
  19. }
  20. }
  21. return to;
  22. },
  23. writable: true,
  24. configurable: true
  25. });
  26. }
  27. let a = {
  28. name: "advanced",
  29. age: 18
  30. }
  31. let b = {
  32. name: "muyiy",
  33. book: {
  34. title: "You Don't Know JS",
  35. price: "45"
  36. }
  37. }
  38. let c = Object.myAssign(a, b);
  39. console.log(c)

12. 实现 解析url参数为对象 的函数

方法一——常规

  1. function queryURLParams () {
  2. const res = {}
  3. const search = location.search.split('?')[1] //去掉前面的?
  4. const hashIndex = search.indexOf('#')
  5. if (hashIndex) obj['HASH'] = wellText
  6. search.split('&').forEach(paramStr => {
  7. let [key, val] = paramStr.split('=')
  8. res[key] = val
  9. })
  10. return res
  11. }

方法二——利用A元素对象的相关属性「OOP」

  1. function queryURLParams(attr) {
  2. let self = this,
  3. obj = {}
  4. // A元素对象:hash/host/hostname/pathname/protocol/search...
  5. // 基于这些私有属性即可获取到URL中每一部分的信息
  6. let link = document.createElement('a')
  7. link.href = self
  8. let { search, hash } = link
  9. link = null
  10. if (hash) obj['HASH'] = hash.substring(1)
  11. if (search) {
  12. search
  13. .substring(1)
  14. .split('&')
  15. .forEach(item => {
  16. let [key, value] = item.split('=')
  17. obj[key] = value
  18. })
  19. }
  20. return typeof attr === 'undefined' ? obj : obj[attr] || ''
  21. }
  22. let str = 'http://www.xxx.com/?lx=0&from=weixin&n=100#video'
  23. console.log(str.queryURLParams()) //=>{lx:0,from:'weixin',n:100,HASH:'video'}
  24. console.log(str.queryURLParams('lx')) //=>0

方法三——正则

  1. /*
  2. * queryURLParams:获取URL地址问号和面的参数信息(可能也包含HASH值)
  3. * @params
  4. * attr:要获取的参数值的 key
  5. * @return
  6. * [object]把所有问号参数信息以键值对的方式存储起来并且返回
  7. */
  8. function queryURLParams(attr) {
  9. let self = this,
  10. obj = {}
  11. let reg1 = /([^?&=#]+)=([^?&=#]+)/g,
  12. reg2 = /#([^?&=#]+)/g
  13. self.replace(reg1, (_, key, value) => (obj[key] = value))
  14. self.replace(reg2, (_, hash) => (obj['HASH'] = hash))
  15. return typeof attr === 'undefined' ? obj : obj[attr] || ''
  16. }
  17. let str = 'http://www.xxx.com/?lx=0&from=weixin&n=100#video'
  18. console.log(str.queryURLParams()) //=>{lx:0,from:'weixin',n:100,HASH:'video'}
  19. console.log(str.queryURLParams('lx')) //=>0

方法四——字符串截取

  1. function queryURLParams(attr) {
  2. let self = this,
  3. obj = {}
  4. let askIndex = self.indexOf('?'),
  5. wellIndex = self.indexOf('#'),
  6. askText = '',
  7. wellText = ''
  8. askIndex === -1 ? (askIndex = self.length) : null
  9. wellIndex === -1 ? (wellIndex = self.length) : null
  10. if (askIndex < wellIndex) {
  11. askText = self.substring(askIndex + 1, wellIndex)
  12. wellText = self.substring(wellIndex + 1)
  13. } else {
  14. askText = self.substring(askIndex + 1)
  15. wellText = self.substring(wellIndex + 1, askIndex)
  16. }
  17. // 井号获取信息了
  18. if (wellText) obj['HASH'] = wellText
  19. // 问号获取信息了
  20. if (askText) {
  21. askText.split('&').forEach(item => {
  22. let [key, value] = item.split('=')
  23. obj[key] = value
  24. })
  25. }
  26. return typeof attr === 'undefined' ? obj : obj[attr] || ''
  27. }

13. js格式化数字(每三位加逗号)

方法一

  1. function toThousands(num) {
  2. var num = (num || 0).toString(), result = '';
  3. while (num.length > 3) {
  4. result = ',' + num.slice(-3) + result;
  5. num = num.slice(0, num.length - 3);
  6. }
  7. if (num) { result = num + result; }
  8. return result;
  9. }

方法二(正则):

  1. function millimeter(num) {
  2. return num.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',');
  3. }
  4. let num = "15628954"; //=>"15,628,954" 千分符
  5. console.log(millimeter(num));

14. 手写instanceof关键字

  1. function instance_of(example, classFunc) {
  2. let classFuncPrototype = classFunc.prototype,
  3. proto = Object.getPrototypeOf(example); // example.__proto__
  4. while (true) {
  5. if (proto === null) {
  6. // Object.prototype.__proto__ => null
  7. return false;
  8. }
  9. if (proto === classFuncPrototype) {
  10. // 查找过程中发现有,则证明实例是这个类的一个实例
  11. return true;
  12. }
  13. proto = Object.getPrototypeOf(proto);
  14. }
  15. }
  16. // 实例.__proto__===类.prototype
  17. let arr = [];
  18. console.log(instance_of(arr, Array));
  19. console.log(instance_of(arr, RegExp));
  20. console.log(instance_of(arr, Object));
  1. let myInstanceof = (target,origin) => {
  2. while(target) {
  3. if(target.__proto__===origin.prototype) {
  4. return true
  5. }
  6. target = target.__proto__
  7. }
  8. return false
  9. }
  10. let a = [1,2,3]
  11. console.log(myInstanceof(a,Array)); // true
  12. console.log(myInstanceof(a,Object)); // true

https://mp.weixin.qq.com/s/F9InP5zW-1YDothP37jBKQ

15. 手写数组去重的方法?

  1. //======================方法一(indexOf)====================
  2. /*
  3. * 新建数组temp,遍历传入数组
  4. * 判断值在不在新数组:不在,push进temp;否则跳过继续下一个循环
  5. *
  6. * IE9以下不支持数组的indexOf方法
  7. */
  8. function uniq(array){
  9. var temp = []; //一个新的临时数组
  10. for(var i = 0; i < array.length; i++){
  11. if(temp.indexOf(array[i]) == -1){
  12. temp.push(array[i]);
  13. }
  14. }
  15. return temp;
  16. }
  17. var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
  18. console.log(uniq(arr));
  19. // Array(7) [7, 2, 3, 9, 10, 5, 8]
  20. //========================方法二( Set )======================
  21. var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
  22. arr = new Set(arr)
  23. console.log(arr));
  24. // Set(7) {7, 2, 3, 9, 10, 5, 8}
  25. //====================方法三( 双重 for 循环 )===================
  26. function uniq(array){
  27. var temp = [];
  28. var index = [];
  29. var l = array.length;
  30. for(var i = 0; i < l; i++) {
  31. for(var j = i + 1; j < l; j++){
  32. if (array[i] === array[j]){
  33. i++;
  34. j = i;
  35. }
  36. }
  37. temp.push(array[i]);
  38. index.push(i);
  39. }
  40. return temp;
  41. }
  42. var arr = [7,2,3,2,9,10,7,5,10,3,5,8,5];
  43. console.log(uniq(arr));