沙箱、防抖节流、函数柯里化、闭包、数据劫持

68 阅读5分钟

函数创建与定义的过程

  • 函数定义
    1. 在堆内存中开辟一段空间。
    2. 把函数体内的代码原封不动的存储在这段空间内。
    3. 把这段空间赋值给栈。
  • 函数调用
    1. 按照变量名内的存储地址找到堆内存中对应的存储空间
    2. 在调用栈中开辟一个新的函数执行空间
    3. 在执行空间中进行形参赋值
    4. 在执行空间中进行预解析
    5. 在执行空间中完整执行一遍函数内的代码
    6. 销毁栈创建的执行空间
  • 不会销毁的函数执行空间
    1. 当函数内返回一个引用数据类型时
    2. 并且函数外部有变量接收着这个引用数据类型
    3. 函数执行完毕 执行空间不会销毁
    4. 如果后续不需要这个变量空间了,让变量指向别的位置即可
    function fn(){
        const obj = {
            a:1,
            b:2
        }
        return obj
    }
    
    const data = fn();
    console.log(data);
    
   //如果后续不需要这个变量空间了,让变量指向别的位置即可
   res = 100;

沙箱模式

  • 沙箱模式是一种通过隔离代码环境来保证代码安全的模式。它将Js运行时放在一个安全的沙箱环境中,以避免外部代码的污染和攻击。
    1. 利用了函数内间接返回了一个函数
    2. 外部函数返回一个对象,这个对象内部写了多个函数
    function fn(){
        let a = 100;
        let b = 200;
        return {
            getA : function (){
                return a
            },
            getB : function (){
                return b
            },
            setA : function (val){
                a = val
            }
        }
    }
    
    //得到一个沙箱
    const res1 = fn();
    console.log(res1.getA()) // 100
    console.log(res1.getB()) // 200
    
    res1.setA(300);
    console.log(res1.getA()) // 300
    
    //重新得到一个沙箱
    const res2 = fn();
    console.log(res2.getA()) // 100

闭包

  • 需要一个不会被销毁的执行空间
  • 需要直接或者间接的返回一个函数
  • 内部函数使用外部函数的私有变量
  • 概念:函数里的函数
  • 优点:
    1. 可以在函数外访问函数内的变量
    2. 延长了变量的生命周期
  • 缺点:闭包函数不会销毁空间,大量使用会造成内存溢出。
    function outer(){
        let a = 100;
        let b = 200;
        
        // inner是outer的闭包函数
        function inner (){
            // 我们使用了一个a变量,但是inner自己没有;
            // 所以我用的是 外部函数outer内部的变量a;
            return a;
        }
        return inner
    }
    
    let res = outer();
    
    let outerA = res()
    console.log(outerA)

防抖和节流

  • 防抖:一段时间内连续触发事件,确保事件只执行一次,而不是每次触发都执行。
    const debounce = (func,wait)=>{
        let timeout;
        return (...args)=>{
          clearTimeout(timeout);
          timeout = setTimeout(()=>{func.apply(this,args)},wait)
        }
    }
    const debounceFn = debounce((val)=>{
        console.log(val)
    },500)
    const onInput = debounceFn(111) 
    
  • 节流:短时间内频繁调用,那么只允许在一定时间间隔内最多只执行一次。

    const throttle = (func,limit)=>{
        let inThrottle;
        return (...args)=>{
            if(!inThrottle){
                func.apply(this,args);
                inThrottle = true;
                setTimeout(()=>{ inThrottle = false},limit)
            }
        }   
    }
    const throttleFn = throttle((val)=>{
         console.log(val)
    },1000)
    const onBtn = throttleFn(123)
    
  • 总结:
    1. 防抖(debounce)更注重结束后的动作,即使事件触发n秒后,如果事件没有再次触发,则执行函数。如果在这n秒内有被重复触发,则重新计算执行时间。
    2. 节流(throttle)更注重过程中的动作,每隔一段时间就触发,不管事件触发得多频繁。

柯里化函数

  • 定义:多个参数的函数转化为一系列只接收一个参数的函数。
   function curry(fn){
       if(typeof fn !== 'function'){
           throw new Error('curry requires a function');
       }
       return function curried(...args){
           if(args.length >= fn.length){
               return fn.apply(this,args)
           }else{
               return function(...args2){
                   return curried.apply(this,args.concat(args2))
               }
           }
       }
   }
   
   // 使用示例
    function sum (a,b,c ) { 
      return a+b+c
    }

    const curriedSum = curry(sum);

    const sumWithFirst = curriedSum(1)
    const sumWithFirstAndSecond = sumWithFirst(2)
    const result = sumWithFirstAndSecond(3)
    console.log(result);

数据劫持(代理)

框架中我们通常数据驱动视图 也就是说数据修改完后视图自动更新。

  • 数据劫持:以原始数据为基础,对原始数据进行复制。
  • 复制出来的数据是不允许被修改的,值是从原始数据中获取的。
  • 语法:Object.defineProperty(哪一个对象,属性名,{配置项})
  • 配置项:
    1. value:改属性对应的值。
    2. writable:该属性确定是否允许被重写,默认值是false.
    3. emunerable:该属性是否可别枚举(遍历), 默认值是false。
    4. get:是一个函数,叫做getter获取器,可以用来决定改属性的属性值,get属性的返回值就是当前属性的属性值。
    5. set:是一个函数,叫做setter设置器,当修改属性值的时候会触发函数。
    6. set和get 不能和其他三个属性一起用
 <div>
    <h1>姓名:<span class="name">默认值</span></h1>
    <h1>年龄:<span class="age">默认值</span></h1>
    <h1>性别:<span class="sex">默认值</span></h1>
    <br>请输入姓名:<input type="text" name="" id="name">
    <br>请输入年龄: <input type="text" name="" id="age">
    <br>请输入性别: <input type="text" name="" id="sex">
  </div>
  <script>
    const nameE1 = document.querySelector('.name')
    const ageE1 = document.querySelector('.age')
    const sexE1 = document.querySelector('.sex')
    const inp1 = document.querySelector('#name')
    const inp2 = document.querySelector('#age')
    const inp3 = document.querySelector('#sex')

    function observer(origin, callBack) {
      const target = {}
      for (let key in origin) {
        Object.defineProperty(target, key, {
          get() {
            return origin[key]
          },
          set(val) {
            origin[key] = val;
            callBack(target)
          }
        })
      }
      // 首次调用
      callBack(target)
      return target
    }

    const app = observer(
      {
        name: '张三',
        age: '18',
        sex: '男'
      }
      ,
      function bindHtml(res) {
        nameE1.innerHTML = res.name
        ageE1.innerHTML = res.age
        sexE1.innerHTML = res.sex
      }
    )

    inp1.oninput = function () {
      app.name = this.value
    }
    inp2.oninput = function () {
      app.age = this.value
    }
    inp3.oninput = function () {
      app.sex = this.value
    }
  </script>

数据代理(ES6)

  • 通过内置构造函数代理
  • 语法 : new Proxy(想要代理的对象)
  • 数据代理完成后,在向对象中添加属性,也可以自动完成代理。
    const obj = {
      name: 'zz',
      age: 1
    }
    const res = new Proxy(obj, {
      get(target, property) {
        return target[property]
      },
      set(target, property, val) {
        target[property] = val
      }
    })

    res.age = 20;
    console.log(res.age)
    console.log(res.name)
    // 数据代理后添加的数据也可以被代理
    res.abc = 123
    console.log('@', res)