那些你面试必须知道的JS知识点

1、JS数据类型有哪些?

基本类型:string 、number、boolean、undefined、null、symbol
引用类型:object

数据类型详细内容:https://blog.csdn.net/Zhang_wang_yun/article/details/129959316?spm=1001.2014.3001.5502
考题:

 <script>
      alert(true + 1) // 2
      alert('name' + true) // nametrue
      alert(undefined + 1) //NaN
      alert(typeof null) // object
      alert(typeof NaN); //number
      alert(typeof undefined); //undefined
      alert(typeof null); //object
    </script>

注:
NaN是一个数值类型,但是不是一个具体的数字。

2、延迟加载JS有哪些方式?

延迟加载:async、defer、setTimeout…

1、默认加载:<script>

在这里插入图片描述
在这里插入图片描述

让我们从定义没有任何属性的作用开始。HTML 文件将被解析,直到脚本文件命中,此时解析将停止,并且将请求获取文件(如果它是外部文件)。然后,将在恢复解析之前执行脚本。

我们来看这一段代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script  src="./index.js"></script>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

</html>

const box = document.querySelector('.box');
console.log(box);

按照上面的解析方式,我们的box是获取不到的
在这里插入图片描述
如果我们改变一下顺序,此时就能获取到box

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="box"></div>
    <script  src="./index.js"></script>
  </body>
</html>


在这里插入图片描述

2、<script async>
在这里插入图片描述

async在 HTML 解析期间下载文件,并在完成下载后暂停 HTML 解析器以执行该文件。

简单来说就是:async是和html解析同步的(一起的),如果有多个文件,它不是顺次执行js脚本(谁先加载完谁先执行)。

我们可以使用async进行延迟加载解决刚才获取不到dom的问题:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script async src="./index.js"></script>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

3、<script defer>
在这里插入图片描述

defer在 HTML 解析期间下载文件,并且仅在解析器完成后执行它。 脚本还保证按它们在文档中出现的顺序执行。

简单来说就是:等html全部解析完成,才会执行js代码,顺次执行js脚本。
同样它也能解决刚才的问题:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script defer src="./index.js"></script>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

async vs defer 官网链接:https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

定时器就不说了,大家应该都很熟悉。

3、== 和 ===有什么不同?

1、==比较的是
string == number || boolean || number …都会隐式转换

   <script>
      console.log(1 == "1"); // true
      console.log(true == 1); // true
      console.log(null == undefined); // true
      console.log([1, 2] == "1,2"); // true
    </script>

既然它会进行隐式转换,那么我们再来看这一段代码:

 <script>
      let s = "2";
      if (s == 2) {
        console.log(typeof s);
      }
    </script>

字符串s和数组2进行比较,通过隐式转换,s它会变成number类型的2,然后进行打印,那么打印的结果应该是我们想的number,但其实并不是。
在这里插入图片描述
这是为什么呢?

原因是因为隐式转换它是通过valueOf转换(valueOf() 方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中。) 所以最后打印的还是string类型。

我们再来看这一段代码,证明是不是这么一回事:

 	 Object.prototype.valueOf = function () {
        alert("111");
      };
      let arr = [1, 2];
      let str = '1,2'
      console.log(arr == str);

我们对valueOf进行重写,此时我们再看结果,它变成了false

在这里插入图片描述

注:只有当其中一个值为引用类型时,重写才会生效。

   <script>
      Object.prototype.valueOf = function () {
        alert("111");
      };
      let arr = 1;
      let str = "1";
      console.log(arr == str);
    </script>

在这里插入图片描述
2、=== : 除了比较值,还比较类型

   <script>
      console.log(1 === "1"); // false
      console.log(false === 1); // false
      console.log(null === undefined); // false
      console.log([1, 2] === "1,2"); // false
    </script>

4、null和undefined的区别

在起初1995年javascript诞生时,最初像java一样,只设置了null作为表示“无”的值,根据C语言的传统,null会被隐式转换成0,这样很不容易发现错误。

并且null像在java里一样被当作一个对象,但是,javascript的数据类型分为基本数据类型和引用数据类型两大类,作者觉得表示“无”的值最好不是对象。

所以作者又设计了一个基本数据类型undefined。

它们的具体区别:JavaScript的最初版本是这样区分的:null是一个表示"无"的对象(空对象指针),转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

 	<script>
      console.log(Number(null));
      console.log(Number(undefined));
    </script>

在这里插入图片描述

5、JS微任务和宏任务

在谈微任务和宏任务之前我们先来了解一下javascript。
1、为什么JavaScript是单线程语言?
javascript语言的一大特点就是单线程语言,也就是说,同一个时间只能做一件事情,为什么JavaScript不能有多线程呢?这样能提高效率。

这主要与它的用途有关,作为浏览器脚本语言,javascript的主要用途时与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

比如,假定javascript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,javascript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

2、javascript是同步代码,如何执行异步代码?
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前个任务耗时很长,后一个任务就不得不一直等着。

消息队列: 消息队列是一个先进先出的队列,它里面存放着各种消息。
事件循环: 事件循环是指主线程重复从消息队列中取消息、执行的过程

实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息再执行,当消息队列为空时,就会等待直到消息队列变成非空。
而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行。

3、js的执行流程:
同步执行完==》事件循环
同步的任务都执行完了,才会执行事件循环的内容
进入事件循环:请求(ajax)、定时器、事件…
在事件循环中包含:【微任务、宏任务】
比如:
微任务:promise.then
宏任务:setTimeout
要执行宏任务的前提是清空了所有的微任务
流程:同步==》事件循环【微任务和宏任务】== 》微任务==》宏任务=》微任务…
在这里插入图片描述

我们来看一些代码进行实战演练:
示例1:

<script>
      for (var i = 0; i < 3; i++) {
        setTimeout(() => {
          console.log(i);
        }, 1000 * i);
      }
    </script>
  第一次循环
    同步任务: for (var i = 0; i < 3; i++) {    
    异步任务:
     setTimeout(() => {
      console.log(i);
    }, 1000 * 0);
    第二次循环
    同步任务: for (var i = 0; i < 3; i++) {   
      异步任务:
      setTimeout(() => {
       console.log(i);
     }, 1000 * 1);
     第三次循环
     同步任务: for (var i = 0; i < 3; i++) {   
      异步任务:
      setTimeout(() => {
       console.log(i);
     }, 1000 * 2); 
     第四次循环,i=3,跳出循环

按照流程:同步==》事件循环【微任务和宏任务】== 》微任务==》宏任务=》微任务…
所有的同步任务执行完,i变成了3,然后依次执行宏任务,所以结果是:
0秒输出3,
1秒后输出3
2秒后输出3

如果将var改为let,那么结果又不一样了。

	<script>
      for (let i = 0; i < 3; i++) {
        setTimeout(() => {
          console.log(i);
        }, 1000 * i);
      }
    </script>
    第一次循环
    同步任务: for (var i = 0; i < 3; i++) {    
    异步任务:
     setTimeout(() => {
      console.log(i); i = 0
    }, 1000 * 0);
    第二次循环
    同步任务: for (var i = 0; i < 3; i++) {   
      异步任务:
      setTimeout(() => {
       console.log(i); i = 1
     }, 1000 * 1);
     第三次循环
     同步任务: for (var i = 0; i < 3; i++) {   
      异步任务:
      setTimeout(() => {
       console.log(i); i = 2
     }, 1000 * 2); 
     第四次循环,i=3,跳出循环

因为 let关键字拥有块级作用域,i并不会被后面的i重新赋值,因为它们已经不在一个作用域里了,所以结果是:
0秒输出0,
1秒后输出1
2秒后输出2

示例二:

 <script>
      setTimeout(() => {
        console.log("我是定时器");
      }, 0);
      new Promise((resolve, reject) => {
        console.log("我是promise");
        resolve();
      }).then((res)=>{
        console.log('我是then1')
      }).then(()=>{
        console.log('我是then2');
      });
      console.log("1");
    </script>
 同步任务:
    1、 new Promise((resolve, reject) => {
       console.log("我是promise");
       resolve();
    })
    2、  console.log("1");
    同步任务执行完进入事件循环
    微任务:
    then((res)=>{
      console.log('我是then1')
    }).then(()=>{
      console.log('我是then2');
    });
    清空所有微任务进入宏任务
    setTimeout(() => {
      console.log("我是定时器");
    }, 0);

所以结果是:
在这里插入图片描述

6、作用域考题

  1. 除了函数外,js是没有块级作用域。
  2. 作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。 注意:如果内部有,优先查找到内部,如果内部没有就查找外部的。
  3. 注意声明变量是用var还是没有写(window.)
  4. 注意:js有变量提升的机制【变量悬挂声明】
  5. 优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

考题一:

  <script>
      function c() {
        var b = 1;
        function a() {
          console.log(b);
          var b = 2;
          console.log(b);
        }
        a();
        console.log(b);
      }
      c();
      // 结果:
      // undefined
      // 2
      // 1
    </script>

考题二:

   <script>
      var name = "a";
      (function () {
        if (typeof name == "undefined") {
          var name = "b";
          console.log("111" + name);
        } else {
          console.log("222" + name);
        }
      })();
    </script>
    <!-- 结果:
    111b -->

考题三:

	<script>
      function fun(a) {
        var a = 10;
        function a() {}
        console.log(a);
      }
      fun(100);
    </script>
    <!-- 结果:
    10 -->

7、JS对象考题

考题一:

	<script>
      console.log([1, 2, 3] === [1, 2, 3]); //false
    </script>

两个数组都是new创建出来的,虽然内容相同,但是地址是不同的,所以为false

考题二:

 <script>
      var obj1 = {
        a: "hellow",
      };
      var obj2 = obj1; //将obj1直接赋值给obj2,它们指向同一个地址
      obj2.a = "world"; //将obj2里面的a值改变,obj1也会跟着变化
      console.log(obj1); //{a:world}
      (function () {
        console.log(a); //undefined 
        var a = 1; //var定义的变量,会进行变量提升
      })();
    </script>

考题三、

	<script>
      var a = {};
      var b = {
        key: "a",
      };
      var c = {
        key: "c",
      };
      a[b] = "123"; 
      a[c] = "456";
      console.log(a[b]); // 456
      for(let k in a) {
        console.log(k) //[object Object]
      }
	 </script>

对象的key值都是字符串类型,在a[b]的时候,它会将b转换为字符串,b作为对象,转换为字符串就变成了【object object】

  <script>
    function Fn() {
        this.a = 'zFn里添加的'
    }
    Fn.prototype.a = '函数原型添加到'
    let obj  = new Fn();
    obj.a = '对象添加的'
    obj.__proto__.a = '对象原型添加的'
    console.log(obj.a);
</script>

总结:

  1. 对象是通过new操作符构建出来的,所以对象之间不想等(除了引用外);
  2. 对象注意:引用类型(共同一个地址);
  3. 对象的key都是字符串类型;
  4. 对象如何找属性|方法; 查找规则:先在对象本身找 ===> 构造函数中找 ===> 对象原型中找 ===> 构造函数原型中找 ===> 对象上一层原型查找

8、JS作用域+this指向+原型考题

考题一:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      function Foo() {
        getName = function () {
          console.log(1);
        }; //注意是全局的window.
        return this;
      }

      Foo.getName = function () {
        console.log(2);
      };
      Foo.prototype.getName = function () {
        console.log(3);
      };
      var getName = function () {
        console.log(4);
      };
      function getName() {
        console.log(5);
      }

      Foo.getName(); //2
      getName(); //4
      Foo().getName(); // 1
      getName(); //1
      new Foo().getName(); //3
    </script>
  </body>
</html>

注:函数加()和不加会造成完全不同的结果。

  Foo.getName(); 
  Foo().getName(); 

我们再来看刚才那道题的答案(加())

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      function Foo() {
        getName = function () {
          console.log(1);
        }; //注意是全局的window.
        return this;
      }

      Foo.getName = function () {
        console.log(2);
      };
      Foo.prototype.getName = function () {
        console.log(3);
      };
      var getName = function () {
        console.log(4);
      };
      function getName() {
        console.log(5);
      }

      Foo().getName(); //1
      getName(); //1
      Foo().getName(); // 1
      getName(); //1
      new Foo().getName(); //3
    </script>
  </body>
</html>

考题二:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      var o = {
        a: 10,
        b: {
          a: 2,
          fn: function () {
            console.log(this.a); // 2
            console.log(this); //代表b对象
          },
        },
      };
      o.b.fn();
    </script>
  </body>
</html>

考题三:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      window.name = "ByteDance";
      function A() {
        this.name = 123;
      }
      A.prototype.getA = function () {
        console.log(this);
        return this.name + 1;
      };
      let a = new A();
      let funcA = a.getA;
      // funcA(); //this代表window
      console.log(funcA())
    </script>
  </body>
</html>

在这里插入图片描述
我们将代码改造一下:
让let funcA = a.getA;变为

  let funcA = a.getA();
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      window.name = "ByteDance";
      function A() {
        this.name = 123;
      }
      A.prototype.getA = function () {
        console.log(this);
        return this.name + 1;
      };
      let a = new A();
      let funcA = a.getA();
      // this指向a
      console.log(funcA)
    </script>
  </body>
</html>

在这里插入图片描述

考题四:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script defer src="./index.js"></script> -->
  </head>
  <body>
    <script>
      var length = 10;
      function fn() {
        return this.length + 1;
      }
      var obj = {
        length: 5,
        test1: function () {
          return fn();
        },
      };
      obj.test2 = fn;
      console.log(obj.test1()); //11
      console.log(fn() === obj.test2()); //false
      console.log(obj.test1() == obj.test2()); //false
    </script>
  </body>
</html>

9、JS判断变量是不是数组,你能写出哪些方法?

方式一:isArray

 <script>
      let arr = [1, 2, 3];
      let str = "123";
      let isArray = Array.isArray(arr);
      let isArray1 = Array.isArray(str);
      console.log(isArray); //true
      console.log(isArray1); //false
    </script>

方式二:instanceof

 <script>
      let arr = [1, 2, 3];
      let str = "123";
      let isArray = arr instanceof Array;
      let isArray1 = str instanceof Array;
      console.log(isArray); //true
      console.log(isArray1); //false
    </script>

方式三:原型prototype

 <script>
      let arr = [1, 2, 3];
      let str = "123";
      let isArray = Object.prototype.toString.call(arr).indexOf('Array') > -1;
      let isArray1 =  Object.prototype.toString.call(str).indexOf('Array') > -1;
      console.log(isArray); //true
      console.log(isArray1); //false
    </script>

方式四:isPrototypeOf()

	<script>
      let arr = [1, 2, 3];
      let str = "123";
      let isArray = Array.prototype.isPrototypeOf(arr);
      let isArray1 =  Array.prototype.isPrototypeOf(str);
      console.log(isArray); //true
      console.log(isArray1); //false
     </script>

方式五:constructor

   <script>
      let arr = [1, 2, 3];
      let str = "123";
      let isArray = arr.constructor.toString().indexOf("Array") > -1;
      let isArray1 = str.constructor.toString().indexOf("Array1") > -1;
      console.log(isArray); //true
      console.log(isArray1); //false
    </script>

10、slice是干嘛的、splice是否会改变原数组

1. slice是来截取的(可截取字符串,也可以截取数组)
	参数可以写slice(3)、slice(1,3)、slice(-3)
	返回的是一个新的数组
2. splice 功能有:插入、删除、替换
	返回:删除的元素
	该方法会改变原数组

11、JS数组去重

方式一:new set

   <script>
   	  // Array.from转换为真数组
      function unique(arr) {
        let newArr = new Set(arr);
        return Array.from(newArr);
      }
      console.log(unique([1, 2, 3, 1, 2, 4, 4, 5]));
    </script>
   
   <script>
      // 扩展运算符转化为真数组
      function unique(arr) {
        let newArr = new Set(arr);
        return [...newArr];
      }
      console.log(unique([1, 2, 3, 1, 2, 4, 4, 5]));
    </script>
    

在这里插入图片描述

方式二:indexOf

 <script>
      //创建一个新数组,通过indexOf判断新数组中是否有传入数组中对应的值,
      //如果== -1表示没有,则追加进新数组中。
      function unique(arr) {
        let newArr = [];
        for (let i = 0; i < arr.length; i++) {
          if (newArr.indexOf(arr[i]) == -1) {
            newArr.push(arr[i]);
          }
        }
        return newArr;
      }
      console.log(unique([1, 2, 3, 1, 2, 4, 4, 5]));
    </script>

方式三:sort

   <script>
      // 先将传入的数据进行排序,如果当前值不等于前一个值,则追加进新数组
      function unique(arr) {
        arr = arr.sort();
        let newArr = [];
        console.log(arr);
        for (let i = 0; i < arr.length; i++) {
          if (arr[i] !== arr[i - 1]) {
            newArr.push(arr[i]);
          }
        }
        return newArr;
      }
      console.log(unique([1, 2, 3, 1, 2, 4, 4, 5]));
    </script>

12、找出多维数组最大值

   <script>
      // 先创建一个新数组,对传入进来的数据进行遍历循环,
      //使用数学Math.max方法查询该数组中的最大值,然后追加进新创建的数组
      function fnArr(array) {
        let newArr = [];
        array.forEach((item, index) => {
          newArr.push(Math.max(...item));
        });
        return newArr;
      }
      console.log(
        fnArr([
          [4, 5, 1, 3],
          [13, 27, 18, 26],
          [32, 35, 37, 39],
          [1000, 1001, 857, 1],
        ])
      );
    </script>

13、给字符串新增方法实现功能

给字符串对象定义一个addPrefix函数,当传入一个字符串str时,它会返回新的带有指定前缀的字符串,例如:

console.log( ‘world’.addPrefix(‘hello’) ) 控制台会输出helloworld

	<script>
      // 在字符串原型上添加一个addPrefix方法,因为函数里面的this是word,
      // 所以只需要将传入的值和this进行拼接即可(谁调用此函数,则this指向谁)
      String.prototype.addPrefix = function (prefix) {
        return prefix + this;
      };
      console.log("word".addPrefix("hello"));
    </script>

14、new操作符具体做了什么

  1. 创建了一个空的对象

我们来验证一下:

<script>
      function fn() {
       
      }
      console.log(new fn());
    </script>

在这里插入图片描述

  1. 将空对象的原型,指向于构造函数的原型
<script>
      function fn() {}
      console.log(fn.prototype == new fn().__proto__);
    </script>

在这里插入图片描述

  1. 将空对象作为构造函数的上下文(改变this指向)
 <script>
      function fn() {
        console.log(this);
        this.name = "东方青云";
      }
      console.log(fn());
    </script>

在这里插入图片描述
我们给fn函数加上new操作符

<script>
      function fn() {
        console.log(this);
        this.name = "东方青云";
      }
      console.log(new fn());
    </script>

在这里插入图片描述

  1. 对构造函数有返回值的处理判断
<script>
      function fn() {
        this.name = "占山";
        return 111;
      }
      console.log(new fn());
    </script>
    <script>
      function fn() {
        this.name = "占山";
        return true;
      }
      console.log(new fn());
    </script>

在这里插入图片描述
如果renturn 返回的是基本数据类型,则对new操作符没有影响。

<script>
      function fn() {
        this.name = "占山";
        return {name:'哈哈哈'};
      }
      console.log(new fn());
    </script>

在这里插入图片描述

 <script>
      function fn() {
        this.name = "占山";
        return ['哈哈哈哈哈'];
      }
      console.log(new fn());
    </script>

在这里插入图片描述
如果return 返回的是引用数据类型,则new操作符失效。

我们自己来写一个:

<script>
      function fn(name, age) {
        this.name = name;
        this.age = age;
      }
      function create(fn, ...args) {
        //1. 创建了一个空的对象
        let obj = {}; //Object.create({})
        //2. 将空对象的原型,指向于构造函数的原型
        Object.setPrototypeOf(obj, fn.prototype);
        //3. 将空对象作为构造函数的上下文(改变this指向)
        let result = fn.apply(obj, args);
        //4. 对构造函数有返回值的处理判断
        return result instanceof Object ? result : obj;
      }
      console.log(create(fn, "东方青云", 18));
    </script>

15、找出字符串出现最多次数的字符以及次数

 let str = "aaabbbbbccddddddddddx";
      let obj = {}; //先创建一个对象,将字符串转成对象的形式:如{a:3,b:5}........
      for (let i = 0; i < str.length; i++) {
        let char = str.charAt(i);
        // 如果该对象有此字符,则++
        if (obj[char]) {
          obj[char]++;
        } else {
          // 没有,则为1次
          obj[char] = 1;
        }
      }
      console.log(obj);
      // {a: 3, b: 5, c: 2, d: 10, x: 1}
      //统计出对象中的最大值
      let max = 0;
      for (let key in obj) {
        if (max < obj[key]) {
          max = obj[key];
        }
      }
      console.log(max);
      //拿最大值去对比
      for (let key in obj) {
        if (obj[key] == max) {
          console.log("出现最多的字符是" + key);
          console.log("次数是" + max);
        }
      }

16、闭包

  1. 闭包是什么
    闭包是一个函数加上到创建函数的作用域的连接,闭包“关闭”了函数的自由变量。

我们来看一段代码

	<script>
      function fn() {
        return function () {};//一个函数
      }//创建函数
    </script>

上面这段代码就是一个闭包,里层函数的作用域连接着外层函数的作用域。
那么什么是闭包关闭了函数的自由变量呢?
我们再来看一段代码:

	<script>
      function fn() {
        let a = 10
        return function () {
          console.log(a)
        };
      }
      fn()();
    </script>

在这里插入图片描述

正常来说当一个函数执行后,它里面定义的变量会进行垃圾回收机制,变量a会被清理掉,但是在里层函数中我们打印出了a,说明a并没有被回收掉。
所以这就是闭包关闭了函数的自由变量。

  1. 闭包可以解决什么问题【闭包的优点】
    2.1 内部函数可以访问到外部函数的局部变量
    2.2 闭包可以解决的问题

我们来看这样一段代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    ul {
      list-style: none;
    }
    ul li {
      background-color: red;
      margin: 10px;
    }
  </style>
  <body>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
    </ul>
    <script>
      var lis = document.querySelectorAll("ul li");
      console.log(lis);
      for (var i = 0; i < lis.length; i++) {
        lis[i].addEventListener("click", function () {
          alert(i);
        });
      }
    </script>
  </body>
</html>

在这里插入图片描述
我们随便点击一个li,它最后弹出的数字都为4,但是我们想让它以0,1,2,3它们对应的索引进行展示,该如何做到?我的答案是使用闭包或者let。
我们先来探究一下为什么会产生这种情况,大家都知道js的执行机制是先将同步任务执行完,才会进入事件循环,所以最后i会变为4.
使用闭包解决:

  <script>
      var lis = document.querySelectorAll("ul li");
      console.log(lis);
      for (var i = 0; i < lis.length; i++) {
        (function (i) {
          lis[i].addEventListener("click", function () {
            alert(i);
          });
        })(i);
      }
    </script>

将i注入到一个函数中,i会保存在内存当中,解决。
使用let解决:

<script>
      var lis = document.querySelectorAll("ul li");
      console.log(lis);
      for (let i = 0; i < lis.length; i++) {
        lis[i].addEventListener("click", function () {
            alert(i);
        });
      }
    </script>

由于let会产生独立作用域,所以也能解决这个问题。

  1. 闭包的缺点
    3.1 变量会驻留在内存中,造成内存损耗问题。
    3.2 内存泄漏【ie】 ,只有ie浏览器才会产生内存泄漏。

如何解决内存损耗问题:把闭包的函数设置为null

 <script>
      var lis = document.querySelectorAll("ul li");
      console.log(lis);
      for (let i = 0; i < lis.length; i++) {
        (function () {
          lis[i].addEventListener("click", function () {
            alert(i);
          });
          lis[i] = null;
        })();
      }
    </script>

17、原型链

1、原型可以解决什么问题?

原型主要用来实现对象共享属性和共享方法

2、谁有原型

函数有prototype
对象有__proto__

  1. 对象查找属性或者方法的顺序

我们来看代码演示:

	<script>
     function fn() {
         this.a = "2";
      }
      fn.prototype.a = "4";
      let obj = new fn();
      obj.a = "1";
      obj.__proto__.a = "3";
      Object.prototype.a = "5";
      console.log(obj.a); //1
    </script>

结果为1
我们将obj.a注释掉

<script>
      function fn() {
         this.a = "2";
      }
      fn.prototype.a = "4";
      let obj = new fn();
      //obj.a = "1";
      obj.__proto__.a = "3";
      Object.prototype.a = "5";
      console.log(obj.a); //2
    </script>

结果为2
我们将this.a ="2"注释掉

<script>
 function fn() {
        // this.a = "2";
      }
      fn.prototype.a = "4";
      let obj = new fn();
      // obj.a = "1";
      obj.__proto__.a = "3";
      Object.prototype.a = "5";
      console.log(obj.a); //3
 </script>

结果为3
我们将obj.proto.a = “3”;注释掉

	<script>
      function fn() {
        // this.a = "2";
      }
      fn.prototype.a = "4";
      let obj = new fn();
      // obj.a = "1";
      // obj.__proto__.a = "3";
      Object.prototype.a = "5";
      console.log(obj.a); //4
    </script>

结果为4
我们将 fn.prototype.a = “4”;注释掉

 <script>
      function fn() {
        // this.a = "2";
      }
      //fn.prototype.a = "4";
      let obj = new fn();
      // obj.a = "1";
      // obj.__proto__.a = "3";
      Object.prototype.a = "5";
      console.log(obj.a); //5
    </script>

结果为5
我们将Object.prototype.a = “5”;注释掉

<script>
      function fn() {
        // this.a = "2";
      }
      // fn.prototype.a = "4";
      let obj = new fn();
      // obj.a = "1";
      // obj.__proto__.a = "3";
      // Object.prototype.a = "5";
      console.log(obj.a); //3
    </script>

结果为undefied,最顶层为null。

查找规则:

先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找

  1. 原型链

4.1 是什么?:就是把原型串联起来
4.2 原型链的最顶端是null

总的来说原型链就是原型可以实现对象共享属性和共享方法,函数拥有原型prototype,对象拥有原型__proto__,原型中对象查找属性或者方法的顺序为先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找,最后形成一条链,也就是我们说的原型链。

18、 JS继承有哪些方式

方式一:
ES6中的class

	<script>
      class Parent {
        constructor() {
          this.age = 18;
        }
      }
      class Child extends Parent {
        constructor() {
          super();
          this.name = "张三";
        }
      }
      let obj = new Child();
      console.log(obj, obj.name, obj.age);
    </script>

方式二:
原型链继承

	<script>
      function Parent() {
        this.age = 18
      }
      function Child() {
        this.name="张三"
      }
      Child.prototype = new Parent();
      let obj = new Child();
      console.log(obj,obj.name,obj.age);
    </script>

缺点:原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。

方式三:
借用构造函数继承

	<script>
      function Parent() {
        this.age = 18
      }
      function Child() {
        this.name="张三"
        Parent.call(this)
      }
      let obj = new Child();
      console.log(obj,obj.name,obj.age);
    </script>

缺点:
1、只能继承父类的实例属性和方法,不能继承原型属性/方法
2、无法实现复用,每个子类都有父类实例函数的副本,影响性能

方式四:组合式继承

	<script>
      function Parent() {
        this.age = 18
      }
      function Child() {
        this.name="张三"
        Parent.call(this)
      }
      Child.prototype = new Parent();
      Child.prototype.constructor = Child;  //重新指向自己
      let obj = new Child();
      console.log(obj,obj.name,obj.age);
    </script>

缺点:在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

19、说一下call、apply、bind区别

共同点:功能一致

可以改变this指向
语法:函数.call()、函数.apply()、函数.bind()
函数第一个参数为指向哪里,第二个参数往后都是传入的参数。

区别:

1、 call和apply可以立即执行,bind不会立即执行,因为它返回的是一个函数,需要加入()进行执行
2、参数不同:apply的第二个参数是数组,而call和bind需要一个一个写。

场景:
1、使用apply

var arr1 = [1,2,4,5,7,3,321];
console.log( Math.max.apply(null,arr1)) 

因为Math.max数学对象的参数它不能直接是一个数组 ,给它加上apply之后,就可以直接传入一个数组。
还有一种办法可以直接传入数组,使用扩展运算符将数组进行展开。
2. 用bind的情况

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <div id="btn">1</div>
    <div id="h1s">2</div>
    <script>
      var btn = document.getElementById("btn");
      var h1s = document.getElementById("h1s");
      btn.onclick = function () {
        console.log(this.id);
      }.bind(h1s);
    </script>
  </body>
</html>

原本this应该指向btn,使用bind改变它的指向,最后打印出来的值为hls。

20、介绍一下sort

sort0 方法用于对数组的元素进行排序,并返回数组,默认排序顺序是根据字符串 Unicode码点。
语法: array.sort(sortby); 参数 sortby可选,规定排序顺序,必须是函数
注:如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序,要实现这一点,首先应把数组的元素都转换成字符申(如有必要),以便进行比较。

arr.sort0
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数
a 和 b,其返回值如下若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值 若 a等于 b,则返回 0.
若 a 大于 b,则返回一个大于 0 的值

场景一、

 	<script>
      let arr = [1, 213, 44, "32", "111"];
      console.log(arr.sort());
      console.log(
        arr.sort(function (a, b) {
          return a - b;
        })
      );
    </script>

在这里插入图片描述
场景二、
第一种写法:

<script>
      let obj = [
        {
          name: "张三",
          age: 20,
        },
        {
          name: "李四",
          age: 18,
        },
        {
          name: "天云",
          age: 22,
        },
        {
          name: "云米",
          age: 17,
        },
      ];
      console.log(
        obj.sort(function (a, b) {
          return a.age - b.age;
        })
      );
    </script>

在这里插入图片描述
第二种写法:

 <script>
      let obj = [
        {
          name: "张三",
          age: 20,
        },
        {
          name: "李四",
          age: 18,
        },
        {
          name: "天云",
          age: 22,
        },
        {
          name: "云米",
          age: 17,
        },
      ];
      function fn(age) {
        return function (a, b) {
          let value1 = a[age];
          let value2 = b[age];
          return value1 - value2;
        };
      }
      console.log(obj.sort(fn("age")));
    </script>

在这里插入图片描述

21、localStorage、sessionStorage、cookie的区别

公共点:在客户端存放数据
区别:

  1. 数据存放有效期 sessionStorage : 仅在当前浏览器窗口关闭之前有效。【关闭浏览器就没了】 localStorage : 始终有效,窗口或者浏览器关闭也一直保存,所以叫持久化存储。 cookie :
    只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效。
  2. localStorage、sessionStorage不可以设置过期时间 cookie 有过期时间,可以设置过期(把时间调整到之前的时间,就过期了)
  3. 存储大小的限制 cookie存储量不能超过4k localStorage、sessionStorage不能超过5M ****根据不同的浏览器存储的大小是不同的。

22、深拷贝和浅拷贝

链接https://blog.csdn.net/Zhang_wang_yun/article/details/130031214

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方青云、

你的鼓励将是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值