数据类型的判断

106 阅读7分钟

1. 数据类型判断

  1. typeof * 只能判断基本数据类型,null也不能判断

  2. constructor * 数组.constructor == Array * 对象.constructor == Object * undefined.constructor 会报错,undefined不能使用constructor * null.constructor 会报错,null不能使用constructor * 缺点:undefined和null不能使用,有风险因为他只是对象中的一个属性所以可能会被更改

  3. Object.prototype.toString.call(传递数据)

2. 深浅拷贝

  1. 定义:其实就是把一个数据结构内的东西给到另一个数据结构

  2. 浅拷贝: * 定义:便利对象拿到对象的所有内容,给到另一个数据结构 * 特点:只会拷贝第一层的基本数据类型,如果有引用数据类型那么是赋值,修改的时候会互相影响

const obj = {
  name:'qf001',
  age:18,
  into:{
    id:12345,
    sex:'男'
  }
}
let newObj = {}
for(let key in obj){
  newObj[key] = obj[key]
}
  1. 深拷贝: * 特点:不管多少层数据都是百分之百复制过来变成两个一模一样但是毫不相关的数据结构
const obj = {
  name:'qf001',
  age:18,
  into:{
    id:12345,
    sex:'男'
  },
  arr:[1,2,3,4]
}
const obj1 = {}
function deepCopy(target,origin){
  // target:目标对象
  // origin:原始对象
  // 把origin身上所有的属性拷贝到target身上
  for(let key in origin){
    if(Object.prototype.toString.call(origin[key]) === '[object] [Object]'){ // 检测是否为对象类型
      target[key] = {}
      deepCopy(target[key],origin[key]) // 递归
    } else if(Object.prototype.toString.call(origin[key]) === '[object] [Array]'){ // 检测是否为数组类型
        target[key] = []
      deepCopy(target[key],origin[key]) // 递归
    } else {
      target[key] = origin[key]
    }
  }
}
deepCopy(obj1,obj)
* 简化版深拷贝
```js
const obj = {
name:'qf001',
age:18,
into:{
  id:12345,
  sex:'男'
},
arr:[1,2,3,4]

} const obj1 = JSON.prase(JSON.stringify(obj)) const obj2 = _.deepClone(obj) // 需要学到node才有库 ```

3. 继承

  1. 定义:不仅有它本身的属性还有其他函数内的属性

  2. 满足以下条件就可以说是子类父类关系 * 子类可以使用父类的属性

  3. 原型继承 * 核心:利用自定义原型的方式来实现继承 ```js /** * 1. 原型继承 * * 核心: 利用自定义原型的方式来实现继承 (子类的原型指向父类的实例化对象) * * 弊端: 自己原型内添加的内容会被覆盖掉, 并且继承到的属性不在自己身上而是在原型对象中 */

    // 构造函数1 function Person(name, age) { this.name = name this.age = age }

    // 原型上添加的方法 Person.prototype.sayHi = function () { console.log('你好') }

    // 得到一个实例化对象 const p1 = new Person('QF001', 18)

    function Student(id) { this.id = id }

    /** * 向 Student 构造函数的原型对象(XF001)中添加一个方法 init */ // Student.prototype.init = function () { // console.log('我是 Student 自带的一个方法') // }

    // 自定义构造函数的原型写法1 (只是演示) // Student.prototype = { // name: 'QF001', // age: 28 // }

    // 自定义构造函数的原型写法2 // Student.prototype = p1

    // 自定义构造函数的原型写法3 Student.prototype = new Person('QF999', 1988) // 重新给 Student 原型一个地址, 可能是 XF002, 但是一定和以前的没关系了

    // 向 Student 构造函数的原型对象(XF002)中添加一个方法 init Student.prototype.init = function () { console.log('我是 Student 自带的一个方法 init') }

    const s1 = new Student('XF001') s1.init()

    // console.log(s1.proto) // console.log(s1.name) // console.log(s1.age)

    s1.sayHi() /** * 调用了 实例化对象 s1 中的 sayHi 方法 * * 1. 首先去这个对象自身查找, 因为没有, 所以回去这个对象的 proto 中查找, 也就是自己构造函数的原型 * 因为 构造函数的原型被我们修改为了 Person 的实例化对象 * 所以相当于去 Person 的实例化对象中查找 * * 2. 现在来到 Person 这个实例化对象中查找, 这个对象中只有 name 和 age 两个属性, 所以来到这个对象中查找时没找到 * 根据对象的访问规则, 没找到的时候会去对象的 proto 中查找 * 所以相当于去 Person 的原型中查找 * * 3. 现在来到 Person 这个构造函数的原型中, 现在找到了 sayHi 这个方法, 所以直接调用, 打印 "你好" */

    // let obj = { // name: 'QF001' // }

    // obj = { // age: 18 // }

    // console.log(obj.name) ```

  4. 借用构造函数继承(借用继承)

 /**
       *  借用构造函数继承; 借用继承
       * 
       *      核心: 把父类构造函数当作普通函数调用, 但是需要使用 call 来改变内部的 this
       *      缺点: 没有继承到父类原型上的方法
      */

      function Person(name, age) {
          this.name = name
          this.age = age
      }
      Person.prototype.sayHi = function () {
          console.log('你好')
      }

      function Student(id) {
          // 1. 自动创建一个对象, 可以用 this 访问


          // 2. 手动向对象上添加属性
          this.id = id
          // Person('QF001', 18) // 因为内部 this 指向是 window 所以属性添加给了 window 对象
          Person.call(this, 'QF001', 18)
          /**
           *  因为 Perosn 内部的 this 被修改为 Student 内部自动创建的对象了
           *  所以执行 Person 的时候 内部的属性实际上是添加到了 Student 内部的对象中了
          */

          // 3. 自动返回这个对象
      }


      const s1 = new Student('XF001')

      console.log(s1.sayHi)
  1. 组合继承 * 其实就是将原型继承与借用构造函数继承组合到一起 js function Old (name,age){ this.name = name this.age = age } Old.prototype.Say = function(){ console.log('hello') } // 借用继承 function New (id){ this.id = id Old.call(this,'qf001',18) } // 原型继承 New.prototype = new Old // 组合起来了叫做组合继承 console.log(new New(10086))

  2. 拷贝继承 * for in 循环除了可以遍历到自身上的一些属性,原型上的也可以 js function New (name,age){ this.name = name this.age = age } New.prototype.Say = function(){ console.log('hello') } function Old (id){ this.Old = Old for(let k in New){ // 此时依次赋值内部的属性,连原型中的数值也复制 Old.prototype[k] = New[k] } }

  3. ES6的继承 * 语法:class 子类类名 extends 父类类名 {} * 语法2:super(要传递给父类的参数)

    constructor () {
      super() // 必须书写在第一行,小括号内可以向父类传参
      // 结构体
    }
    

7. 函数的定义与调用

  1. 函数的定义阶段: * 在堆内存中开启一个空间 * 将函数内的代码原封不动的放到这个空间 * 将这个空间的地址放在变量名并且存储到栈内存中

  2. 函数的调用阶段: * 根据函数名的地址去堆内存中找到指定的空间 * 在调用栈中开启一个空间,在执行空间中进行形参赋值 * 在执行空间中的函数内执行与解析 * 在执行空间中将函数的代码执行一遍 * 销毁这个执行空间

  3. 不会销毁的函数执行空间 * 函数内部向外部返回一个引用数据类型 * 函数外有变量接受这个返回的引用数据类型

  4. 后续想要销毁这个函数执行空间 * 直接取消外部变量对他的引用即可(res = null)

8. 闭包

  1. 如何创建一个闭包 * 需要一个不会被销毁的函数执行空间 * 需要直接或者间接的返回一个函数 * 返回的函数内需要使用外部函数内的私有(局部)变量

  2. 闭包的优点: * 在函数外可以使用函数内的变量 * 延长了变量的生命周期

  3. 闭包的缺点: * 闭包函数内部有一个不会销毁的空间,如果大量使用会导致内存泄露(内存的浪费)

9. 沙箱模式

  1. 利用了函数内间接返回一个函数

  2. 外部函数返回一个对象,对象内书写多个函数

function fn(){
    let a = 100
    let b = 200
    let obj = {
      getA (){
        return a
      },
      setA (val) {
        a = val
      },
      getB (){
        return b
      },
      setB (val) {
        b = val
      }
    }
    return obj
  }
  // 此时就完成了沙箱

  let c = fn()
  console.log(c)
  console.log(c.getA(),
  c.setA('qf001'),
  c.getA())
  console.log(c.getB())
  c.setB(400)
  console.log(c.getB())
  1. 沙箱模式语法糖: * 语法糖:一碗水解渴但是不好喝,加糖好喝还解渴 * 目的:在不影响功能的前提下尽可能地减少沙箱模式的代码量 * 核心:利用setter和getter帮助我们进行操作 ```js function fn() { let a = 0

       const obj = {
           // getA() { return a },
           // setA(val) { a = val }
    
           get a() { return a }, // 这个get是一个语法
           set a(val) { a = val } // 这个set也是一个语法
       }
    
       return obj
    

    }

    const res = fn() console.log(res)

    // console.log(res.getA()) 没有使用语法糖的写法 console.log(res.a) // 调用函数a的语法

    // res.setA('QF999') 没有使用语法糖的写法 res.a = 'QF999' // 给函数a重新赋值的语法

    console.log(res.a) ```

10. 防抖与节流

  1. 防抖: * 短时间内快速触发 * 永远都是用下一次替换上一次,永远执行最后一次 ```js const inp1 = document.querySelector('.inp1') const inp2 = document.querySelector('.inp2') const inp3 = document.querySelector('.inp3')

    // 无处理 inp1.oninput = function () { console.log(搜索了: ${this.value}) } // 防抖 // let timerID = 0 // inp2.oninput = function () { // clearTimeout(timerID) // timerID = setTimeout(() => { // console.log('经过了 300 ms 后, 搜索了' + this.value) // }, 300) // }

    // inp2.oninput = (() => { // return () => {} // })()

    inp2.oninput = ((timerID) => (e) => { clearTimeout(timerID) timerID = setTimeout(() => { console.log('经过了 300 ms 后, 搜索了' + e.target.value) }, 300) })(0) ```

  2. 节流: * 短时间内快速触发 * 在这一次执行的过程中不再执行新的,这一次执行结束可以执行新的 ```js // 节流 // let flag = true // inp3.oninput = function () { // if (flag === false) return

    // flag = false

    // setTimeout(() => { // console.log(过了 300 毫秒后, 搜索了: ${this.value}) // flag = true // }, 300) // }

    inp3.oninput = (function (flag) { return function () { if (flag === false) return

           flag = false
    
           setTimeout(() => {
               console.log(`过了 300 毫秒后, 搜索了: ${this.value}`)
               flag = true
           }, 300)
       }
    

    })(true); ```