阅读 4441

JS十二个进阶知识点,你会写几个?

几种继承分析优缺点

原型链继承
            function Parent () {
                this.names = ["舔狗的泪"];
            }
            
            function Child () {
            
            }
            
            Child.prototype = new Parent();
            
            let child1 = new Child();
复制代码

缺点1:不能向Parent里面传参。

    child1.names.push(1)
    let child2=new Child()
    console.log(child2.names) // ["舔狗的泪",1]
复制代码

缺点2:多个实例对引用类型的操作会被修改

call继承
            function Parent () {
                this.names = "舔狗的泪";
            }
            
            Parent.prototype.getName=()=>{}
            
            function Child () {
                Parent.call(this,'可以传参')
            }
            
            let child1 = new Child();
复制代码

优点:可以传参。
缺点:child1拿不到Parent原型上面的方法

组合继承
            function Parent () {
                this.names = "舔狗的泪";
            }
            
            Parent.prototype.getName=()=>{}
            
            function Child () {
                Parent.call(this,'可以传参')
            }
            
            Child.prototype=new Parent()
            
            let child1 = new Child();
复制代码

优点:可以传参 可以拿到父原型上面的方法。
缺点:new Child()时候 Child里面的Parent方法执行了一次,Child.prototype=new Parent()又执行了Parent方法。

寄生继承
     function createP(original) {
         //下面一行的代码意思 clone.prototype={ name: '舔狗的泪' }
        var clone = Object.create(original); 
        clone.getName  = function () {
          // 以某种方式来增强对象
          console.log(this.name)
        };
        return clone; 
      }
    
     var parent = { name: '舔狗的泪' };
     var child = createP(parent);
复制代码

缺点1:不能向Parent里面传参。
缺点2:多个实例对引用类型的操作会被修改

寄生组合继承
            function Parent () {
                this.names = ["舔狗的泪"];
            }
            
            Parent.prototype.getName=()=>{}
            
            function Child () {
                Parent.call(this,'可以传参')
            }
            
            Child.prototype=Object.create(Parent.prototype)
            Child.prototype.fn=()=>{}
            
            let child1 = new Child();
复制代码

上面的缺点都优化了

call、apply、bind实现

call 可以支持多个参数

用法:fn1.myCall1('指向','参数1','参数2')

    Function.prototype.myCall1 = function (context,...arg) {
        // 如果没有传或传的值为空对象 context指向window
        context = context || window;
        let fn = mySymbol();
        context[fn] = this; //给context添加一个方法 指向this
        // 处理参数 去除第一个参数this 其它传入fn函数
        const result = context[fn](...arg); //执行fn
        delete context[fn]; //删除方法
        //返回函数调用的返回值
        return result;
      };

复制代码

分析:context[fn] 被指向的对象context下面加一个fn属性,这个fn属性对应就是需要改变this指向的函数,context[fn]()执行。里面的this就指向context,因为是context调用的它。

apply

用法:fn.myApply('指向',['参数1','参数2']),和call基本相同。

     Function.prototype.myApply = function (context, args) {
        context = context || window;
        args = args ? args : [];
        const fn = Symbol();
        context[fn] = this;
        const result = context[fn](...args);
        delete context[fn];
        return result;
      };
复制代码
bind

bind有必要分析一波,大多数人真不一定懂。用法:fn.myBind('指向','参数1')('参数2')。

    Function.prototype.myBind = function (context) {
        if (typeof this !== 'function') {
          throw new Error('请bind一个函数');
        }

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);

        var fBound = function () {
          var funArgs = Array.prototype.slice.call(arguments);
          return self.apply(
            this instanceof fNOP ? this : context,
            args.concat(funArgs)
          );
        };
        var fNOP = function () {};
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
      };

复制代码

为什么要做这一行判断this instanceof fNOP ? this : context,当bind后作为一个构造函数的时候,是不用改变this的指向。结合看下图,此时 fBound 中的this是指向a的 。this instanceof fNOP ===true

    let A=Animal.bind(Cat,name)
    let a=new A()
复制代码

为啥要写下面几行代码?
当Animal的原型上存在其他方法,a是拿不到的。

        var fNOP = function () {};
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
复制代码

那为啥不直接 fBound.prototype=this.prototype?
因为如果fBound.prototype=this.prototype后,fBound.prototype和this.prototype指针指向同一个内存空间,fBound.prototype改变后会影响this.prototype

new的实现原理

  1. 创建一个空对象
  2. 这个新对象 原型 的连接
  3. 执行构造函数方法,属性和方法指向创建的对象
  4. 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
        function _new(){
            let targetObj={}
            let [constructor,...args]=[...arguments]
            targetObj.__proto__=constructor.prototype
            let result =constructor.apply(targetObj,args)
            if(result&&(typeof (result)==='object'||typeof (result)==='function')){
                return result
            }

            return targetObj
        }
复制代码

compose

redux applyMiddleware 源码的精华也是compose方法,把中间件串起来执行。大家有空可以看看redux源码,写的真的牛逼。我高仿源码compose带传参功能。

        function middleware1(x){
            return x+1
        }
        function middleware2(x){
            return x+1
        }
        function middleware3(x){
            return x+1
        }
        function middleware4(x){
            return x+1
        }
        function compose(...funcs) {
            return arg => funcs.reduceRight((composed, f) => f(composed), arg);
        }
        compose(middleware1,middleware2,middleware3,middleware4)(0)
复制代码

深拷贝

没有用WeakMap版本的,循环引用会死循环导致内存泄漏

       function deepClone1(obj){
            if(typeof obj==='object'&&obj!==null){
                let obj1=Array.isArray(obj) ? [] : {}
                for (let key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        obj1[key] = deepClone(obj[key]) // 递归拷贝
                    }
                }
                return obj1
            }
            return obj
        }
        let test={name:'aa',age:18}
        test.a=test
        let test1=deepClone1(test) //会报错 ,内存溢出
复制代码

引用WeakMap,可以处理上面的溢出问题,Map和WeakMap都可以处理

     function deepClone2(obj, hash = new WeakMap()){
            if(typeof obj==='object'&&obj!==null){
                if (hash.has(obj)) {
                    return hash.get(obj);
                }
                let obj1=Array.isArray(obj) ? [] : {}
                hash.set(obj, obj1);
                for (let key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        obj1[key] = deepClone2(obj[key],hash) 
                    }
                }
                return obj1
            }
            return obj
        }
复制代码

数组扁平

   [1,2,3,[4,5,[6,7]]].flat(Infinity)  //[1, 2, 3, 4, 5, 6, 7]
复制代码
    function flatter(arr) {
            if (!arr.length) return;
            return arr.reduce(
              (pre, next) =>
                Array.isArray(next) ? [...pre, ...flatter(next)] : [...pre, next],
              []
            );
        }
复制代码

函数柯里化

fn.length指的是addFn参数长度,因为有a,b,c所以长度为3

  let curry = (fn, ...args) => args.length < fn.length ? (...arguments) => curry(fn, ...args, ...arguments) : fn(...args);

  function addFn(a, b, c) {
      return a + b + c;
  }
  var add = curry(addFn);
  
  add(1,2,3) //6
  add(1,2)(3) //6
  add(1)(2)(3) //6
复制代码

webpack中AsyncSeriesWaterfallHook 钩子

这是个异步串行钩子,下一个任务拿到上一个任务的返回值。下图是用法。
webpack事件流核心就是tapable,tapables上的9个钩子控制着plugins串联执行

	{ AsyncSeriesWaterfallHook } = require('tapable')
	let myAsyncSeriesWaterfallHook=new AsyncSeriesWaterfallHook(['name'])
	myAsyncSeriesWaterfallHook.tapAsync('1',(name,cb)=>{
		setTimeout(()=>{
			console.log('1',name)
			cb(null,'aa')
		},1000)
	})
	myAsyncSeriesWaterfallHook.tapAsync('2',(name,cb)=>{
		setTimeout(()=>{
			console.log('3',name)
			cb(null,'bb')
		},1000)
	})
	myAsyncSeriesWaterfallHook.tapAsync('3',(name,cb)=>{
		setTimeout(()=>{
			console.log('3',name)
			cb(null,'cc')
		},1000)
	})

	myAsyncSeriesWaterfallHook.callAsync('yang',()=>{
		console.log('end')
	})
    
        // 1 yang
        // 2 aa
        // 3 bb
        // end   
复制代码

没有借助async await去实现的异步串行钩子,next用的真巧妙。

    
class AsyncSeriesWaterfallHook{
    constructor() {
        this.hooks = [];
    }
    tapAsync () {
        this.hooks.push(arguments[arguments.length-1]);
    }
    callAsync () {
        let args = Array.from(arguments); //将传进来的参数转化为数组
        let done = args.pop(); // 取出数组的最后一项 即成功后的回调函数
        let index = 0;
        let _this = this;
        function next(err, data) {
            if(index>=_this.hooks.length) return done();
            if (err) return done(err);
            let fn = _this.hooks[index++];
            if (index == 1) {
                fn(...args, next)
            } else {
                fn(data, next)
            }
        }
        next()
    }
}
复制代码

ES6 class

juejin.cn/post/702429…

Map Set

juejin.cn/post/702540…

Promise

juejin.cn/post/702689…

async await

juejin.cn/post/702910…

总结

          她原地怔了下,然后就上楼,我回到宿舍没一会便收到她发的信息,写了很多……大致内容就是给我发了一张好人卡,那是我人生中的第一次对喜欢的女孩表白,也是我第一次体会到心痛的滋味,我不知道怎么回复她,就对着我们的聊天记录翻了一整宿……
          后面的一段时间我每天都过的浑浑噩噩,对任何事都提不起来兴趣,我也没有帮她再带早餐了,我和她的关系也变得很微妙了,我害怕她看到我,每次上课我都会找个离她较远的位置,我们俩也没了任何消息来往,但我依然想她……
          英雄联盟统治了我们大学四年,网吧,寝室基本都是这款游戏,当时学校有人组织了英雄联盟比赛,每个专业派一支队伍去打比赛,来自艾欧尼亚钻一的我成为参赛的一员,随着比赛不断的胜利,我们也备受班上同学关注,很多同学都会主动和我打招呼,聊天,约我一起打游戏,那是我第一次感到我至少不是一无是处,我们来到了4强,比赛前晚,同学纷纷对我们发表祝福的话,她晚上也给我单独发了条消息,说了些加油鼓励的话,我抑制住内心的激动回复了句:好的。
          4强我祭出我3000场的韦恩,在前期团战劣势情况下,后期一团战凭借我各种风骚走位一波 '喷它Q' 终结了对面,当天晚上我成了学校贴吧讨论热点……(未完待续)

文章分类
前端
文章标签