最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JS基础梳理(一)

    正文概述 掘金(GUTI)   2021-02-04   461

    一、类型

    1、基础类型和引用类型

    • 基础类型有:number string boolean null undefined symbol bigint
    • 引用类型有:object array function ...

    2、类型检测

    typeOf 和 instanceOf

    • typeOf可以判断基础类型、判断对象类型时,除了函数 其他都是 object
    • instanceOf 可以判断对象类型,原理是通过原型链查找原型对象

    Object.prototype.toString.call()

    instanceOf 实现手写

    const myInstanceOf = (a, A) {
      if(typeOf a !== 'object') {
          return false
      }
      if(a._proto_ === A.prototype) {
        return true
      }
      return myInstanceOf(a._proto_, A)
    } 
    

    3、类型转换

    • == 的转换只有三种:转成数字、转成字符串、转成bealoon

    4、对象转原始类型的流程

    1. 先找Symbol.toPrimitive方法, 优先调用
    2. 再找valueOf方法,如果转为原始类型,则返回。
    3. 调用toString(), 如果转为原始类型则返回
    4. 如果都没有则报错

    二、闭包

    作用域链

    什么是闭包

    闭包是指有权访问另外一个作用域中变量的函数。

    为什么会有闭包

    作用域有 全局作用域和函数作用域。内层函数会将拷贝外层的作用域,与自己的作用域一起形成作用域链。作用域链链保证了执行环境对变量的有序访问,当在当前环境内没有找到的变量,就会去父级作用域查找

    三、原型链

    如何理解原型链

    • 构造函数有一个prototype属性,指向了原型对象。
    • 原型对象中的属性和方法会被构造函数的所有实例共享。
    • 实例对象有一个_proto_属性指向了原型对象
    • 原型对象又有自己的_proto_指向它的原型对象,因此就形成了原型链

    四、继承

    • 实现继承的几种方式(逐步趋近完善)

    1、借用call

    function Parent(){
      this.name = `parent1`;
    }
    
    function Child() {
      Parent.call(this)
      this.type = 'child'
    }
    

    缺点:没办法继承父类的方法

    2、借用原型链

    function Parent(){
      this.name = `parent1`;
      this.play = [1,2,3]
    }
    
    function Child() {
      this.type = 'child'
    }
    
    Child.prototype = new Parent()
    

    缺点:继承的属性会被所有实例共享

    3、call 和原型链组合

    function Parent(){
      this.name = `parent1`;
      this.play = [1,2,3]
    }
    
    function Child() {
      Parent.call(this)
      this.type = 'child'
    }
    
    Child.prototype = new Parent()
    

    缺点: Parent的构造函数会被重复调用。

    4、组合方式优化

    function Parent(){
      this.name = `parent1`;
      this.play = [1,2,3]
    }
    
    function Child() {
      Parent.call(this)
      this.type = 'child'
    }
    
    Child.prototype = Parent.prototype
    

    缺点: Child.prototype.constructor 指向了Parent

    5、寄生组合(完美方案)

    function Parent(){
      this.name = `parent1`;
      this.play = [1,2,3]
    }
    
    function Child() {
      Parent.call(this)
      this.type = 'child'
    }
    
    Child.prototype = Object.create(Parent.prototype)
    Child.prototype.constructor = Child
    

    五、函数的arguments、this

    arguments

    1. arguments 是不是数组

    不是数组、是一个类数组对象。它的key从0开始依次递增。并且有length属性,但是没有数组的诸多工具方法

    2. 如何转成数组

      1. 通过Array.form
      2. 通过结构赋值 [...arguments]
      3. Array.prototype.slice.call(arguments)
      4. Array.prototype.concat.apply([], arguments)
    

    this 的指向

    各种场景下的this指向

    • 全局上下问的this 指向window,严格模式下为undefined
    • 直接调用函数与全局上下文一致
    • 对象.方法的方式,this指向这个对象
    • DOM事件绑定: onclickaddEventerListenerthis指向绑定事件的元素 IE浏览器中的attachEvent,this指向window
    • new + 构造函数,构造函数中的this指向实例对象。
    • 箭头函数没有自己的this,也不能绑定,箭头函数中的this指向构造时上下文环境的this

    手动实现new

    • 创建一个空对象obj
    • appy一下构造函数
    • obj_proto_ 指向构造函数的prototype
    • 如果构造函数的执行结果返回一个对象,则返回该对象、否则返回obj
    function myNew(cstor, ...args) {
      let obj = {}
      let res = cstor.apply(obj, args)
      if(typeOf res === 'object' || typeOf res === 'function') {
        return res
      }
      obj.__proto__ = Object.create(cstor.prototype)
      return obj
    
    }
    

    实现apply、call

    • ES5的方式
    // 以call为例
    Function.prototype.call = function(context) {
      
      //  不能用 var args = arguments.slice(1) ,因为arguments 不是数组对象
      var args = []
      for(i = 1; i < arguments.length; i ++) {
        args.push(arguments[i])
      }
      context.fn = this
      // 通过数组转字符串来提取参数
      var res = eval('context.fn(' + arg + ')')
      delete context.fn
      return res
    }
    
    • ES6的方式
    // apply
    
    Function.prototype.myApply(obj, args) {
      let fn = this
      obj.fn = fn
      // !数组里面的参数通过解构传进来
      let res = obj.fn(...args)
      delete obj.fn
      return res
    }
    
    // call
    
    Function.prototype.myApply(obj, ...args) {
      let fn = this
      obj.fn = fn
      let res = obj.fn(...args)
      delete obj.fn
      return res
    }
    

    手动实现bind

    • bind 会返回一个新函数
    • 如果新函数被作为构造函数执行时,忽略this绑定。
    Function.prototype.bind = function(context, ...bindArg) {
      let fn = this
     // 闭包
      let resFn = function(...arg) {
          if (this instanceof resFn) { 
              // 作为构造函数被调用
            return fn.call(this)
          } else {
            return fn.call(context, ...bindArg.concat(arg)))
          }
      }
     
     // 原函数的原型对象不能丢
      resFn.prototype = Object.create(this.prototype)
      return resFn
    }
    

    六、 数组

    ##1、判断数组中包含某值

    • includes
    • indexOf
    • find
    • findIndexOf
    • some

    2、数组扁平化

    let arr = [ [1,3,[12]] ,5]
    
    • flat 方法
    arr.flat(Infinity)
    
    • replace + 正则匹配
    let str = JSON.stringify(arr)
    str.replace(/(\[ | \])/, '').split(',')
    // 除了split, 也可以 在外面加上[] 字符之后通过JSON.parse 转成数组
    
    • 递归
    // 结合reduce
    function myFlat(arr) {
      return arr.reduce((res, item) => {
        if(Array.isArray(item)) {
          return res.concat(myFlat(item))
        } else {
          return res.concat(item)
        }
      }, [])
    }
    // 也可以用forEach
    
    • some + concat + 扩展运算符号
    while(arr.some(item => Array.isArray(item))) {
      arr = [].concat(...arr)
    }
    

    延伸:对象扁平化

    // 例:有对象如下 
    {
      a: 1,
      b: { ba: 2,  bb: 4},
      c: [ 1 , 3],
    }
    // 扁平化转成
    {
      a: 1,
      b.ba: 2,
      b.bb:4,
      c.0: 1,
      c.2:  3
    }
    
    

    实现如下:利用递归

    const objectFlat = function(originObj) {
      if(!originObj) {
        return originObj
      }
    
      let result = {}
      function flat(obj, prefixKey, res) {
        for(key in obj) {
          let currentKey = prefixKey ? `${prefixKey}.${key}` : key
          if(typeof obj[key] === 'object' && obj[key] !== null) {
            // 如果是数组或对象
            flat(obj[key], currentKey , res)
          } else {
            res[currentKey] = obj[key]
          }
        }
      }
    
      flat(originObj, '', result)
      return result
    }
    

    3、数组的函数手动实现

    • map
    Array.prototype.map = function(callback, thisObj) {
      // 边界处理
      if(this === null || this === undefined) {
        throw new Error("can not read prototype 'map' of null or undefind ")
      }
      let currentArr = this
      let len = currentArr.length
      let res = []
      for(let i = 0; i < len; i ++) {
        res.push(callback.call(thisObj, currentArr[i], currentArr, thisObj))    
      }  
      return res
    }
    
    • reduce
    Array.prototype.reduce = function(callback, initVal) {
        //  校验入参,具体同上
        CHACK_ARG(callback, initVal)
    
      let len = this.length
      let res = initVal || this[0] 
      for(let i = initVal ? 0 : 1 ; i < len ; i++) {
         res = callback(res, this[i], this)
      }
      return res
    }
    
    
    • filter
    Array.prototype.fliter = function(callback) {
      // 校验入参
      CHECK_ARG(callback, this)
    
      let currentArg = this
      let len = this.length
      let res = []
      for(let i = 0; i < len ; i++) {
        if(callback(currentArg[i])) {
           res.push(currentArg[i])
        }
      }
    }
    
    • push、pop
    Array.prototype.push = function(...args) {
      let arr = this
      let len = this.length
      let addCount = args.length
      
      // 边界校验:
      if(len + addCount > 2 ** 53 - 1){
        throwTypeError("The number of array is over the max value restricted!")
      } 
       for(let i = 0; i < addCount; i ++) {
          arr[len + i] = args[i]
        }
        return arr.length
    }
    
    Array.prototype.pop = function() {
       let arr = this
        let len = this.length
        if(len === 0) {
          return undefined
        }
        
        result = arr[len - 1]
        delete arr[len - 1]
        arr.length = len - 1
        return result
    }
    
    • splice
    // 实现 splice方法
    
    Array.prototype.splice = function(position, count, ...items) {
    
      // 判断数组是否是密封对象或冻结对像
      isSealedOrFrozen(this)
    
      let arr = this
      let len = this.length
      
      // 处理起始位置,将负数的转成下标
      let startIndex = countStartIndex(position, len)
      
    // 处理删除值
      let deleteCount = countDeleteCount(startIndex, arr , count)
      // 删除需要删除的元素,并返回被删除的元素数组
      let deletedArr = deleteArrItems(arr, startIndex, count)
    
      // 移动数组元素
      moveArrItem(arr, startIndex, deleteCount, items)
      // 插入新元素
      insertNewItems(arr, items, startIndex)
    
      return deletedArr
    
    }
    
    function  isSealedOrFrozen(arr, deleteCount, addItems) {
      
      if(Object.isSealed(arr) && deleteCount !== addItems.length) {
        // ! 密封对象不可以删除或新增属性,但可以修改已有属性,所以当新增和删除相同时,不会有问题
        throw new TypeError('this array is a sealed Object')
      } else if(Object.isFrozen(arr) && (deleteCount > 0 || addItems.length > 0) ) {
        // ! 冻结对象不可删除、不可新增、不可更改
        throw new TypeError('this array is a frozen object')
      }
    }
    
    function countStartIndex(position, arrLen) {
      if(parseInt(position) !== position) {
        throw new Error('参数类型错误')
      }
    
      if(position < 0) {
        return arrLen + position
      }
    
      return position
    }
    
    function countDeleteCount(startIndex, arr, count) {
      if(parseInt(count) !== count) {
        throw new Error('count 参数类型错误')
      }
    
      if(count < 0) {
       return 0
      }
    
      if(count > arr.length - startIndex) {
        return arr.length - startIndex
      }
      return count
     }
    
    function deleteArrItems(arr, startIndex, count) {
      // 删除元素,但暂不挪动位置
      let deleted = []
      if(!count) {
        return deleted
      }
      for(let i = startIndex; i < startIndex + count ; i ++) {
        deleted.push(arr[i])
        delete arr[i]
      }
    
      return deleted
    }
    
    function moveArrItem(arr, startIndex, count, items) {
      let arrLen = arr.length
      let itemsLen = items.length
      if(itemsLen === count) {
        return
      }
    
      if(itemsLen > count) {
        // 添加数大于删除数整体后移
        let moveLen = itemsLen - count
        for(let i = arrLen - 1; i <= startIndex; i --) {
          arr[i + moveLen] = arr[i]
        }
      }
    
      if(itemsLen < count) {
        let moveLen = count - itemsLen
        for(let i = startIndex + count ; i < arrLen; i ++) {
          arr[i - moveLen] = arr[i]
        }
      }
    }
    
    function insertNewItems(arr, items, startIndex) {
      // 
      let arrLen = arr.length
      let itemsLen = items.length
    
      for(let i = startIndex; i < startIndex + itemsLen; i ++) {
        arr[i] = items[i - startIndex]
      }
    }
    
    • sort
        -  当对比函数返回值大于0, 则a 在b 后面,即a 的下标应该比b大;
        -  反之,则a 在b 的前面,即a 的下标比b小;
        -  整个过程是完成一次升序的排列。
    
    // 使用范例:
    let nums = [2,3,1]
    nums.sort(function(a, b) {
      if(a > b) return 1;
      else if(a < b) return -1
      else if(a === b) return 0
    })
    

    手写实现:

    源码中的sort思路 :

    • n <= 10时,采用插入排序
    • n > 10时,采用三路快速排序
    • 10 < n <= 1000,采用中位数作为哨兵元素
    • n > 1000 每隔200 ~215个元素挑一个元素,放到新数组,然后对它进行排序,然后找到中间数,作为哨兵元素。
    Array.prototype.sort = function (compareFn) {
      const len = arr.length
    
      // 处理参数边界值
      const arr = handleParams(this, compareFn)
    
      return quickSort(arr, compareFn)
    }
    
    function handleParams(arr, fn) {  // 处理参数边界值
      if (typeof fn !== 'function' && fn !== 'undefined') {
        throw new TypeError('fn is not a function!')
      } else if (fn === 'undefined') {
        return arr.map((item) => {
          return `${item}`
        })
      } else {
        return arr
      }
    }
    
    function insertSort(arr, compareFn) {
      // 插入排序
      const len = arr.length
      for (let i = 0; i < len; i++) {
        const current = arr[i]
        // 从当前元素往前找,如果与前面的元素比较结果 < 0, 则将当前元素插入到它前面
        for (let j = i; j >= 0; j--) {
          if (compareFn(current, arr[i]) < 0) {
            arr[i + 1] = arr[i]
            arr[i] = current
          }
        }
      }
    }
    
    function quickSort(arr, compareFn) {
      const len = arr.length
      if (len <= 10) {
        // 如果数组太短,自动改用插入排序
        return insertSort(arr, compareFn)
      }
    
      const sentry = getSentry(arr, compareFn)
      // 三路快排
      const low = []
      const high = []
      const eq = [sentry]
      for (let i = 0; i < len; i++) {
        if (compareFn(arr[i], sentry) < 0) {
          low.push(arr[i])
        } else if (compareFn(arr[i], sentry) > 0) {
          high.push(arr[i])
        } else {
          eq.push(arr[i])
        }
      }
      return [...quickSort(low, compareFn), ...eq, ...quickSort(high, compareFn)]
    }
    
    function getSentry(arr, compareFn) { // 计算哨兵元素
      const len = arr.length
      if (len <= 10) {
        // 如果长度小于10 不需要哨兵元素
        return 0
      }
      if (len > 10 && len <= 1000) {
        // 直接取中间点
        const sentryIndex = Math.floor(len / 2)
        return arr[sentryIndex]
      }
      // 超过1000 每隔200元素挑一个元素,放到新数组,然后对它进行排序,然后找到中间数,作为哨兵元素
    
      const sentryArr = [] // 用来存放备选的哨兵
      const sentryArrLen = Math.floor(len / 200) // 计算备选哨兵个数
    
      for (let i = 0; i < sentryArrLen; i++) {
        sentryArr.push(arr[200 * i])
      }
    
      const sortedSentryArr = quickSort(sentryArr, compareFn)
      const middleIndex = Math.floor(sortedSentryArr.length / 2)
    
      return sortedSentryArr[middleIndex]
    
    }
    
    

    拓展:其他的排序算法

    1、冒泡排序

    
    function bubbleSort(arr) {
      let len = arr.length
    
      for(let i = 0; i < len; i++) {
        for(let j = 0; j < len - 1 - i; j++) {
          if(arr[j] > arr[j + 1]) {
            let temp = arr[j]
            arr[j] = arr[j + 1]
            arr[j + 1] = temp
          }
        }
      }
    
      return arr
    }
    

    2、归并排序

    function mergeSort(arr) {
      function sort(arr, start, end) {
        const len = end - start
        if (len <= 1) {
          return arr
        }
    
        const middleIndex = Math.floor(len / 2)
        const left = sort(arr, start, middleIndex)
        const right = sort(arr, middleIndex, end)
    
        const result = []
        let p1 = 0
        let p2 = 0
        while (p1 < left.length && p2 < right.length) {
          if (p1 < p2) {
            result.push(left[p1])
            p1++
          } else {
            result.push(right[p2])
            p2++
          }
        }
    
        while (p1 < left.length) {
          result.push(left[p1])
          p1++
        }
    
        while (p2 < right.length) {
          result.push(right[p2])
          p2++
        }
    
        return result
      }
    
      return sort(arr, 0, arr.length)
    }
    
    

    4、如何跳出forEach

    • 使用try-catch,并在需要抛出跳出循环的地方抛出错误
    • 推荐使用every 或 some 替代forEach,every在return false 时中止循环,some 在 return true 时跳出循环。

    起源地下载网 » JS基础梳理(一)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元