JS 高频面试题

这篇具有很好参考价值的文章主要介绍了JS 高频面试题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

JS 的数据类型有哪些,有什么区别

基本数据类型(Undefined、Null、Boolean、Number、String、Symbol)
引用数据类型(对象、数组和函数)
区别:
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址;
在操作系统中,内存又被分为栈区和堆区,栈区内存由编译器自动分配释放,堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

Symbol 是什么数据类型

Symbol 是 ES6 引入的一种新的基本数据类型,表示独一无二的值。因为是原始数据类型,所有不能new Symbol。Symbol 类型的 key 不能通过 Object.keys() 或者 for…in 来枚举,它未被包含在对象自身的属性名集合(property names)之中所以,利用该特性,可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。

encodeURI和encodeURIComponent的区别(了解)

应用场景不同:encodeURI是用来处理整个URI的,它应该接受URI的protocol、host、port等部分,只对path和query进行编码。适合给URL本身(locaion.origin)编码。而encodeURIComponent是用来编码准备用作query部分的字符串的。适合给参数编码。
编码范围不同:encodeURI不会对ASCII字母、数字、~!@#$&()=:/,;?+*'进行编码,而encodeURIComponent会对所有非标准字符进行编码。

JS 判断数据类型的方法

方法一、typeof 其中数组、对象、null都会被判断为object,其他判断都正确。

console.log(typeof 'str');           // string
console.log(typeof []);              // object    
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null); 

方法二、instanceof只能正确判断引用数据类型,而不能判断基本数据类型。

console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object); 

方法三、Object.prototype.toString.call()

var a = Object.prototype.toString;
console.log(a.call(2));

判断数组类型的方法有哪些

方法一、
obj.__proto__ === Array.prototype;
方法二、
Object.prototype.toString.call([]);
方法三、
Array.isArrray(obj);
方法四、
obj instanceof Array

typeof和instanceof的区别?

typeof 判断数据类型的原理是通过查看值在内存中的底层表示来确定其类型,返回值是数据类型
instanceof此操作符用于检测一个对象是否是某个构造函数或类构造的实例,返回值是布尔值;
typeof 主要用于检测基本数据类型,对于应用数据类型检测不准确;
而instanceof 用来判断引用数据类型,不能正确判断基本数据类型;

为什么使用instanceof不能正确判断基本数据类型

原理是查找左边的_proto_获取到原型对象,判断原型链中是否有和右侧相等;基本数据类型没有构造函数也没有原型链;

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);
  }
}

const 、let、var的区别

一、作用域:let和const具有块级作用域,var不存在块级作用域
二、变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错
三、重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量
四、初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
五、let创建的变量是可以更改指针指向(可以重新赋值),但const声明的变量是不允许改变指针的指向(对象和数组除外)

使用const定义的数据不能修改吗

其实不能改的不是值,而是变量指向的内存地址,对于基本数据类型来说,其值就保存在变量指向的那个内存地址,因此等同于常量。
而对于引用数据类型来说,保存的只是指针,至于指针指向的数据结构是不能保证的,所有引用数据类型是可以修改的;

箭头函数和普通函数的区别

1、普通函数可以使用函数声明和函数表达式定义,箭头 箭头函数语法更简洁
2、箭头函数没有自己的this,它只会在自己作用域的上一层继承this(你不能在箭头函数中使用new关键字来创建一个新的对象实例,因为箭头函数没有自己的this值)
3、箭头函数继承来的this指向永远不会改变,call()、apply()、bind()等方法不能改变箭头函数中this的指向
4、箭头函数没有自己的arguments(普通函数中,arguments 是一个类数组对象,它包含了传递给函数的参数)监听函数中可以只能通过参数名称来访问
5、箭头函数不能作为构造函数使用,因为没有this

JS 模板字面量 是什么

JS 模板字面量是 ES6 引入的一种新的字符串表示方式,它使用反引号(`)包裹字符序列,允许在字符串中插入变量、表达式和原始字符串,并且支持多行文本的处理

如何实现图片懒加载

图片懒加载就是延迟加载非视口的图片,从而提高页面加载速度,提升用户体验。
window.innerHeight 获取视口的高度,图片顶部到窗口的距离可以使用getBoundingClientRect().top 然后监听页面的scroll方法,获取到img标签,如果图片到顶部的距离小于窗口的高度就应该显示图片。

<body>
        <img data-src="/gift.png">
      <script>
        const images = document.querySelectorAll('img');
        window.addEventListener('scroll',()=>{
            images.forEach((item)=>{
                const imageTop = item.getBoundingClientRect().top; // 获取图片到顶部的距离
                if(imageTop < window.innerHeight) {
                const imgSrc =  item.getAttribute('data-src');
                item.setAttribute('src',imgSrc);
                }
            })
        })
      </script>
    </body>

使用这个方法的问题在于只要滚动就会频繁触发,并且已经展示图片的时候还是会执行;
我们可以使用浏览器提高的构造函数IntersectionObserver来优化

 <body>
        <img data-src="/gift.png">
      <script>
        const images = document.querySelectorAll('img'); 
        // 观察的元素每次进入和离开都会触发
        const callBack = entries => {
          entries.forEach(entry => {
            // 有交叉
            if(entry.isIntersecting) {
               const iamge = entry.target;
               const imgSrc =  image.getAttribute('data-src');
               image.setAttribute('src',imgSrc);
               observer.unobserve();// 赋值后取消观察
            }
          })
        }
        const observer = new IntersectionObserver(callBack); 
        // 浏览器提供的构造函数,交叉观察,部分浏览器版本不兼容
        images.forEach((item)=>{
            observer.observe(item);
        })
      </script>
    </body>

说下JS 的事件循环机制

js是单线程的,只有一个调用栈,并且陷入后出;在执行调用栈的时候会先执行同步任务,发现异步任务加入异步任务队列,队列分为宏任务和微任务,队列按照先入先出的规则。
常见的宏任务:script 标签、setTimeout、事件的回调函数
常见的微任务:Promise.then()catch() finally() Object.observer()process.nextTick
执行顺序:
先执行同步任务,清空调用栈,执行微任务队列的任务,微任务队列执行完后才执行宏任务。每次宏任务结束后事件循环会先执行微任务,直到微任务队列里的任务被清空才会执行下一轮宏任务,这样就可以避免因为宏任务的繁重导致任务的阻塞。

JS 中创建对象的方式

1、new Object 实例,为它逐个添加属性和方法,缺点是不便于代码的阅读和管理

let cat = new Object();
cat.name = 'tuotuo';
cat.age= 2;
cat.seepk = function () {
 console.log('我要吃猫条');
}

2、字面量的方式

let obj = {
    key1: 'value1',
    key2: 'value2'
};

3、工厂模式,创建对象模版,缺点是创造的新对象和工厂函数之间缺乏关联

function createCat (name,age) {
 const obj = new Object();
 cat.name = name;
 cat.age= age;
 cat.seepk = function () {
   console.log('我要吃猫条');
 }
 return obj;
}
let cat1 = createCat('xiaoyiyi',0.5);

4、构造函数模式,解决了工厂模式的问题,obj instanceof MyObject 是true

function MyObject(key1, key2) {
    this.key1 = key1;
    this.key2 = key2;
}
let obj = new MyObject('value1', 'value2');

5、Object.create()使用Object.create()方法来创建一个新对象,该方法使用现有的对象来提供新创建的对象的__proto__

let proto = {
    text:'测试1',
    foo: function() { console.log('foo'); }
};
let obj = Object.create(proto);
obj.text = '覆盖文案';
console.log(protp.text) // '覆盖文案'

6、 class 的方式,融合了构造函数和工厂模式

class Cat {
  constructor (name,age) {
    this.name = name;
    this.age =  age;
  }
};
let cat1 = new Cat('tuotuo',2)

说下防抖和节流的区别,并手写

触发频率不同:防抖是将多次执行变为最后一次执行,节流是在一段时间内只执行第一次,时间到了才会执行下一次;
作用不同:防抖用于在一定时间间隔内只触发一次事件,节流用于限制事件在一定时间内的触发次数。
适用场景不同:防抖适用于在用户频繁触发事件时,只执行最后一次事件,如输入框实时搜索;节流适用于在一定时间内只触发一次事件,如滚动加载。
防抖函数实现:

const btn = document.getElementById('btn');
function handleClick () {
  // 因为回调的关系,在防抖函数中调用该this为window
  console.log('点击了按钮')
};  

function  debounce (fn,delay) {
   // 利用闭包将变量放返回函数外层,因为作用域链所有下面返回的函数都能访问到这个变量
   let timer ;
   // 使用高阶函数解决未点击就会被触发的问题
   return function() {
   console.log(this);// 在回调之前该this为btn元素,保存下this执行
   const context = this;
   const args = arguments;// 拿到传递过来的所有参数
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    },delay);
   }
}
btn.addEventListener('click',throttle(handleClick,1000));

节流函数的实现

const btn = document.getElementById('btn');
function handleClick () {
  // 因为回调的关系,在防抖函数中调用该this为window
  console.log('点击了按钮')
};  

function throttle (fn,delay) {
   // 利用闭包将变量放返回函数外层,因为作用域链所有下面返回的函数都能访问到这个变量
   let timer ;
   // 使用高阶函数解决未点击就会被触发的问题
   return function() {
   console.log(this);// 在回调之前该this为btn元素,保存下this执行
   const context = this;
   const args = arguments;// 拿到传递过来的所有参数
   if(timer) return; // 如果定时器不为空说明在时间间隔内不执行任务
    timer = setTimeout(function() {
      fn.apply(context, args);
      timer = null; // 每次执行完了就清空定时器
    },delay);
   }
}
btn.addEventListener('click',throttle(handleClick,1000));

Ajax 有了解吗?

AJAX不是指一种新的编程语言,而是有机地利用了一系列相关的技术,核心是使用 XMLHttpRequest 进行异步数据查询、检索,通过这个对象 可在不重载页面的情况与 Web 服务器交换数据,再使用 DOM(Document Object Model)进行动态显示及交互。

XML被设计用来传输和存储数据,早期的时候使用的是XML,现在使用JSON 格式;

优点:
1.无刷新更新数据
Ajax最大的优点就是能在不刷新整个页面的情况下维持与服务器通信
2.基于规范被广泛支持
不需要下载浏览器插件或者小程序,但需要客户允许JavaScript在浏览器上执行。
缺点:
1.Ajax干掉了Back与History功能,即对浏览器机制的破坏
在动态更新页面的情况下,用户无法回到前一页的页面状态,因为浏览器仅能记忆历史纪录中的静态页面
2.只能同源请求
AJAX只能向同源的地址发送请求,如果跨源(非同源地址)发送请求,会受同源策略的影响,无法得到响应结果(同源是指两个页面拥有相同的协议、域名和端口,如果有一个不相同,就是不同源)
3.网页内容动态获取,对SEO不友好

http请求和响应报文,都包含四个部分:行 头 空行 体

ajax浏览器走缓存未发送请求?
可以在请求的url上拼一个时间戳,这样每次url是不一样的,都会发起请求

ajax 用户重复点击重复发请求如何优化?
可以添加一个loading标识。如果上一个请求未完成,则abort取消上一个请求,创建的新的xhr对象并发送请求。

说说你对跨域的理解

在解释跨域之前,先解释下同源策略,同源策略是了浏览器的安全策略,他会检查请求的源和响应脚本的源是否相同,如果不相同,那么服务端的响应会被浏览器拦截,并且在控制台抛出错误,而实际请求是发送成功的,响应也可能是成功的;如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
在实际开发中,我们是需要请求不同源的数据的,所以需要绕开浏览器的这个限制。
解决方案:
一、JSONP(JSON with Padding):核心就是利用<script> 标签的src属性允许加载不同源资源,默认情况下script标签的type为text/javascript 请求来的资源会被执行。通常我们会在客户端提前定义一个回调函数,接收服务端传给我们的JSON 格式的数据,然后通过指定srcipt标签的src属性,带上参数和回调函数,服务端接收到我们传的参数和回调函数,然后将会数据外包到回调函数中,同时使用JSON格式进行传输,会把这个函数用JSON进行包裹,这也就是JSON with Padding的由来。
但是,JSONP 只支持 GET 请求,并且存在安全风险。

 <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
 
    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);
 
    // 回调执行函数
    function handleCallback(res) {
        alert(JSON.stringify(res));
    }
 </script>

二、CORS(Cross-Origin Resource Sharing):CORS 是一种标准化的跨域解决方案,它通过服务器端设置响应头来实现跨域请求。客户端在请求的时候会在请求头中带上origin,服务端需要设置Access-Control-Allow-Origin 响应头来指定哪些源可以访问该资源。如果没有设置响应头,并且是不同源,响应会被浏览器拦截;CORS 的优点是支持所有类型的请求,并且更加安全。
三、代理服务器:通过设置代理服务器来转发请求,可以避免跨域问题。所有请求都发送到代理服务器,然后由代理服务器向目标服务器发起请求,并将结果返回给客户端。这种方法需要在服务器端配置代理服务器。
四、WebSocket: 是一种全双工通信协议,可以在不同源的网页之间进行通信。通过 WebSocket,客户端和服务器之间可以建立持久连接,并交换数据。这可以避免跨域问题,因为 WebSocket 连接是在客户端和服务器之间直接建立的。

使用WebSocket的方法:
打开连接:通过使用WebSocket API中的new WebSocket()构造函数来创建一个新的WebSocket对象,并传递要连接的URL。然后,可以使用open()方法来打开与远程服务器的连接。
发送消息:使用WebSocket对象的send()方法向服务器发送消息。
接收消息:通过WebSocket对象的onmessage事件来接收服务器发送的消息。
关闭连接:使用WebSocket对象的close()方法来关闭与远程服务器的连接。

常见和推荐的还是第二种、第三种

formData 、Blob、FileReader 是什么(了解)

FormData是JavaScript中用于处理表单数据的接口,提供了一种表示表单数据的键值对的方法,可以用于在客户端和服务器之间发送数据。表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。

Blob(Binary Large Object)表示二进制类型的大对象,是一个可以存储二进制文件的容器。

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

说说你对 async await 的理解

async/await解决了Promise的嵌套问题,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。使异步代码看起来更像同步代码,易于理解和维护。async在函数前面加个async就变成了异步的,函数返回的是一个Promise对象,因此可以使用then方法进行后续处理。await用来等待异步任务的完成,必须和async同时使用;使用aiwat等待可以保证多个异步任务的执行顺序,让代码便于维护。

async function fn(){
 // 捕获错误
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

说说对class的理解

class 是面向对象编程中的一个基础构造,用于定义对象的模板,它定义了一组属性(数据成员)和方法(成员函数),这些属性和方法可以被类的实例(对象)所共享。通常类的名称需要大写,通过new 调用创建一个实例,new的时候会调用类的constructor方法,在构造函数中我们可以给类设置一些属性。类有封装继承多态三个特点,封装即类内部定义了方法和属性隐藏内部的实现细节,只暴露必要的接口。继承是只可以通过extend来继承另一个类继承属性和方法,同时在子类的构造函数中直接调用super即可继承父类的属性(先写super再写this,子类需要初始化自己的this);多态性是指在父类和其子类之间或者在不同的对象之间,对同一消息可以有不同的响应。
此外,还可以通过static定义静态方法或属性,属于类本身,而不是类的实例。
Setter方法是用于设置类的实例属性的值的方法。
Getter方法是用于读取类的实例属性的值的方法。

cookie、localStorage、sessionStorage的区别

大小:cookie 4kb,localStorage10mb、sessionStorage5mb
有效期:cookie手动设置、localStorage永久除非手动删除、sessionStorage窗口关闭失效
存储位置:往返浏览器和服务器、浏览器、浏览器
语法:复杂、简单、简单

如何异步加载JS

① async:立即下载脚本,并且当脚本下载完成后立即执行。这意味着如果页面中有多个async脚本,它们可能会交错执行。适合第三方的脚本
② defer:等到整个HTML文档解析完成后才开始下载脚本,它们会按照在HTML文档中出现的顺序执行。
③ 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
④ 将script内容放底部;

什么是类数组对象?

就是很像数组的对象,但是本身并不是数组,通常会有以下特点:
1、拥有一个length属性:这个属性表示对象中元素的数量。
2、元素可以通过索引访问:例如,使用obj[0]或obj[1]这样的语法来访问。
3、可能没有数组的方法:例如,push, pop, slice等。
举例:
函数arguments参数、DOM 方法的返回结果;
JS 高频面试题,前端面经&amp;面试题,javascript,前端,开发语言

如何将类数组对象转成数组?

1、Array.from(arrayLike);
2、Array.prototype.slice.call(arrayLike);
3、Array.prototype.splice.call(arrayLike, 0);

数组常用的方法有哪些,哪些是改变原数组的?

以下是会改变原数组的方法:
push(): 会向数组的末尾添加一个或多个元素,并返回新的长度。
unshift(): 会向数组的开头添加一个或多个元素,并返回新的长度。
pop(): 会删除并返回数组的最后一个元素。
shift(): 会删除并返回数组的第一个元素。
splice(): 会添加/删除数组中的元素。
reverse(): 会颠倒数组中元素的顺序,并返回数组。
sort(): 会对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点。可以提供自定义排序函数来改变排序行为。
以下是不会改变原数组的方法:
slice(): 会返回一个新的数组对象,包含从开始到结束(不包括结束)选择的数组的一部分浅复制。
concat(): 会合并两个或多个数组,并返回结果。
filter(): 会创建一个新数组,包含通过测试的所有元素。
forEach(): 会对数组的每个元素执行一次提供的函数。
map(): 会创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后的结果。
some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.

说说对原型链的理解

每个对象都有一个原型对象,包含一些共享的属性和方法,可以理解这个对象的祖宗。
我们可以在控制台中找到_proto_ 属性观察它的原型对象,或者通过Object.getPrototypeOf(obj) 方法获取到对象的原型对象。
我们通常会通过构造函数来创建一个对象,我们创建出来的对象的原型比如p._proto_ 指向的就是构造函数的prototype 原型对象;而原型本身也是一个对象,我们通用也可以通过._proto_ 获取到它的原型对象,一层层往上查找,形成的链路就是作用域链。作用域的终点是Object.prototype.__proto__=== null

function Person(name) {
    this.name = name
}
var p = new Person('hello');

console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person

当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎会自动去它的原型对象中查找,如果原型对象中也没有,则会继续向上查找,直到找到或者到达原型链的顶端。

手写promise

概念:
是es6 提出的异步编程的解决方案,支持链式调用,解决了回调地狱问题,从语法上说Promise.是一个构造函数,从功能上来说,Promise.对象用来封装一个异步操作并获取其成功或失败的结果。
实例的属性:
Promise.实例的promiseState属性的有三种状态pending、fulfilled、rejected一旦从进行状态变成为其他状态就永远不能更改状态了;实例promiseResult属性保存的是对象的结果;
Promise构造函数API:
Promise.resolve(返回一个成功或失败的Promise)、Promise.reject(返回一个失败的Promise)、Promise.all (参数是包含多个Promise的数组,返回值是一个新的Promise,只有数组中全部成功才返回成功结果是一个数组,否则返回失败;Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。 )
Promise.race(参数是包含多个Promise的数组,返回值是最先完成的promise的状态结果)
拓展问题:
一、改变promise的状态和指定回调是哪个先执行,比例resolve先执行还是then先执行?
当promise构造器中的函数是同步的改变状态,则是先改变状态再执行回调;当异步修改状态则是先指定回调;
二、指定的回调函数何时执行(比如传入then的函数)?
在状态改变完成后才会执行
三、then的返回结果是个新的promise,这个promise的结过是有什么决定的?
如果抛出异常,则直接失败;
如果返回的是非promise的任意值,新的promise状态成功;
如果返回的是promise,则根据里面romise执行结果决定

小破站跟着写

Promise.all()是并发的还是串行的?如果有一个抛出异常了会如何处理?

Promise.all() 是并发的,而不是串行的。这意味着它不会按照特定的顺序解决,而是当所有的 Promise 都解决时,才会解决。如果任何一个 Promise 失败,Promise.all() 将会立即被拒绝,并且其拒绝值是第一个失败的 Promise 的拒绝值

Promise为什么能链式调用?

主要是基于promise的两个特性,第一个就是promise.then 的返回值还是promise,第二个就是当我们在promise中执行reject 或者resolve的时候会改变promise 的状态从pending改变为resolve或者reject;
当你调用一个 Promise 的 then 方法时,新的 Promise 会被创建并返回。这个新的 Promise 在旧 Promise 的状态改变时会被解决或拒绝;
当创建一个 Promise 状态改变时,它会调用 then 方法中的回调函数,并返回一个新的 Promise。然后,当这个新的 Promise 解决或拒绝时,它也会调用下一个 then 方法中的回调函数,并返回一个新的 Promise。这样,就可以形成链式调用。

说说this的指向

对象的函数调用:绑定到该对象
构造函数中的this:指向创建的新实例
DOM事件函数:一般指向事件绑定的DOM元素
普通函数调用:在严格模式下绑定到 undefined,否则绑定到全局对象
ES6 中的箭头函数:箭头函数会继承外层函数并调用 this 绑定
apply、call 或 bind 可以改变this的指向,改变后的this就是我们作为参数传入的对象

闭包了解吗

在介绍闭包之前需要了解一下作用域的概念,我们通常说的作用域有全局作用域和函数作用域,也就是代码片段的执行环境。在全局的定义的变量和函数在全局作用域中,函数内部也有自己的作用域。每新建一层都会包含前面一层的变量对象,如果在自己的作用域中找不到变量就去父级作用域中查找,直到访问到window对象,这一层层的关系就是作用域链。
闭包就是在有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。原理就是作用域链的包含关系。
作用:使我们在函数外部能够访问到函数内部的变量,使已经运行结束的函数上下文中的变量对象继续留在内存中;

null和undefined的区别

首先这两个都是基本数据类型,但是定义不同,null表示空对象,转为数字的时候是0,通常会被用作初始化一个对象,同时它也是原型链的终点;
而undefined表示未定义,通常在声明了变量没有赋值的时候被赋默认值值undefined,转为数字的时候是NaN,一般我们不会手动设置undefined,并尽量避免undefined的出现;
使用等号运算符判断的时候,== 是true,=== 是false

==和===的区别是什么呢

在JavaScript 中,== 相等 和 === 全等 都用于比较两个值是否相等;
== 相等,在进行比较的时候,会先转换类型,再比较两个值是否相等;如1==‘1’ 因为字符串1会被转为数字1
=== 全等,先判断数据类型,如果不是同一类型直接为 false;若类型相同,再比较值是否相等

什么是高阶函数

满足以下任何一个条件就算高阶函数:
1、以一个函数作为参数
2、返回一个函数
第一种情况举例:数组的filter、map、forEach、setTimeout 都是高阶函数
第一种情况举例:bind 返回一个指定this的函数
实际应用:封装公共函数,代码抽离
为什么会出现高阶函数:在封装过程中运算的缺失,需要外界传入

函数柯里化

函数柯里化是高阶函数的一种应用,通常是指将原本接收多个参数统一处理的函数变为一个接收单一参数并返回一个函数接收剩下的参数,并返回结果;
使用函数柯里化可以让我们的函数职责单一、实现参数逻辑的复用、通过柯里化我们可以创建更具描述性的函数,提高代码的可读性;

JS 的语言特性

1、解释性、脚本语言,不需要预编译,每次都需要解析器将源代码解析后才能执行;
2、弱类型语言,对变量的数据类型没有严格要求,并且会自动转换类型;
3、跨平台性,不依赖于操作系统(Windows、Linux还是Mac),仅需要浏览器的支持(JavaScript是在浏览器中运行的,而浏览器是跨平台)
4、动态性,JavaScript是一种采用事件驱动的脚本语言,它不需要经过Web服务器就可以对用户的输入做出响应。

Node 和 JS的区别

node 是js 运行时的环境,js是脚本语言需要经过解析器解析才能执行,而node就是一个解析器。
js 一般视为前端语言,由ECMAScript 、BOM 、DOM组成;
而node为服务端语言,组成有ECMAScript、os(操作系统)、file(文件系统)、net(网络系统)database(数据库);Node.js没有浏览器Api,document、window等

懒加载和按需加载的区别是什么

其实差不太多。。
懒加载也就是延迟加载,比如路由的懒加载、组件懒加载、非视口图片区域的懒加载,只有用到了才去加载;
按需加载是相对于全局加载来说的,比如我们使用第三方组件库通常会是使用到按需加载

数组去重

方法有很多,不要背代码,记住几个常见方法的思路,自己能手写出来

  const arr = [3,4,5,1,1,2,2,3,3,4,5];
       // 利用set
       function uniqueArray1() {
          return Array.from(new Set(arr));
       };
       // 利用Map和forEach
      const uniqueArray2 = function () {
        let map = new Map();// 每个键只出现一次
        arr.forEach(item=>{
          map.set(item,item);
        });
        return [...map.keys()];
      };
      // 利用filter 和 indexOf  根据数组中元素第一次出现的索引值过滤
      const uniqueArray3 = function () {
         return arr.filter((value,index,arr)=>{
            console.log(value,index,arr);
            return arr.indexOf(value) === index;
          });
      };
      // 利用数组的reduce、includes方法判断后一项是否已经在前数组内
      const uniqueArray4 = function ()  {
       return arr.reduce((pre,next) => {
          if(!pre.includes(next)) {
            pre.push(next);
          };
          return pre;
        },[]);
      };
      // 利用数组的sort 方法排序后去重
      const uniqueArray5 = function ()  {
        const orderArr = arr.sort();
        let result = [];
        for(let i = 0;i<orderArr.length;i++) {
           if((orderArr[i] !== orderArr[i+1]) && orderArr[i]) {
            result.push(orderArr[i]);
           }
        }
         return result;
      };
      // 利用双for循环
      const uniqueArray6 = function ()  {
        for(let i = 0;i<arr.length;i++) {
          for(let j = i+1 ;j<arr.length;j++) {
            if(arr[i]===arr[j]) {
              arr.splice(j,1);
              j--;
            }
          }
        };
        return arr;
      }
      console.log(uniqueArray1(),uniqueArray2(),uniqueArray3(),uniqueArray4(),uniqueArray6());  // [1, 2, 3, 4, 5, ]

常见的DOM操作有哪些

1、获取元素:
document.getElementById(id): 根据元素的id属性获取元素。
document.getElementsByClassName(className): 根据元素的class属性获取元素。
document.getElementsByTagName(tagName): 根据元素的标签名获取元素。
document.querySelector(selector): 根据选择器获取第一个匹配的元素。
document.querySelectorAll(selector): 根据选择器获取所有匹配的元素。
2、.创建元素:
document.createElement(tagName): 创建一个指定标签名的元素节点。
document.createTextNode(text): 创建一个包含指定文本内容的文本节点。
3.修改元素属性和内容:
element.setAttribute(name, value): 设置元素的属性值。
element.getAttribute(name): 获取元素的属性值。
element.innerHTML: 设置或获取元素的HTML内容。
element.innerText: 设置或获取元素的文本内容。
4.添加和删除元素:
parentElement.appendChild(newChild): 将一个新的子节点添加到指定父节点的子节点列表的末尾。
parentElement.removeChild(child): 从指定父节点的子节点列表中删除一个子节点。

不使用第三个变量交换数据

      // 方法一、定义一个临时变量,在数据交换中做缓存
      var a = 1;b=2;
      var temp = a;
      a = b;
      b = temp;
      // 方法二、es6 解构
      let a = 1;
      let b= 2;
      [a,b] = [b,a]
      // 方法三、利用中间数组
      let a = 1;
      let b= 2;
      a = [a,b];
      b=a[0];
      a=a[1];
      // 方法四、使用运算符
      let a = 1;
      let b= 2;
      a=a+b;
      b=a-b;
      a=a-b;

es6 新特性有哪些

新增了块级作用域(let、const)
箭头函数
模版字符串
Promise和async/await
解构赋值
默认参数、剩余参数和展开运算符
class 类
Map 和 Set
模块化(Module)

JS的组成部分

ECMAScript 是JavaScript的核心语法,规定了JS的编程语法和基础核心知识;
文档对象模型(DOM) DOM提供的接口可以对页面上的各种元素进行操作(大小、位置、颜色等);
浏览器对象模型(BOM)提供了独立于内容的、可以与浏览器窗口进行交互的对象;

new 操作符的具体步骤

1、创建一个空对象。
2、将这个新对象的原型指向构造函数的原型。
3、将构造函数的 this 指向这个新对象。
4、执行构造函数中的代码,初始化这个新对象。
5、返回这个新对象。

function myNew(constructor, ...args) {
    // 创建一个新的空对象
    let obj = {};
    
    // 将这个新对象的原型指向构造函数的原型
    Object.setPrototypeOf(obj, Object.getPrototypeOf(constructor.prototype));
    
    // 将构造函数的this指向这个新对象  call和apply在改变this指向后,会立即执行函数
    let result = constructor.apply(obj, args); 
    
    // 执行构造函数中的代码,初始化这个新对象
    if (result !== undefined) {
        obj = result;
    }
    
    // 返回这个新对象
    return obj;
}

apply、bind、call的区别并手写

功能一致:call、apply和bind的功能都是可以改变函数执行时的this指向。
执行方式不同:call和apply在改变this指向后,会立即执行函数。而bind在改变this指向后,不会立即执行函数,它返回的是一个函数,需要再通过括号手动调用。
参数不同:第一个参数都是this要指向的对象,第二个参数是调用时需要的参数,call、bind直接将参数一一传入,apply的参数接收的是个数组;

call 实现基本思路:
1、接收需要指向的对象作为上下文,没有传默认就是window;使用agruments接收参数;
2、如果当前的this不是函数,则无法调用抛出错误;
3、获取到除第一个参数外的所有参数(因为第一个参数是context)
4、将this设置为context身上的方法fn
5、调用context的fn方法并保存结果,此执行结果this指向的为context
6、删除context的临时方法
7、返回调用结果

Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
    result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

apply 实现思路,基本同上,区别在于参数是数组

Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
}

bind 实现基本思路:
1、接收需要指向的对象作为上下文,没有传默认就是window;使用agruments接收参数;
2、如果当前的this不是函数,则无法调用抛出错误;
3、获取到除第一个参数外的所有参数(因为第一个参数是context)
4、返回一个函数,该函数内通过构造函数调用call方法

 Function.prototype.myCall =  function (context) {
        if(typeof this !== 'function') {
          throw Error('出错了,无法调用');
        };
        const args = [...arguments].slice(1);
        context = context || window;
        let fn = this;
        return function () {
          return fn.call(context,args);
        }
 }

实现继承的方式有哪些

方法一、es6类中使用extends继承

class Parent {
        constructor (name) {
          this.name = name;
        }
      };
      /* 如果想继承父类构造函数中的属性,不能直接覆盖,需要先调用super 
       ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,
       然后再将该对象作为子类的实例;ES6 的继承必须先调用super()方法,
       因为这一步会生成一个继承父类的this对象
      */ 
      class Child extends Parent {
        constructor (age,name) {
          super(name);
          this.age = age;
          // this.name = name;
        }
      };
      console.log(new Child(2,'tuotuo'))

方法二、原型链继承
缺点:
1、子类父类共享原型,改了子类会影响父类原型
2、创建子类实例时无法传参

function Father () {
        this.name='tuojiba';
        this.info = {
          hobby:'玩逗猫棒'
        }
      };
      // 父类构造函数原型上添加的属性会被子类继承
      Father.prototype.getHobby = function () {
        console.log(this.name +'喜欢'+this.info.hobby)
      }
      function Child () {

      };
      // 在此新建父类实例时无法传参
      const father = new Father();
      Child.prototype = father;
      const child1 = new Child();
      // 由于子类父类原型共享,修改子类的原型属性父类的也会被修改
      Child.prototype.getSex = function () {
        console.log(this.name +'是小妹妹')
      };
      console.log(child1.getHobby(),father.getSex());

方法三、构造函数继承
解决了原型共享、传参的问题,但是这个实现的方法是定义在子类的狗仔函数中的,也就是每次实例化一个子类都会执行一次这个方法;

function Parent (name) {
        this.name = name;
      };
      function Child (name,age) {
        Parent.call(this,name);
        this.age = age;
      };
      // 修改子类的原型不会影响父类的原型
      Child.prototype.getFood = function () {
        console.log('小肉肉饿死啦')
      };
      // 创建子类时可以传参
      const child1 = new Child('小肉肉','2岁啦');
      const parent1 = new Parent('小肉肉妈妈');
      console.log(child1.age,'child1.name')

方法四、组合继承,结合了原型链和构造函数
使用最多的继承方式

function Parent (name) {
        this.name = name;
      };
      function Child (name,age) {
        Parent.call(this,name);
        this.age = age;
      };
      Child.prototype = new Parent();
      // 修改子类的原型不会影响父类的原型
      Child.prototype.getFood = function () {
        console.log('小肉肉饿死啦')
      };
      // 创建子类时可以传参
      const child1 = new Child('小肉肉','2岁啦');
      const parent1 = new Parent('小肉肉妈妈');
      console.log(child1.age,'child1.name')

方法五、Object.create
不需要单独为子类写一个构造函数,但是无法向父类传参,并且新建的对象属性和原对象属性共享;

let parent = {
  mySon:['坨坨--好大儿']
};
let child = Object.create(parent);// 无法向父类传参
child.mySon.push('肉肉--小老二');// 属性共享
console.log(child.mySon,parent.mySon)

前端模块化的方案以及区别

什么是前端模块化?
是指将一个复杂的系统拆成一个个模块,每个模块都有独立的功能,模块直接通过特定的方式进行交互。使用前端模块化的优点:
1、团队协作开发、成员见分工合作;
2、每个模块功能明确可独立开发和测试,便于维护;
3、模块可以直接复用,提高了开发效率;

前端模块化有什么方案?
一、2009 年CommonJs 规范出现,使用 require() 函数来引入其他模块,使用 module.exports 对象来导出模块。
他的加载是同步的,对于服务端来说加载的模块从内存或硬盘中加载基本适用,但客户端由于网络加载的延迟性,无法保证顺序,不适用;
二、2010年美国程序员开发了RequireJS,并发布了AMD,适用于客户端的异步加载规范
三、2011年中国支付宝开发了SeaJS并发布了CMD规范,同样适用于客户端的异步加载规范
四、2015年ECMASctipt 发布了第六个版本同时包含es6 模块化方案,一经发布AMD、CMD基本不再使用,浏览器和客户端都可以使用,但部分浏览器不完全支持,需要使用bable转换;
使用 export 关键字将模块中的变量、函数、对象等导出,使用 import 关键字导入其他模块导出的数据

目前使用最多的还是es6 模块化方案,模块化在开发中的实际应用比如使用webpack打包,webpack采用的是CommonJS,因为webpack基于node运行时默认支持CommonJS;node同时支持CommonJS和es6 模块化;
但是我们可以使用webpack的babel-loader 来转换,从而在组件中直接使用es6的导入导出;文章来源地址https://www.toymoban.com/news/detail-816420.html

到了这里,关于JS 高频面试题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【面试高频】JavaScript作用域、闭包、变量提升

    目录 前言 一、作用域 1. 局部作用域 2. 全局作用域 二、作用域链 三、闭包 1. 闭包是什么 2. 闭包的运用 JavaScript 中的作用域、闭包和变量提升是 JavaScript 中的重要概念,也是面试高频考点。 作用域规定了变量的可见性和生命周期,闭包通过捕获自由变量的方式延长了变量的

    2024年02月12日
    浏览(23)
  • 【手撕面试题】JavaScript(高频知识点三)

    目录 面试官:什么是防抖和节流,他们的应用场景有哪些? 面试官:js中什么是可选操作符,如何访问数组? 面试官:请简述一下 event loop 面试官:请简述 node/v8 中的垃圾回收机制 面试官:如何删除项目中没有使用到的 package? 面试官:请你谈谈 js 中在 new 的时候发生了什么

    2023年04月08日
    浏览(36)
  • 【前端面经】JS-异步解决方案

    同步和异步 众所周知, JavaScript 是一门单线程的语言, 单线程就意味着同一时间只能执行一个任务, 当前任务执行结束, 才会执行下一个任务. 这种模式的好处就是执行环境比较单纯, 但坏处也很明显, 一旦有某个任务卡住了, 就会导致整个程序阻塞. 为了解决这个问题, JS将任务的

    2024年02月02日
    浏览(31)
  • 重学JavaScript高级(十二):async/await-事件循环-面试高频

    前面我们学习了生成器和迭代器,那么在本篇文章中,我们主要讲解生成器与Promise的结合使用,从而引出async/await语法,同时会涉及面试中频次最高的一个知识点: 事件循环 首先需要了解回调地狱 在Promise出来之前,我们多次请求网络接口,有可能产生回调地狱 而Promise的出

    2024年02月02日
    浏览(64)
  • JS 高频面试题

    基本数据类型(Undefined、Null、Boolean、Number、String、Symbol) 引用数据类型(对象、数组和函数) 区别: 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; 引用数据类型在栈中存储了指针,该指针指向堆

    2024年01月23日
    浏览(28)
  • 【前端高频面试题--git篇】

    🚀 作者 :“码上有前” 🚀 文章简介 :前端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 【前端高频面试题–HTML篇】 【前端高频面试题–CSS上篇】 【前端高频面试题–CSS下篇】 【前端高频面试题–JS上篇】 【前端高频面试题–JS下篇】 【前端高频面试题–

    2024年02月22日
    浏览(27)
  • 【前端高频面试题--Vue基础篇】

    【前端高频面试题–HTML篇】 【前端高频面试题–CSS上篇】 【前端高频面试题–CSS下篇】 【前端高频面试题–JS上篇】 【前端高频面试题–JS下篇】 【前端高频面试题–ES6篇】 【前端高频面试题–ES7-ES11】 【前端–异步编程】 【前端高频面试题–TypeScript篇】 【前端高频面

    2024年02月22日
    浏览(39)
  • 【前端高频面试题--微信小程序篇】

    🚀 作者 :“码上有前” 🚀 文章简介 :前端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 【前端高频面试题–HTML篇】 【前端高频面试题–CSS上篇】 【前端高频面试题–CSS下篇】 【前端高频面试题–JS上篇】 【前端高频面试题–JS下篇】 【前端高频面试题–

    2024年02月20日
    浏览(25)
  • 前端高频面试题汇总正题+(附答案解析)

    正题 1、 2、 3、 4、 5、 6、 7、 8、 9、 10、 解析 第1题解析 View Code 第2题解析 View Code 第3题解析 View Code 第4题解析 View Code 第5题解析 View Code 第6题解析 View Code 举例: View Code 第7题解析 View Code 举例: View Code 第8题解析 View Code 第9题解析 View Code 第10题解析 View Code   鉴定完毕,

    2024年02月08日
    浏览(26)
  • 高频前端面试题汇总之HTML篇

    1. src和href的区别 src和href都是 用来引用外部的资源 ,它们的区别如下: src: 表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置。src会将其指向的资源下载并应⽤到⽂档内,如请求js脚本。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源

    2024年04月26日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包