JS (一)

64 阅读5分钟
  1. 执行上下文、作用域链、闭包

    js有三种执行上下文类型,分别为全局执行上下文、函数执行上下文、Eval函数执行上下文 全局执行上下文:这是默认或者说基础的上下文,任何不在函数中的代码都在全局在上下文中。它会执行两件事,一是创建一个全局的window对象(浏览器情况下),二是设置this等于这个全局对象,一个程序中只会有一个全局执行上下文。 函数执行上下文:每当一个函数执行的时候,都会为这个该函数创建一个新的上下文。每个函数都有它自己的执行上下文,且是在函数被调用的时候创建的。函数上下文可以有任意多个,每当一个新的执行上下文被创建,它都会按定义顺序执行。 Eval函数执行上下文:执行在eval函数内部的代码也会有它自己的执行上下文,但由于不常用,因为不怎么了解。

    执行栈:也就是其他语言所说的调用栈,是一种LIFO(后进先出)的数据结构的栈,被用来存储代码运行时创建的所有执行上下文。 ​ js引擎第一次碰到某段代码时,首选创建一个全局上下文执行并压入当前执行栈,每当后续遇到一个函数,为该函数创建一个新的执行上下文并压入执行栈顶部,引擎执行位于顶部的函数,执行结束时,从栈中弹出,然后控制流程到当前栈的下一个上下文,直到所有的上下文执行完。

    作用域链 首先理解作用域,作用域是运行代码中某些特定部分中变量、函数和对象的可访问性。 Es6之前没有块级作用域,只有全局作用域和函数作用域,Es6提供了块级作用域,可通过let const来体现。 全局作用域,在代码的任何地方都能访问到的对象拥有全局作用域, 函数作用域,只在固定代码片段可访问的对象或变量拥有的作用域。 块级作用域,声明的变量在指定块的作用域外无法访问。

    ​
        var a = 100
        function F1() {
            var b = 200
            function F2() {
                var c = 300
                console.log(a) // 自由变量,顺作用域链向父作用域找
                console.log(b) // 自由变量,顺作用域链向父作用域找
                console.log(c) // 本作用域的变量
            }
            F2()
        }
        F1()
    

    寻找变量从当前作用域寻找,如果没找到,在父级作用域寻找,如果还是没找到,在全局作用域寻找,这一层一层的关系就是作用域链。

    闭包

    能够访问其他函数内部变量的函数,被称为 闭包 ,内部的函数存在外部作用域的引用就会导致闭包

    // 闭包是指那些能够访问自由变量的函数。这里的自由变量是外部函数作用域中的变量。

     function add() {
        let num = 100
        function con(){
            console.log(num)
        }
        con()
     }
     add()
    使用场景1return回一个函数   
    var n = 10
    function fn(){
        var n =20
        function f() {
           n++;
           console.log(n)
         }
        return f
    }
    ​
    var x = fn()
    x() 
    ​
    // 节流
    function throttle(fn, timeout) {
        let timer = null
        return function (...arg) {
            if(timer) return
            timer = setTimeout(() => {
                fn.apply(this, arg)
                timer = null
            }, timeout)
        }
    }
    ​
    // 防抖
    function debounce(fn, timeout){
        let timer = null
        return function(...arg){
            clearTimeout(timer)
            timer = setTimeout(() => {
                fn.apply(this, arg)
            }, timeout)
        }
    }
    ​
    ​
    
  2. call,apply,bind

    fun.call(thisArg,param1,param2)

    fun.apply(thisArg,[param1,param2])

    fun.bind(thisArg,param1,param2)

    call/apply:fun执行的结果 bind:返回fun的拷贝,并拥有指定的this值和初始参数

    funthis指向thisArg`对象**

    非严格模式下:thisArg指定为null,undefined,fun中的this指向window对象.

    严格模式下:funthisundefined

    值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean

  3. 浅拷贝 深拷贝(一般来说,js数据分为基本数据类型和引用数据类型,我们说的深浅拷贝都是相对于引用数据类型而言的)

    浅拷贝:只复制了引用,未复制真正的值;

    let a = [1,2,3]
    let cloneA = a
    cloneA.push(4)
    console.log(a,cloneA) // [1,2,3,4] , [1,2,3,4]
    可以看到a和clone都改变了 这就浅拷贝
    

    深拷贝:引用和值都复制了。只要进行了深拷贝,他们就是完全独立的两个个体了,其中一个数据的更改不会引起另一个数据的变化

    1.深拷贝最简单的方式:JSON.stringfy JSON.parse
    let a = [1,2,3]
    let cloneA = JSON.parse(JSON.stringfy(a))
    cloneA.push(4)
    conosle.log(a,cloneA) // [1,2,3] , [1,2,3,4]
    但这这种方式不能适配所有的情况
    const obj = {
        name:'tom',
        sayHi:function(){
            console.log('say hi')
        }
    }
    const cloneObj = JSON.parse(JSON.stringfy(obj))
    console.log(cloneObj)  // {nam:'tom'}
    // 可以看到在进行之前的深浅拷贝后,sayHi方法丢了,原因就是此种方式在转换时遇到undefined,function,symbol类型的数据回忽略。
    2. 递归
    function deepClone(val){
        // 判断一下复制的目标是对象还是数组
        const targetObj = val.constructor === 'Array'?[]:{}
        for(let keys in val){
            if(val[keys]&&typeof val[keys] === 'Object'){
                tragetObj[keys] = val[keys].constructor === 'Array'?[]:{}
                targetObj[keys] = deepClone[val[keys]]
            }else{
                targetObj[keys] = val[keys]
            }
        }
        return targetObj
    }
    ​
    deepClone(): source => {
        let clone = Object.assign({}, source);
        Object.keys(clone).forEach(
          key => (clone[key] = typeof source[key] === 'object' ? deepClone(source[key]) : source[key])
        );
        return clone;
      }
    const cloneObj =  deepClone(obj)
    conosle.log(cloneObj) // {name:'tom',sayHi:function(){console.log('say hi')}}
    
  4. reduce

    reduce是一个数组的迭代方法,和map、filter不同,reduce方法可以缓存一个变量,迭代时我们可以操作这个变量然后返回它。

    使用场景1:数组累加。

    let arr = [1,2,3,4]
    arr.reduce((a,i) =>{ return a + i})
    

    使用场景2:处理不规则数组

    let data = [
      ["红色","128g", "苹果手机"],
      ["南北","两室一厅","128㎡","洋房住宅"],
      ["小米","白色","智能运动手表","心率血压血氧","来电信息提醒"], 
      ["官方发售","2020年秋季","篮球","球鞋","品牌直邮"]
    ]
    ​
    data.map((item) => {return item.reduce((a.i) =>{ return `${a} {i}` })})
    

    使用场景3:数组去重

    let arr = [1,2,3,'a','b','c',2,'a']
    arr.reduce((val,item) => {
        if(val.indexOf(item) === -1){
            val.push(item)
        }
        return val
    },[])
    

    使用场景4:按属性分组

            let obj = [
                {name: '张三', job: '数据分析师', country: '中国'},
                {name: '李四', job: '科学家', country: '中国'},
                {name: '雷尔', job: '科学家', country: '美国'},
            ]
    ​
            let obj1 =  obj.reduce((val,item) => {
                let newKey = item.country
                if(!val[newKey]){
                    val[newKey]=[]
                }
                val[newKey].push(item)
                return val
            },[])
            console.log(obj1);

    使用场景5:数组扁平化

            let arr = [[1, 2, 3], ['a', ['b', 'c'], 'd'], [5]]
            function flatArr(val) {
                return val.reduce((newArr, item) =>  
                newArr.concat(Array.isArray(item)? flatArr(item): item
                ) ,[])
            }
            console.log(flatArr(arr))
    

    使用场景6:字符串反转

    let str = 'hello,world'
    console.log([...str].reduce(val,item => item+val))
    
  5. www.bilibili.com/video/BV1ME… Golang