你知道的JavaScript

519 阅读8分钟

TO 前端:.png

护驾

前端面试必不可少的一问,那就是JavaScript。在此,我已经嗝屁很多回了。立个Flag—拿捏住JavaScript,死死地!。Flag还是要立,万一实现了呢?

JavaScript的核心知识点

变量类型与浅拷贝、深拷贝

🙋:JS的数据类型有哪些?

基本数据类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol(ES6新增,表示独一无二的值)、BigInt(ES10新增,表示大于2^53-1的整数)。

引用数据类型:对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。

Tips📎:
1.基本数据类型保存在栈(stack)内存中;引用数据类型保存在堆(heap)内存中。
2.:自动分配内存空间,系统自动释放,里面存放的基本数据类型的值和引用类型的地址;:动态分配的内存,大小不定,也不会自动释放,里面存放的是引用类型的值。

🙋:什么是浅拷贝、深拷贝?如何实现浅拷贝、深拷贝?

浅拷贝:创建新的数据,这个数据有着原始数据属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,指向同一个object。

/*------ 实现浅拷贝 ------*/

// 1.利用Object.assign():可以把任意多的源对象自身的枚举属性拷贝给目标对象,然后返回目标对象(针对对象只有一层,没有嵌套的情况)

var obj = { name: '偷一只猪ma' }
var imoon = Object.assign({}, obj)
console.log(imoon); //{name:'偷一只猪ma'},改变obj的值,imoon的值也会跟着改变

// 2.ES6展开运算符...
var obj = { name: '偷一只猫ma' , info : { age: 1 }}
var copy = { ...obj }
obj.info.age = 18
console.log(copy.info.age) //18

// 3.数组只有一层的话,可以利用concat、slice
var arr = [1, 2, 3]
var arr1 = arr.concat()
console.log(arr1); //[1,2,3]
var arr2 = arr.slice()
console.log(arr2); //[1,2,3]

深拷贝:开辟了一个新的内存空间,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

/*------ 实现深拷贝 ------*/

// 1.lodash库中的 _.cloneDeep(拷贝对象)

// 2.JSON.parse(JSON.stringify(拷贝对象))
//但是这种方式会忽略undefined、symbol和函数

// 3.递归
function deepClone (obj) {
      let objClone = Array.isArray(obj) ? [] : {}
      if (obj && typeof obj === 'object') {
        for (const key in obj) {
          if (Object.hasOwnProperty.call(obj, key)) {
            if (obj[key] && typeof obj[key] === 'object') {
              objClone[key] = deepClone(obj[key])
            } else {
              objClone[key] = obj[key]
            }
          }
        }
      }
      return objClone
    }

    let arr = { name: '1', info: { age: 12 } }
    let arr1 = deepClone(arr)
    arr.info.age = 19
    console.log(arr1.info.age); //12

🙋:如何判断数据类型?

💧typeof

一般用来判断基础数据类型,不能判断Array、Object、Null、RegExp、Date,都会返object。

    typeof Symbol()       //symbol
    typeof ''             //string
    typeof 1              //number
    typeof true           //boolean
    typeof null           //object
    typeof undefined      //undefined
    typeof []             //object
    typeof new Function() //function
    typeof new RegExp()   //object
    typeof new Date()     //object
    typeof {}             //object

💧💧instanceof

用来判断A是否为B的实例,表达式:A instanceof B。如果A是B的实例,则返回true,否则返回false。instanceof用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性,但不能检测null和undefined。

    [] instanceof Array                          //true
    [] instanceof Object                         //true
    /s/g instanceof RegExp                       //true
    new Date('2022/03/14') instanceof Date       //true
    [1,2,3] instanceof Array                     //true

💧💧💧constructor

  • null和undefined是无效对象,不会存在constructor。
  • constructor是不稳定的,把类的原型进行重写,在重写的过程中会把原来的覆盖掉。

💧💧💧💧Object.prototype.toString.call()

判断类型最准的一个方法。


🙋:什么是深拷贝、浅拷贝?如何实现一个深拷贝、浅拷贝?

深拷贝

变量作用域与解构赋值

🙋:JS的变量作用域?

变量作用域:变量的可用性范围,ES5可分为全局作用域局部作用域ES6则在原有基础上新增一个块级作用域

全局作用域:不在任何函数内定义的变量就具有全局作用域。全局作用域的变量实际上被绑定在window的一个属性上。

    var imoon = "偷一只猪ma"
    
    console.log(imoon);  //偷一只猪ma
    console.log(window.imoon);  //偷一只猪ma
    
    /*以变量方式定义的函数也是一个全局变量*/
    function stealPig () {
      console.log('偷一只猪ma');
    }
    
    stealPig()  //偷一只猪ma
    window.stealPig() //偷一只猪ma

局部作用域:函数内部声明的变量,只能在函数内部被访问。

    function stealPig(){
      var a=1;
      let b=2;
      
      console.log(a,b) //1 2
    }
    
    stealPig()
    
    console.log(a) //报错,a is not undefined
    console.log(b) //报错,b is not undefined

块级作用域:使用let/const声明的变量,且只大括号里面起作用

    if (true) {
      var a = 1  //a为全局变量
      let b = 2  //b为块级变量,只在大括号里面起作用
      
      console.log(a, b); //1 2
    }
    console.log(a); //1
    console.log(b); //报错,b is not undefined

解构赋值:

ES6开始,JavaScript引入了解构赋值。

/*对多个变量进行解构*/
    let [x, y, z] = [1, 2, 3]
    
    console.log(x); //1
    console.log(y); //2
    console.log(z); //3
    
/*数组本身嵌套,注意嵌套层次和位置要保持一致*/    
    let [a, [b, c]] = [10, [20, 30]]
    console.log(a); //10
    console.log(b); //20
    console.log(c); //30
    
/*对一个对象进行解构*/
/*对象本身嵌套,也要注意嵌套层次一致*/
    var imoon = {
       name: '偷一只猪ma',
       age: 18,
       address:{
         city:'chengdu'
       }
    }
    var { age,address:{city} } = imoon
    console.log(age);   //18
    console.log(city);  //chengdu
    
/*把对象的password属性赋值给变量id*/    
    var imoon = {
      name: '偷一只猪ma',
      age: 18,
      city: "chengdu",
      password: '123'
    }

    let { name, password: id } = imoon
    
    console.log(name); //偷一只猪ma
    console.log(id);  //123
    console.log(password); //报错,password is not defined
    
/*single是person中不存在的属性,避免返回undefined,则使用默认值*/    
    var imoon = {
      name: '偷一只猪ma',
      age: 18
    }

    let { name, single=true } = imoon
    console.log(name);
    console.log(single);

变量提升:

JavaScript的函数定义有个特点,会先扫描整个函数体的语句,把所有声明的变量提升到函数顶部。

    function stealPig(){
        var x='hello'+y
        console.log(x)
        var y='偷一只猪ma'
    }
    
    /*JS看到的如下:*/
    function stealPig(){
        var y
        var x='hello'+y
        console.log(x)  //helloundefined
        y='偷一只猪ma'
    }
    foo()

闭包

🙋:什么是闭包?

闭包有3个特性:

  • 1.函数嵌套函数;
  • 2.函数内部可以访问函数外部的参数和变量;
  • 3.参数和变量不会被垃圾回收机制回收;

举几个🌰:

🌰 One: 创建闭包最常用的方式,就是在一个函数内部创建另一个函数。

    function stealPig() {
      var a = 1, b = 2
      function sum () {
        return a + b
      }
      return sum
    }

    var b = stealPig()
    console.log(b()); //3

🌰 Two: 经典面试题之定时器与闭包,准确来说是异步与闭包。

众所周知,JavaScript单线程的。简单来说,单线程就是同一时间只能做一件事。那么它怎样执行异步代码呢?JS会在执行时生成一个主任务队列(先进先出),队列里的任务会按顺序一个一个执行。当遇到异步任务时,会将其相关回调放到任务队列中,当主任务队列里的任务都执行完成后,JS通过事件循环机制(EventLoop),发现任务队列中有任务等待,就取出来添加到主线程中开始执行。

20220323115415.jpg

   for(var i = 0; i < 10; i++){
      setTimeout(function () {
        console.log(i) //10个10
      }, 0)
    }
    
    /*如果想打印0-9,该如何?就在for循环里面创建闭包*/
    for (var i = 0; i < 10; i++) {
      (function (j) {
        setTimeout(function () {
          console.log(j)//0-9
        }, 0)
      })(i)
    }

🌰 Three:测试题

    function fun(n,o){
      console.log(o)
      return {
        fun:function(m){
            return fun(m,n)
        }
      }
    }
    
    var a=fun(0) //?
    a.fun(0) //?
    a.fun(1) //?
    a.fun(2) //?
    
    var b=fun(0).fun(1).fun(2).fun(3) //?
    
    var c=fun(0).fun(1) //?
    c.fun(2) //?
    c.fun(3) //?

优、缺点:

优点:

    1. 可以读取函数内部变量
    1. 可以避免全局污染

缺点:

    1. 闭包会导致变量不会被垃圾回收机制清除,会大量消耗内存
    1. 不恰当的使用闭包可能会造成内存泄漏

内存泄漏:说白了就是已经不在使用的内存,没能得到及时的释放。


箭头函数

ES6新增的一种函数,看似匿名函数的一种简写。如:()=>{}

🙋:箭头函数与普通函数的区别?

📎 One: 写法不一样。

/*普通函数*/
   function stealPig(){
     console.log('偷一只猪ma')
   }
   
/*箭头函数*/
   let stealCat=()=>{
     console.log('偷一只猫ma')
   }

📎 Two:箭头函数不能作为构造函数使用。

/*普通函数*/
    function stealPig(pig){
      this.pig=pig
    }
    const p=new stealPig(18) 
    console.log(p) //stealPig{pig:18}

/*箭头函数*/
    let stealCat = (cat) => {
      this.cat = cat
    }
    const c = new stealCat(18)
    console.log(c); //报错,stealCat is not a constructor

📎 Three:两者的this指向不同。

普通函数的this指向:谁调用该函数就指向谁。
箭头函数的this指向:箭头函数本身没有this,箭头函数内部的this指向了其外层作用域,谁定义了函数,this就指向谁。

📎 Four: apply()、call()、bind()目的是改变this指向,箭头函数的this由函数定义时作用域决定,已经确定就无法改变。

📎 Five: 箭头函数没有自己的arguments,一种替代方案就是使用扩展运算符。

/*普通函数*/
    function sum () {
      console.log(arguments);
      //Arguments(2) [4, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
      return arguments[0] + arguments[1];
    };
    sum(4, 6);
    
/*箭头函数*/
     let sum1 = (a, b) => {
      console.log(arguments); //报错,arguments is not defined
    }
    sum1(4, 6)
    
    /*解决办法:使用扩展运算符*/
     let sum1 = (...c) => {
      console.log(c); //[4,6]
    }
    sum1(4, 6)

📎 Six: 箭头函数没有prototype原型。


原型与原型链

🙋:什么是原型?

20220323171225.jpg

    //构造函数
    var Imoon = function (name) {
      this.name = name
    }
    var stealDog = new Imoon('橘橘')
    
    console.log(stealDog.name) //橘橘
    console.log(stealDog.age)  //undefined
  • 每个对象都有一个_proto_属性,并且指向它的prototype原型对象。

QQ20220324-142226@2x.png

  • 任何一个函数,只要被new了以后,就是构造函数(如Imoon),而new出来的被称为实例(如stealDog)。任何函数都可以当作构造函数。
  • 函数中都有一个prototype属性,,声明一个函数的时候就会有个prototype属性,这个属性会初始化一个空对象,也就是原型对象
  • 原型对象里面会有个构造器:constructor,它会默认指向声明的那个函数。

QQ20220324-111359@2x.png


🙋:什么是原型链?

当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会起它的_proto_隐式原型上查找,即它的构造函数prototype,如果还没找到,就会在构造函数的prototype_proto_中查找,这样一层一层就会形成链式结构,称之为原型链

在stealDog查找某个属性时,会执行下面的步骤:

QQ20220324-153549@2x.png