HLJ 发布于
2022-01-22 18:30:15

js面试题

上一篇文章:

81个JavaScript实例代码

什么是闭包?

  • 闭包是指存在自由变量的函数!

  • 在A作用域中使用的变量a,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,a就是一个自由变量。如下代码

  • 案例1

    var a = 10
    function fn(){
    var b = 20
    console.log(a + b)
    }
    
  • 案例2

    function a(x, y){
      console.log(x, y) //在这里,x和y都不是自由变量
      function b(){
          console.log(x, y) //但在这个内部函数b中,x和y相对于b都是自由变量,而函数a的作用域则是环境。
      }
      //无论b最终是否会作为返回值被函数a返回,b本身都已经形成了闭包。
    }
    
  • 案例3

  • 函数可以记住并访问所在的词法作用域

    function a () {
      let value = 2;
      function b () {
          return value += 2; // 对value局部变量进行累加
      }
      return b;
    }
    const fn = a();
    console.log(fn());    // 4
    console.log(fn());    // 6
    console.log(fn());    // 8
    
  • 优点:1、避免全局变量污染。2、缓存变量

  • 缺点:容易造成内存泄漏

什么是promise

  • promise是一个构造函数,是用来处理异步操作的解决方式
  • promise构造函数是同步执行的,then方法是异步执行的
    new Promise(resolve=>{
    console.log(1);
    resolve(3);
    }).then(res => {
    console.log(res);
    });
    console.log(2);
    // 输出结果 1, 2, 3
    
  • Promise.all()
  • Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
    const p = Promise.all([p1, p2, p3]);
    
  • (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
  • finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法
  • 不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

js数据类型有几种?有什么不同

  • js的数据类型可分为基本数据类型引用数据类型
  • 基本数据类型(栈存储):Undefined,Null,Boolean,Number、String
  • 引用数据类型(堆存储):对象、数组、函数
  • 堆是先进先出,栈是先进后出
  • js基本数据类型和引用类型的类型值
    let a = 10
    let a2 = '10'
    let a3 = null
    let a4 = undefined
    let a5 = true
    let a6 = {id:1,value:'20'}
    let a7 = [1,2,3,4,5,6]
    let a8 = function(){
    return '6'
    }
    console.log(typeof(a))  //number
    console.log(typeof(a2))  //string
    console.log(typeof(a3))  //object
    console.log(typeof(a4))  //undefined
    console.log(typeof(a5))  //boolean
    console.log(typeof(a6))  //object
    console.log(typeof(a7))  //object
    console.log(typeof(a8))  //function
    

null,undefined 的区别

  • null 表示一个对象是“没有值”的值,也就是值为“空”
  • undefined 表示一个变量声明了没有初始化(赋值)
  • undefined不是一个有效的JSON,而null是
  • undefined的类型(typeof)是undefined
  • null的类型(typeof)是object

同步请求和异步请求的区别

  • 按顺序执行的函数为同步请求、同时进行请求的函数为异步请求
  • 区别:同步按顺序执行函数、解析的速度相对异步较慢;异步则相反,先读取完成的函数先执行、解析速度快

深拷贝和浅拷贝

  • 1、浅拷贝:只复制指向某个对象的指针,而不复制这个对象本身,新旧对象共享一块内存。
  • 2、深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象旧对象不会变。
  1. 深拷贝方式:

    • 1、递归(对可遍历的属性进行递归)

      // 一般来说,在js中我们用JSON.parse(JSON.stringify(obj))来完成深拷贝,但是该方法不能解决属性为函数,undefined,循环引用的的情况,因此我们有时需要自己实现一个深拷贝。
      function clone(o) {
       var temp = {}
       for (var key in o) {
        if (typeof o[key] === 'object' && o[key] !== null) {
            temp[key] = clone(o[key])
        } else {
            temp[key] = o[key]
        }
       }
       return temp
      }
      
    • 2、使用JSON对象的parse和stringify

  2. 浅拷贝方式:

    • 1、Object.assign
    • 2、(...) 扩展运算符

什么是箭头函数

  • 箭头函数是在es6或更高版本中编写函数的表达式

解释JS中的高阶函数

  • 高阶函数是JS函数式编程的最佳特性。它是以函数为参数并返回函数作为结果的函数。一些内置的高阶函数是map、filter、reduce 等等。

call、bind、apply的区别

  1. 1、都是用来改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向
  2. 2、区别:参数书写方式不同
    call(thisObj, arg, arg2, arg3);
    apply(thisObj, [args]);
    bind(thisObj,arg, arg2, arg3)(); //bind 返回的是一个新的函数,必须调用它才会被执行。
    
  • Call
    let p1 = {
      firstName: 'John',
      lastName: 'Smith'
    };
    let p2 = {
      firstName: 'Ann',
      lastName: 'Brown'
    };
    function sayWelcome(greeting) {
      console.log(`${greeting} ${this.firstName} ${this.lastName}`);
    }
    sayWelcome.call(p1, 'Welcome'); // Welcome John Smith
    sayWelcome.call(p2, 'Welcome'); // Welcome Ann Brown
    
  • Apply
    let p1 = {
      firstName: 'John',
      lastName: 'Smith'
    };
    let p2 = {
      firstName: 'Ann',
      lastName: 'Brown'
    };
    function sayWelcome(greeting) {
      console.log(`${greeting} ${this.firstName} ${this.lastName}`);
    }
    sayWelcome.apply(p1, ['Welcome']); // Welcome John Smith
    sayWelcome.apply(p2, ['Welcome']); // Welcome Ann Brown
    
  • Bind
    let p1 = {
      firstName: 'John',
      lastName: 'Smith'
    };
    let p2 = {
      firstName: 'Ann',
      lastName: 'Brown'
    };
    function sayWelcome(greeting,greeting2) {
      console.log(`${greeting} ${this.firstName} ${this.lastName}`)
    }
    sayWelcome.bind(p1, 'Welcome')(); // Welcome John Smith
    sayWelcome.bind(p2, 'Welcome')(); // Welcome Ann Brown
    

普通函数和箭头函数有啥区别

  • 书写方式不同
  • 箭头函数this所指的对象是上层函数作用域里this所指的对象
  • 箭头函数不能用作变量提升
  • 箭头函数没有构造函数不能实例化

== 和 === 的区别

  • 1、相等运算符(==)是判断等号两边的值是否相等
  • 2、完全相等运算符(===)是判断等号两边的值和类型是否相等

什么是原型对象和原型链

  • 1、原型对象(Person.prototype)是 构造函数(Person)的一个实例。

  • 2、JavaScript是⾯向对象的,每个实例对象都有⼀个__proto__属性,该属性指向它的原型对象,这个实例对象的构造函数有⼀个原型属性 prototype,与实例的proto属性指向同⼀个对象。当⼀个对象在查找⼀个属性的时候,⾃⾝没有就会根据__proto__向它的原型进⾏查找,如果 都没有,则向它的原型的原型继续查找,直到查到Object.prototype.proto_为null,这样也就形成了原型链。

  • 原型链的顶层就是Object.prototype,而这个对象的是没有原型对象的。

为什么要使用原型链呢?

  • 1.为了实现继承,简化代码,实现代码重用!
  • 2.只要是这个链条上的内容,都可以被访问和使用到!

typeof的一个小知识

  • 在判断一个变量是否存在时应该用typeof来判断,如if(typeof a!="undefined"){},而不要去使用if(a)因为如果a不存在(未声明)则会出错,

  • if(typeof a !== 'undefined'){
      console.log(10)
    }
    //运行结果不报错
    if(a){
      console.log(10)
    }
    //运行结果报 a is not defined 错
    

当判断一个变量是否是数组类型时使用 instanceof 关键字而不是 typeof

  • 当变量为null ,数组,对象时使用typeof返回结果是object
  • const arr = [1, 2, 3, 4, 5, 6]
    console.log(arr instanceof Array)  //true
    console.log(typeof arr)   // object
    

如何判断一个对象是否属于某个类?

  • instanceof

什么是节流和防抖?

  • 函数节流:是确保函数特定的时间内至多执行一次。
  • 函数防抖:是函数在特定的时间内不被再调用后执行。

preventDefault()阻止元素默认事件和 stopPropagation()阻止元素事件冒泡

  • preventDefault()函数: a标签,radio、checkbox复选框等表单元素,div没有默认事件

  • <input type="checkbox" id="checkbox"/>
    <a href="http://www.good1230.com/" target="_black">HLJ的博客</a>
    <script>
    let dom = document.querySelector("#checkbox")
      dom.addEventListener("click", function(event) {
          console.log("preventDefault() 阻止了复选框选择事件!")
          event.preventDefault();
      }, false);
    let dom2 = document.querySelector("a");
      dom2.addEventListener("click",function(e){
        console.log("preventDefault() 阻止了a标签跳转事件!")
        e.preventDefault()
      },false);
    </script>
    

    stopPropagation()函数

    <div id="c" onclick="alert(1)">
      <div id="c2" onclick="alert(2)">
        <input type="button" id="c3" value="点击" onclick="alert(3)">
      </div>
    </div>
    

    点击button按钮时,浏览器会先后弹出3, 2, 1,这时我们可以用stopPropagation()来阻止触发两个div上的事件

    let dom = document.getElementById('c3')
      dom.addEventListener('click',function(e){
        e.stopPropagation()
      },false);
    

什么是同源策略

  • 同源策略指的是:协议,域名,端口三者一致
  • 同源策略的目的:是为了保证用户信息的安全,防止恶意的网站窃取数据
  • 如果非同源,浏览器会进行什么限制?
    • Cookie,LocalStorage和IndexDB 无法读取
    • Dom不能获得
    • ajax请求不能发送

什么是跨域?跨域解决方法

  • 跨域是指当请求非同源的URL链接,简单说就是协议,域名,端口中存在有和当前Url不同的就是跨域
  • 跨域解决方法
    • cors
    • nginx反向代理
    • jsonp
      • 由于script的src不受同源策略限制,我们可以通过src访问服务器上的脚本进行数据回调
      • (动态创建script标签,回调函数)

什么是JSON

  • JSON 是一种轻量级的数据交换格式

JavaScript 动态操作标签元素

  1. 创建新节点:
    • createElement() //创建元素标签
      let element = document.createElement('div')
      document.body.appendChild(element)
      
    • createTextNode() // 创建一个文本节点
      let element = document.createElement('div')
      let elementNode = document.createTextNode('text')
      element.appendChild(elementNode)
      document.body.appendChild(element)
      
  1. 添加、删除、更新、插入:
    • appendChild() // 添加标签
      let element = document.createElement('div')
      document.body.appendChild(element)
      
    • removeChild() // 删除标签
      let element = document.getElementById("div")
      document.body.removeChild(element)
      
    • insertBefore() // 插入标签
<ul id="myList">
  <li>a</li>
  <li>c</li>
</ul>
<button onclick="myFunction()">试一下</button>

<script>
function myFunction()
{
  let newElement=document.createElement("li")
  let newElementNode=document.createTextNode("b")
      newElement.appendChild(newElementNode)

  let list=document.getElementById("myList")
      list.insertBefore(newElement,list.childNodes[0]);
}
</script>
    • insertBefore() // 更新标签

LocalStorage、SessionStorage、Cookie 之间的区别

  • localStorage

    localStorage.setItem(key, value)
    localStorage.getItem(key)
    localStorage.removeItem(key)
    localStorage.clear()
    
  • sessionStorage

    sessionStorage.setItem(key, value)
    sessionStorage.getItem(key)
    sessionStorage.removeItem(key)
    sessionStorage.clear()
    
  • 特性 LocalStorage SessionStorage Cookie
    存放数据大小 LocalStorage 的存储容量为5MB/10MB SessionStorage 的存储容量为5MB Cookies的存储容量为4KB
    数据生命周期 由于它不是基于会话的,因此必须通过 javascript 或手动删除 它是基于会话的,可以按窗口或选项卡工作。这意味着数据仅在会话期间存储,即直到浏览器(或选项卡)关闭 Cookies 根据选项卡和窗口的设置和工作过期
    客户端只能读取本地存储 客户端只能读取本地存储 客户端和服务器都可以读取和写入 cookie
    没有向服务器传输数据 没有向服务器传输数据 存在到服务器的数据传输
    浏览器兼容性 支持它的旧浏览器较少 支持它的旧浏览器较少 所有浏览器都支持它,包括旧版浏览器
    共同点 都是保存在浏览器端 都是保存在浏览器端 都是保存在浏览器端

get和post

特性 get post
参数位置 参数拼接到URL后面 参数在请求体中
参数大小 受限于浏览器url大小,一般不超过32K 1G
安全性 参数暴露在URL中,安全性低 相对get安全性更高些
适用场景 从服务器端获取数据 从服务器端获取数据
缓存 在浏览器中可缓存 不可以缓存

SDF

GET 和 POST 其实都是 HTTP 的请求方法。除了这 2 个请求方法之外,HTTP 还有 HEAD、PUT、DELETE、TRACE、CONNECT、OPTIONS 这 6 个请求方法。所以HTTP 的请求方法共计有 8 种。

[“1”, “2”, “3”].map(parseInt) 答案是多少?

  • let array = ['1','2',3].map(parseInt)
    console.log(array) 
    // 输出结果 [1, NaN, NaN]
    

parseInt()

  • parseInt() 函数可解析一个字符串,并返回一个整数。

  • parseInt(string, radix)

  • 参数 描述
    string 必需。要被解析的字符串。
    radix 1、可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。2、如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。3、如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
  • 当参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。

  • 举例,如果 string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。

    parseInt("10");            //返回 10
    parseInt("19",10);        //返回 19 (10+9)
    parseInt("11",2);        //返回 3 (2+1)
    parseInt("17",8);        //返回 15 (8+7)
    parseInt("1f",16);        //返回 31 (16+15)
    parseInt("010");        //未定:返回 10 或 8
    

    map 和 forEach 的区别

  • map返回一个新的数组,forEach返回undefined

  • 使用场景:修改或返回新数组使用map,相反只是做遍历循环时用foreach 或 for

    const array = [1, 2, 3, 4, 5]
    const arr = array.forEach(x => x * x)
    const arr2 = array.map(x => x * x)
    console.log(array)  // [1, 2, 3, 4, 5]
    console.log(arr)  // undefined
    console.log(arr2)  //[1, 4, 9, 16, 25]
    

    CSS 单位: %, em, rem, px, vh, vw

  • % – %单位用于设置相对于当前字体大小的字体大小。

  • em -相对于当前对象内文本的字体尺寸(参考物是父元素的font-size)

  • 注意:这里的 2em 表示当前字体大小的 2 倍。

  • rem – rem是相对于HTML根元素的字体大小(font-size)来计算的长度单位

  • px -相对于显示器屏幕分辨率

  • vh - 相对于视口高度的 1%。

  • vw – 相对于视口宽度的 1%

js中的宏观任务和微观任务

js 是单线程执行的,js中的任务按顺序一个一个的执行,但是一个任务耗时太长; 那么后面的任务就需要等待,为了解决这种情况,将任务分为了同步任务和异步任务; 而异步任务又可以分为微任务和宏任务。

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

返回结果的打印顺序是:

script start
script end
promise1
promise2
setTimeout
  • 1.宏观任务(macro task):由宿主(node、浏览器)发起的任务,如setTimeOut、setInterval、setImmediate、I/O
  • 2.微观任务(micro task):由js引擎发起的任务,如process.nextTick、promise、mutationObserver
  • 微任务必须在宏任务之前执⾏。因为 在宏观任务中 JavaScript 可能产⽣ Promise 这样的异步
  • JavaScript 必须保证这些异步代码必须在⼀个宏观任务中完成,因此,每个宏观任务中⼜包含了⼀个微观任务队列:
    var r = new Promise(function(resolve, reject){
          console.log("a");
          resolve()
      });
      setTimeout(()=>console.log("d"), 0)
      r.then(() => console.log("c"));
      console.log("b")
    
  • 打印结果为: a b c d
  • 如何分析异步执⾏的顺序:
  • ⾸先我们分析有多少个宏任务;
  • 在每个宏任务中,分析有多少个微任务;
  • 根据调⽤次序,确定宏任务中的微任务执⾏次序;
  • 根据宏任务的触发规则和调⽤次序,
  • 确定宏任务的执⾏次序;确定整个顺序。 https://wenku.baidu.com/view/231ad6c49a8fcc22bcd126fff705cc1755275f37.html

再回头来看看开头的一段代码,会不会豁然开朗了呢。JS 引擎首先会把Promise对象 和 console.log(“b”) 两个微观任务存入执行栈,把 setTimeout(宏观任务)存入 “任务队列”

所以在输出 a 和 b 以后并不会按照预期那样立即从 “任务队列” 中读取 setTimeout,因为 then方法是微观任务Promise对象的回调函数,先于 setTimeout 执行

Promise.resolve().then(()=>{
    console.log('1')
    setTimeout(()=>{
      console.log('2')
    },0)
  })
  setTimeout(()=>{
    console.log('3')
    Promise.resolve().then(()=>{
      console.log('4')
    })
  },0)
  • 打印1 3 4 2

  • 在交流群中看到有的小伙伴还是不太清楚正确的执行顺序,基于前面的介绍,大致的分析过程及草图如下:

  • 1(红色):JS 引擎会把微观任务Promise存入执行栈,把宏观任务setTimeout存入 “任务队列”

  • 2(绿色):主线程率先运行执行栈中的代码,依次输入1,然后把绿框的setTimeout存入 “任务队列”

  • 3(蓝色):执行栈清空以后,会率先读取 “任务队列” 中最早存入的setTimeout(红框的那个),并把这个定时器存入栈中,开始执行。这个定时器中的代码都是微观任务,所以可以一次性执行,依次输出3 和 4

  • 4(紫色):重复第3步的操作,读取 “任务队列” 中最后存入的setTimeout(绿框的那个),输出2 good博客 图片

  • 所以最终的输出结果就是 1 3 4 2

    Promise.resolve().then(()=>{
      console.log('1')
      setTimeout(()=>{
        console.log('2')
      },0)
    })
    setTimeout(()=>{
      console.log('3')
      Promise.resolve().then(()=>{
        console.log('4')
      })
    }, 3000)
    
  • 打印1 2 3 4

    setTimeout(function(){console.log(4)},0); 
    new Promise(function(resolve){ 
      console.log(1) 
      for( var i=0 ; i<10000 ; i++ ){
         i==9999 && resolve() 
      } 
      console.log(2) 
    }).then(function(){ 
      console.log(5) 
    }); 
    console.log(3);
    
  • 另外一个会让人感到迷惑的地方就是 resolve回调函数内部的那几行代码,输出1以后接着跑1000次循环才调用resolve方法,其实resolve()的意思是把 Promise对象实例的状态从pending变成 fulfilled(即成功)

  • 成功的回调就是对应的then方法。所以resolve() 后面的 console.log(2) 会先执行,因为 resolve() 回调函数是在本轮事件循环的末尾执行 (关于这部分内容,可以参考 Promise对象 一文)

  • 打印1 2 3 5 4

    setTimeout(function(){console.log(4)},0); 
    new Promise(function(resolve){ 
      console.log(1) 
      for( var i=0 ; i<10000 ; i++ ){
        //  i==9999 && resolve() 
      } 
      console.log(2) 
    }).then(function(){ 
      console.log(5) 
    }); 
    console.log(3);
    
  • 打印1 2 3 4

https://dandelioncloud.cn/article/details/1470389873935970306/

当前文章内容为原创转载请注明出处:http://www.good1230.com/detail/2022-01-22/576.html
最后生成于 2022-09-12 08:04:27
上一篇文章:

81个JavaScript实例代码