JS相关

164 阅读32分钟

数据类型

1. 基本数据类型

  • 基本数据类: 在内存中占据空间小,大小固定,被频繁使用,直接存储在栈内存number string undefined null boolean
  • 引用数据类型: 占据空间大,大小不固定,在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在堆内存里。Object Function Array
  • ES6新增 symbol 本质上是唯一标识符,可以用作对象的唯一属性名,主要是为了解决可能出现的全局变量冲突的问题。。BigInt 是一种数字类型的数据,可以表示任意精度格式的整数。可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。。

2. 判断数据类型的方法

  • typeof: 其他判断都正确,直接输出原本类型,像数组、对象、null都会被判断为object。
    typeof(NaN)                                //number
    
    • NaN 指 “不是一个数字”,但是typeof类型检测返回的结果是number,因为它是number类型的一个特殊的值,表示一个有特殊用途的常规值。例如在执行数学运算时没有成功,失败后就会返回NaN。
  • instanceof: 只能正确判断引用数据类型,而不能判断基本数据类型。它的内部原理是 判断在它的原型链中能否找到该类型的原型instanceof 运算符可以用来测试一个对象在它的原型链中是否存在一个构造函数的 prototype 属性。
    console.log(2 instanceof Number);                    // false
    console.log(true instanceof Boolean);                // false 
    console.log('str' instanceof String);                // false 
    console.log([] instanceof Array);                    // true
    console.log(function(){} instanceof Function);       // true
    console.log({} instanceof Object);                   // true
    
    instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
    1. 首先获得对象的原型
    2. 然后获取类型的原型
    3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
    function myInstanceof(left, right) {
      // 获取对象的原型
      let proto = Object.getPrototypeOf(left)
      // 获取构造函数的 prototype 对象
      let prototype = right.prototype; 
      // 判断构造函数的 prototype 对象是否在对象的原型链上
      while (true) {
        if (!proto) return false;
        if (proto === prototype) return true;
        // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
        proto = Object.getPrototypeOf(proto);
      }
    }
    
  • constructor: 有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。而且,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:
    console.log((2).constructor === Number); // true
    console.log((true).constructor === Boolean); // true
    console.log(('str').constructor === String); // true
    console.log(([]).constructor === Array); // true
    console.log((function() {}).constructor === Function); // true
    console.log(({}).constructor === Object); // true
    
    function Fn(){};
    Fn.prototype = new Array();
    var f = new Fn();
    console.log(f.constructor===Fn);    // false
    console.log(f.constructor===Array); // true
    
  • Object.prototype.toString.call(): 原理是使用 Object 对象的原型上的 toString 方法来判断数据类型:
    var a = Object.prototype.toString;
    console.log(a.call(2));                       //[object Number]
    console.log(a.call(true));                    //[object Boolean]
    console.log(a.call('str'));                   //[object String]   
    console.log(a.call([]));                      //[object Array]
    console.log(a.call(function(){}));            //[object Function]
    console.log(a.call({}));                      //[object Object]
    console.log(a.call(undefined));               //[object Undefined]
    console.log(a.call(null));                    //[object Null]
    
  • typeof和instanceof的区别?
    1. typeof 判断基本类型时,直接返回基本类型,在判断数组、对象、null时,返回object,而instanceof的返回结果都是布尔值。
    2. instanceof 只能正确判断引用数据类型,而不能判断基本数据类型,判断的基本数据类型返回都是false。
  • obj.toString()和Object.prototype.toString.call(obj)的区别? 因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。

3. null 和 undefined 的区别

  • 概念上: undefined 表示变量声明了但是没有赋值,会默认为undefined;null 主要用于赋值给一些可能会返回对象的变量,作为它的初始化值。undefined 代表的含义是未定义,null 代表的含义是空对象
  • 用typeof判断: undefined类型返回"undefined"; Null 类型化会返回 "object",这是一个历史遗留的问题。
  • 用"=="和"==="比较: 当使用双等号对两种类型的值进行比较时会返回 true,因为null二进制全部为0,undefined二进制前3为000。使用三个等号时会返回 false。

4. "==" 和 "===" 区别

  • 首先两个都可以用来判断相等。
  • "==": 表示相等,会在比较之前进行隐式类型转换,再判断操作数是否相等,只要值相等,就返回true。
  • "===": 表示严格相等,不会进行类型转换,左右两边不仅值要相等,类型也要相等,如果两个值的类型不同,会直接返回false。

5. 隐式类型转换规则

  • 一类为条件判断型的转换(以if形式的转换)

    image.png

  • 一类则为比较型的转换(==、<=、>=,返回true或false)

    • 数值与各种类型的比较: 一般其他数据类型都会将转为数值,再进行比较。
    • 字符串与各种类型的比较: 如果双方都是字符串的话,就直接比较;如果有一边不是,先将字符串转为数字,然后再使用数字比较的规则来判断。
    • 布尔值与其他类型比较跟字符串的类似
    • 对象间的比较: 因为对象之间比较的是地址,不涉及数据的隐式转换,所以任意两个对象总是不相等的。
    • 对象和其他类型值比较时: 会调用 toprimitive()方法(将对象转换到基本类型值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。)、tovalueof()方法(返回对象的原始值)、 tostring()方法 (返回对象的字符串) juejin.cn/post/713874…

6. 列举隐式、强式类型转换

1. 强式转换:

  • parselnt
  • parseFloat
  • Number

2. 隐式转换:

  • ==
  • +string:可以转换为数字
  • a+" " :可以转换为字符串

JS基础

new操作

1. new做了什么

  • 创建了一个新的空对象 作为要返回的实例对象。
  • 设置原型链,将空对象的原型__proto__指向构造函数的prototype属性。
  • 执行函数体 让构造函数的this指向obj,并执行函数体 var result=Func.call(obj);
  • 判断返回值,判断返回类型,如果是值就返回这个obj,如果是引用类型,返回这个引用对象。
  • 手动封装一个new函数
    function New () {  
        var obj = new Object();  
        obj._proto_ = Object.prototype;  
        Object.call(obj);  
        return typeof result === 'object'? result : obj;
    }
    

2. call、apply、bind原理及区别

  • 相同点:为了改变函数执行时的上下文,也就是可以改变this的指向。
  • fn.call: 立即调用,接收一个this绑定的对象和一个参数列表来调用一个函数。
  • fn.apply: 立即调用,接收this绑定的对象和一个包含多个参数的数组来调用一个函数。
  • fn.bind: 不会立即调用,它会创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  • 使用场景:
    1. 合并两个数组
    2. 获取数组中的最大值和最小值

箭头函数和普通函数区别

  • 箭头函数: 首先,箭头函数也叫匿名函数,它简化了函数的定义方式,如果没有参数,就直接写一个空括号即可,如果只有一个参数,可以省去参数的括号,如果有多个参数,用逗号分割,如果函数体的返回值只有一句,可以省略大括号, 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数。
  • 区别:
    1. 声明方式不同: 普通函数通过function可以函数声明,或者函数表达式声明。箭头函数需要通过箭头,而不是通过关键字function来声明。
    2. this指向不同: 普通函数内部的 this 指向函数运行时所在的对象;而箭头函数没有自己的 this ,而是引用被定义时,上层作用域中的 this 。
    3. 改变this指向: 普通函数的this指向可以通过call、apply、bind来改变。箭头函数的 this 指向不会变。
    4. arguments: 普通函数能够打印出arguments ,箭头函数没有自己的arguments,因为它处于全局作用域中,所以没有。
    5. 箭头函数没有原型prototype
    6. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数
    7. 箭头函数不能当成一个构造函数

数组

1. 定义方法:

  • 直接定义列举,例如: var arr = [1,2,,,,3,4];
  • 通过new Array,例如: var arr = new Array(1,2,3,4,6);

2. 判断数组的方法

  • 使用原型上的Object.prototype.toString.call()做判断:
    Object.prototype.toString.call([])            //[object Array]
    
  • 通过ES5的Array.isArray()做判断:返回布尔值
    Array.isArrray([]);                           //true
    
  • instanceof:返回布尔值 (变量值 instanceof Array)
    [] instanceof Array                          //true
    
  • Array.prototype.isPrototypeOf:返回布尔值 利用isPrototypeOf()方法,判定Array是不是在obj的原型链中,如果是,则返回true,否则false。
    Array.prototype.isPrototypeOf([])             //true
    
  • 通过原型链做判断:返回布尔值
    [].__proto__ === Array.prototype              //true
    

3. 数组常见的方法:

  • 改变原数组: push pop shift unshift sort reverse splice fill
  • 不改变原数组: concat join split toString slice indexof
  • 具体用法:
    1. push():可以向数组的末尾添加一个或多个元素,并返回新的长度。
    2. pop(): 可以删除一个数组中的最后的一个元素,并且返回这个元素。
    3. shift():可以删除数组的第一个元素,并返回这个元素。
    4. unshift(): 可以向数组的开头添加一个或更多元素,并返回新的长度。
    5. sort(): 可以对数组元素进行排序,并返回这个数组
    6. reverse(): 可以用于反转数组中元素的顺序,返回反转后的数组。
    7. splice(): 可以向/从数组中添加/删除项目,然后返回被删除的项目,较常用。
    arr.splice(从第几位开始,截取多少的长度,在切口处添加新的数据)
    
    1. concat():合并两个或多个数组,返回一个新数组。
    2. join():把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。
    3. split():用于把一个字符串分割成字符串数组,不影响原字符串。
    4. slice():返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。注意:字符串也有一个slice() 方法是用来提取字符串的,两个不一样。
    5. indexof():查找数组是否存在某个元素,返回下标,如果不存在,则返回-1。

4. 数组的遍历方法有哪些

  • 数组遍历方法(12个):

ES5:forEacheverysomeflitermapreducereduceRight;
ES6:findfindIndexkeysvaluesentries;

  • 1. forEach
    • 特点: 对于空数组是不会执行回调函数的;对于已在迭代过程中删除的元素,或者空元素会跳过回调函数;遍历次数在第一次循环前就会确定,再添加到数组中的元素不会被遍历;如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值;无法中途退出循环,只能用return退出本次回调,进行下一次回调;它总是返回 undefined值,即使你return了一个值。

    • 定义: 按升序为数组中含有效值的每一项执行一次回调函数。

    • 语法:

      array.forEach(function(currentValue, index, arr), thisValue)

    • 参数: function(必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数:

      currentValue (必须),数组当前元素的值
      index (可选), 当前元素的索引值
      arr (可选),数组对象本身
      thisValue (可选): 当执行回调函数时this绑定对象的值,默认值为undefined

      let a = [1, 2, ,3]; // 最后第二个元素是空的,不会遍历(undefined、null会遍历)
      let obj = { name: 'OBKoro1' };
      let result = a.forEach(function (value, index, array) { 
        a[3] = '改变元素';
        a.push('添加到尾端,不会被遍历')
        console.log(value, 'forEach传递的第一个参数'); // 分别打印 1 ,2 ,改变元素
        console.log(this.name); // OBKoro1 打印三次 this绑定在obj对象上  // break; // break会报错
        return value; // return只能结束本次回调 会执行下次回调
        console.log('不会执行,因为return 会执行下一次循环回调')
      }, obj);
      console.log(result); // 即使return了一个值,也还是返回undefined// 
      

      回调函数也接受接头函数写法

  • 2. every检测数组所有元素是否都符合判断条件
    • 定义: 方法用于检测数组所有元素是否都符合函数定义的条件

    • 语法

      array.every(function(currentValue, index, arr), thisValue)

    • 参数:(这几个方法的参数,语法都类似)

      function(必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数

      currentValue (必须),数组当前元素的值
      index (可选), 当前元素的索引值
      arr(可选),数组对象本身
      thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

    • 方法返回值规则:

      • 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
      • 如果所有元素都满足条件,则返回 true。
      function isBigEnough(element, index, array) { 
       return element >= 10; // 判断数组中的所有元素是否都大于10
      }
      let result = [12, 5, 8, 130, 44].every(isBigEnough); // false
      let result = [12, 54, 18, 130, 44].every(isBigEnough); // true// 接受箭头函数写法 
      [12, 5, 8, 130, 44].every(x => x >= 10); // false
      [12, 54, 18, 130, 44].every(x => x >= 10); // true
      
  • 3. some数组中的是否有满足判断条件的元素
    • 定义:数组中的是否有满足判断条件的元素
    • 语法

      array.some(function(currentValue, index, arr), thisValue)

    • 参数:(这几个方法的参数,语法都类似)

      function(必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数

      currentValue (必须),数组当前元素的值
      index (可选), 当前元素的索引值
      arr(可选),数组对象本身
      thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

    • 返回值规则

      如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。 如果没有满足条件的元素,则返回false。

      function isBigEnough(element, index, array) {
       return (element >= 10); //数组中是否有一个元素大于 10
      }
      let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
      let result = [12, 5, 8, 1, 4].some(isBigEnough); // true
      
  • 4.fliter过滤原始数组,返回新数组
    • 定义: 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。
    • 语法

      let new_array = arr.filter(function(currentValue, index, arr), thisArg)

    • 参数: (这几个方法的参数,语法都类似)

      function(必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数

      currentValue (必须),数组当前元素的值
      index (可选), 当前元素的索引值
      arr(可选),数组对象本身
      thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

      let a = [32, 33, 16, 40];
      let result = a.filter(function (value, index, array) {
       return value >= 18; // 返回a数组中所有大于18的元素
      });
      console.log(result,a);// [32,33,40] [32,33,16,40]
      
  • 5.map对数组中的每个元素进行处理,返回新的数组
    • 定义: 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
    • 语法

      let new_array = arr.map(function(currentValue, index, arr), thisArg)

    • 参数: (这几个方法的参数,语法都类似)

      function(必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数

      currentValue (必须),数组当前元素的值
      index (可选), 当前元素的索引值
      arr(可选),数组对象本身
      thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

      let a = ['1','2','3','4'];
      let result = a.map(function (value, index, array) {
       return value + '新数组的新元素'
      });
      console.log(result, a); 
      // ["1新数组的新元素","2新数组的新元素","3新数组的新元素","4新数组的新元素"] ["1","2","3","4"]
      
  • 6.reduce为数组提供累加器,合并为一个值
    • 定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。
    • 语法

      array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

    • 参数

      function (必须): 数组中每个元素需要调用的函数。

    • 回调函数的参数

      total (必须),初始值, 或者上一次调用回调返回的值
      currentValue(必须),数组当前元素的值
      index(可选), 当前元素的索引值
      arr(可选),数组对象本身
      initialValue(可选): 指定第一次回调 的第一个参数。 回调第一次执行时: 如果initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回.

      // 数组求和 
          let sum = [0, 1, 2, 3].reduce(function (a, b) {
               return a + b;
          }, 0);// 6
      // 将二维数组转化为一维 将数组元素展开
          let flattened = [[0, 1], [2, 3], [4, 5]].reduce( (a, b) => a.concat(b), []);
      // [0, 1, 2, 3, 4, 5]
      
  • 7. ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值
    • 注意 : 目前只有Safari 9支持,,其他浏览器未实现,babel转码器也还未实现
    • 定义:三个方法都返回一个新的 Array Iterator
    • 对象,对象根据方法不同包含不同的值。
    • 语法array.keys()array.values()array.entries()
    • 参数:无。
      for (let index of ['a', 'b'].keys()) {
       console.log(index);
      }// 0// 1
      for (let elem of ['a', 'b'].values()) {
       console.log(elem);
      }// 'a'// 'b'
      for (let [index, elem] of ['a', 'b'].entries()) {
       console.log(index, elem);
      }// 0 "a"// 1 "b"
      

5. 数组去重:

  • 双重for循环: 建立新数组,遍历旧数组,往新数组中放置旧数组的元素,遍历新数组,判断如果新数组中有相同的元素,就不往新数组中放了。
  • 先排序,后删除相邻重复的元素: 例如: 可以手写排序算法先将数组进行排序, 也可以使用数组方法 sort() 对数组进行排序, 之后相邻两两进行判断是否重复, 重复即使用数组方法 splice() 进行去重。
  • 遍历数组判断是否存在相同元素: 例如: 建一个新数组, 遍历原数组, 在循环中新数组调用 indexOf() 查找新数组中是否存在原数组的该元素, 若返回值为 -1, 即将该元素添加到新数组, 最后新数组即为去重后的数组。
  • 遍历数组, 依次比对直至无元素重复: 例如: 遍历数组, 遍历次数为数组长度-1, 从下标 0 的位置开始, 调用方法 indexOf() 判断该元素是否重复, 重复复及去除该元素(数组将塌陷一个单位), 循环索引-1, 重复判断该下标位置直至该下标位置不存在重复项, 才进入下一个位置循环, 直至数组去重完成。利用了indexOf()方法(indexOf()方法如果查询到则返回查询到的第一个结果在数组中的索引,如果查询不到则返回-1)。先创建一个新的空数组用来存储新的去重的数组,然后遍历arr数组,在遍历过程中,分别判断newArr数组里面是不是有遍历到的arr中的元素,如果没有,直接添加进newArr中,如果已经有了(重复),那么不操作,那么从头到尾遍历一遍,正好达到了去重的目的。
  • 利用对象的性质进行数组去重,原理: 对象的 key 唯一, 且可以为 number 类型: 例如: 准备一个空对象和空数组, 遍历需去重数组, 将数组元素以对象的 key 的形式’存储’下来, 然后遍历(for…in…)对象, 将对象的 key 添加到数组, 从而实现数组去重。
  • 利用数据类型 Set 的性质进行数组去重: 原理: Set 类型的变量不接受重复数据 例如: 将需去重的数组转为 Set 数据类型, 该过程即完成去重操作, 在转为数组即可完成数组去重。
    var arr = [1,2,2,4,3,4,1,3,2,7,5,6,1]
    
    var newArr = new Set(arr)
    
  • indexof(): indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
  • 排序算法: blog.csdn.net/qq_38455499…

6. for...in 和 for...of 的区别

  • for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
  • for...in:
    是ES5的语法标准
    遍历的是数组的下标
    可以得到对象的key,数组、字符串的下标,还有对象原型链上的方法名和属性名,但是取不到对象的值
    不能遍历 map/set 
    不能迭代 generators(生成器) 
    IE 支持
  • for...of:
    是ES6新增的遍历方式
    返回数组下标对应的的属性值
    遍历获取的是对象的键值,不会遍历原型链
    可以遍历 map/set 
    可以迭代generators 
    IE 不支持
    可以遍历一个含有iterator接口的数据结构,并且返回各项的值
    不能遍历普通对象,遍历对象则会报错,因为能够被for of正常遍历的数据类型都需要实现一个遍历器Iterator。而数组、字符串、Set、Map结构,早就内置好了Iterator(迭代器),它们的原型中都有一个Symbol.iterator方法,而Object对象并没有实现这个接口,使得它无法被for of遍历。
  • 可以通过为Object实现一个Iterator来使Object可以使用正常for of遍历
    Object.prototype[Symbol.iterator] = function() {
      let _this = this
      let index = 0
      let length = Object.keys(_this).length
      return {
          next:() => {
              let value = _this[Object.keys(_this)[index]]
              let done = (index >= length)
              index++
              return {value,done}
          }
      }
    }
    for(let i of obj){
      console.log(i)
    }
    

7. ForEach和map的区别

首先它们两个都可以用来遍历数组

  • forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
  • map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;

8. Object.keys()

  • 定义: 是ES5新增的方法,可以用来遍历对象,将对象中的key以数组的形式返回。
  • 遍历数组、字符串时,返回它们的的索引。

9. 类数组

  • 定义: 类数组拥有length属性,它的索引都是非负整数,它和数组类似,但是不具有数组的方法,像push,pop那些,类数组本质是一个普通对象,而数组的话会返回Array类型。类数组对象有auguments、DOM方法的返回结果、函数参数。
  • 类数组转换为数组:
    • 通过call调用数组的slice方法
      Array.prototype.slice.call(arrayLike)
      
    • 通过call调用数组的splice方法来实现转换
      Array.prototype.splice.call(arrayLike, 0)
      
    • 通过Array.from方法转换
       Array.from(arrayLike)
      
    • 通过applay调用数组的concat方法进行转换
      Array.prototype.concat.apply([], arrayLike)
      
  • auguments:
    • 定义: 是一个对象,它的属性从0开始依次递增,有calle、length等属性,与数组类似,但没有数组常见的方法,pop,push等。
    • 遍历类数组: 先将类数组转换成数组,再调用数组的遍历方法。

闭包

1. 定义及触发条件

  • 定义: 闭包是一个能够读取其他函数内部变量的函数。只要内部的函数存在外部作用域的引用就会形成闭包,它是由函数函数内部能够访问到外部的变量共同组成的,它可以间接访问一个变量。举个例子,比如函数A内部有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包。
  • 产生原因: 闭包是 JS 函数作用域的一个产物,在js中,执行函数的时候,会给函数创建属于自己的执行期上下文,当函数执行结束的时候,会销毁掉这个作用域。但是在一个函数嵌套里,里面的函数可以访问到外面函数的变量,当函数执行完毕后,就不会回收掉这个变量,这个时候,这个没有被回收掉的变量和子函数的总和就是一个闭包。
  • 触发: 函数作为参数被传递,在别处定义好的函数拿到这里使用;函数作为返回值被返回时,在这里定义好的函数拿到别处使用。
  • 用途:
    • 创建私有变量: 可以通过在外部调用闭包函数,让我们在函数外部能够访问到函数内部的变量。
    • 使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了对变量对象的引用,所以这个变量对象不会被回收。
  • 优缺点
    • 优点:
      1. 可以访问到函数内部的局部变量
      2. 可以避免全局变量的污染
      3. 闭包函数保留了对变量对象的引用,这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
    • 缺点: 会增大内存使用量,滥用闭包会影响性能,导致内存泄漏等问题。
  • 闭包的应用场景:
    • 在一个函数里面作为返回值。
    • 在闭包里面给一个变量赋值。
    • 函数作为参数:用闭包返回一个函数,把此函数作为另一个函数的参数,在另一个函数里面执行这个函数。
    • 循环赋值的时候,那么闭包就会形成很多个互不干扰的私有作用域。
    • 使用的回调函数。
    • 节流、防抖。

2. 作用域、作用域链

  • 作用域: 定义了变量或函数有权访问的数据,最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
  • 全局作用域: 最外层函数和最外层函数外面定义的变量拥有全局作用域,所有未定义直接赋值的变量自动声明为全局作用域,所有window对象的属性拥有全局作用域,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。
  • 块级作用域: 在ES6中,使用let、const指令可以声明块级作用域,块级作用域的话,可以在函数中创建也可以在一个代码块中的创建。解决了内层变量可能覆盖外层变量这种情况;也可以用来防止计数的循环变量泄露为全局变量。
  • 作用域链: 在当前作用域中查找需要的变量,如果这个作用域没有这个变量,就会去父级作用域查找,依次向上级作用域查找,直到访问到window对象就会被终止,这样一层层的关系就是作用域链。通过作用域链,可以访问到外层环境的变量和函数。

3. 执行上下文

  • 定义: 当 JS 引擎解析到可执行代码片段的时候,就会先做一些执行前的准备工作,这个准备工作就叫做 执行上下文 也就是执行环境。当开始执行代码时,js引擎会创建一个执行栈,执行时,先把全局执行上下文压入栈中,之后每当有函数调用时,将函数的执行上下文也压入栈中,执行结束弹出函数执行上下文
  • 分类:
    • 全局执行上下文——任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
    • 函数执行上下文——每当一个函数被调用时,都会创建一个新的函数执行上下文。
    • Eval 函数执行上下文—— 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,不太使用。
  • 过程:
    • 创建阶段(预编译阶段):会先进行语法分析,然后进行代码的预编译,生成变量对象,建立作用域链,确认this指向,绑定this。(创建执行上下文,找形参和变量的声明,值赋为undefined,统一形参和实参,找到函数声明,赋予函数体)。
    • 执行阶段:变量赋值、函数引用、执行其它代码。

垃圾回收

1. 概念

  • 当js代码运行时,需要分配内存来存储变量和值,当这些变量不参与运行,或者不能从根上访问到的时候,就需要被系统回收,释放被占用的内存空间,也就是垃圾回收的过程。

2. 回收机制

  • Js 具有自动垃圾回收机制,会定期对不再使用的变量、对象所占用的内存进行释放, 原理就是只要找到不再使用的变量,就会释放掉它所占用的内存。
  • JS中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续到页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
  • 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。

3. 垃圾回收的方式

  • 标记清除: 这个方法是浏览器常见的垃圾回收方式。它在变量进入环境和离开环境时都会标记这个变量,然后在将变量标记为离开时,就会释放这个变量所占的内存。
  • 引用计数: 相对来说,这个办法用的比较少。具体做法,它会跟踪记录每一个变量值,当这个值被引用一次,就会给它+1,当引用失败或者引用它的变量引用了其他值,那就会给它减-1,直到它的引用次数为 0 时,说明这个变量也没有价值了,那么,在回收的时候,就会把这个变量所占用的空间释放出来。

4. 减少垃圾回收:

  • 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但同时也会创建一个新的空对象。可以将数组的长度设置为0,以此来达到清空数组的目的。
  • 对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
  • 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。

原型、原型链、继承

1. 概念

image.png

  • 原型: 原型,简单来说,它就是一个对象模板,它定义了构造函数制造出的对象的公共祖先,通过这个构造函数产生的对象,可以继承该原型的属性和方法。

  • 原型链: 当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,就形成了原型链。原型链的尽头,可以通过Object.prototype.proto === null。

  • 构造函数: 任何一个函数,只要被 new 了以后,就可以作为一个构造函数,而 new 出来的被叫做实例,每个构造函数的 prototype 原型对象里的 constructor 指向 构造函数 本身。。

  • prototype: 每个构造函数中都有一个 显示原型prototype 属性,他是一个指针,指向一个对象,这个对象的用途是包含所有实例共享的方法和属性,我们把这个对象叫做原型对象。声明一个函数时候就会有个 prototype 属性,这个属性会初始化一个空对象,也就是原型对象,原型对象中会有一个构造器: constructor,它默认会指向声明的那个函数。

  • proto 每个引用类型都有一个__proto__(隐式原型)属性。对象的__proto__属性,指向它的构造函数的原型对象,也就是(Function.prototype)。

  • Object.create() 创建的对象没有__proto__属性

2. 原型链的终点

  • 原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是
    Object.prototype.__proto__ === null
    

3. 获取对象的原型 Object.getPrototypeOf(obj)

  • 会返回指定对象的原型,它里面可以传递一个指定的对象,然后进行查询,如果没有继承属性,则返回 null。

4. 继承(六种继承方式)

  • 原型链继承: 就是让子类的原型等于父类的实例对象,这样继承的子类的实例共享了父类实例上的方法,当一个子类实例修改了父类实例的属性,其他子类实例的属性就会发生变化。但是缺点是:过多的继承了没用的属性,构造实例的时候不能向父类的构造函数传递参数。
  • 借用构造函数继承: 在创建实例的时候可以向父类构造函数传递参数,可以实现多继承,缺点:子类实例没有继承到父类原型上的属性和方法。
  • 组合式继承: 就是原型链继承和构造函数继承的结合,它的特点是,可以向父类的构造函数传递参数,也可以继承父类原型上的属性和方法,但是每次创建函数的时候,都会调用两次父类的构造函数,造成性能上和内存的损耗。
  • 原型式继承: 类似于复制一个对象,用函数包装起来,这样的继承会使所有原型都会继承原型上的属性,但是创建出来的新实例的属性都是要在后面添加的。
  • 寄生式继承: 寄生式继承相当于给原型式继承又套了一层函数传递参数,可以为原型式继承创建的对象添加属性。
  • 寄生组合式继承: 解决了组合式继承的缺点:调用两次父类的构造函数。

Ajax请求

1. 概念

  • 可以在不使页面重新加载刷新的情况下,与服务器进行交互,并更新网页部分区域的技术。

2. 原理

  • 通过XMLHttpRequest对象来向服务器发送请求,然后通过js来操作DOM实现页面的更新,就是在客户端和服务器端加了一个中间层,使得用户操作和对服务器的请求异步。
  • Promise实现
      function ajax(url, methods) {
        return new Promise((resolve, reject) => {
          var xhr = new XMLHttpRequest()
          // 初始化
          xhr.open(methods, url, true)
          // 发送
          xhr.send()
          // 处理响应结果
          xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response)
              } else {
                reject(xhr.status)
              }
            }
          }
        })
    }
    

2. ajax、axios、fetch的区别

  • axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。 简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。 (axios是ajax ajax不止axios)
  • fetch没有办法原生监测请求的进度,而ajax基于原生的XHR开发,可以监测,ajax多用于jquery项目,不符合现在前端MVVM的浪潮,axios支持 Promise API、支持并发请求;
  • 和ajax相比,fetch有着更好更方便的写法,它不是对ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象;但是它的兼容性比较差。
  • fetch只对网络请求报错,对400、500都当做成功的请求,而ajax不会。

3. 301和302区别

301:永久重定向,意为旧的URL已经不再使用,已永久转移至新的地址。
302:临时重定向,意为某个时间段因为某些原因临时进行的跳转行为,旧的URL地址依然使用并存在。

防抖节流

1. 出现原因:

  • 在前端开发中,有一部分的用户行为会频繁的触发事件执行,而对于DOM操作、资源加载等耗费性能的处理,很可能导致界面卡顿,甚至浏览器的崩溃。函数节流和函数防抖就是为了解决类似需求应运而生的。
  • 函数节流: 就是预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。好像水滴攒到一定重量才会落下一样。
  • 场景: 窗口调整(resize) 页面滚动(scroll) 抢购疯狂点击(mousedown)

2. 柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。目的: 简化代码结构,提高系统的维护性,一个方法,只有一个参数,强制了功能的单一性,很自然就做到了功能内聚,降低耦合。 柯理化的优点就是降低代码的重复,提高代码的适应性

function add(a,b,c){} ; 
var newAdd = Curry(add);
newAdd(1)(2)(3)

image.png

image.png

image.png