JavaScript基础面试题
面试题:JS是单线程还是双线程
JS是单线程的,本身不可解决异步问题,可以用async和await来解决,或者使setTimeout()。
面试题:延迟加载JS有哪些方式?
1、defer 属性
<script src="test1.js" defer="defer"></script>
用途:表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕之后再执行。在
<script>
元素中设置defer
属性,等于告诉浏览器立即下载,但延迟执行。
defer
属性只适用于外部脚本文件。2、async 属性
<script src="test1.js" async></script>
目的:不让页面等待脚本下载和执行,从而异步加载页面其他内容。
HTML5 为
<script>
标签定义了async
属性。与defer
属性类似,都用于改变处理脚本的行为。同样,只适用于外部脚本文件。注意:async和defer一样,都不会阻塞其他资源下载,所以不会影响页面的加载。
缺点:不能控制加载的顺序3、动态创建DOM方式
将创建DOM的script脚本放置在标签前, 接近页面底部
4、使用jQuery的getScript()方法
5、使用setTimeout延迟方法
延迟加载js代码,给网页加载留出更多时间
6、让JS最后加载
把js外部引入的文件放到页面底部,来让js最后引入,从而加快页面加载速度
面试题:JS数据类型有哪些?
基本数据类型
Number、String、Boolean、Null、Undefined、Symbol、BigInt引用数据类型
object、Array、Date、Function、RegExp
面试题:null和undefined的区别
undefined:
在 JavaScript 中, undefined 是一个没有设置值的变量。
typeof 一个没有值的变量会返回 undefined。
null:
在 JavaScript 中 null 表示 "什么都没有"。
null是一个只有一个值的特殊类型。表示一个空对象引用。
null
和undefined
的值相等,但类型不等:undefined的类型(typeof)是undefined
;null的类型(typeof)是object
面试题:==和===有什么不同
==是非严格意义上的相等,值相等就相等
===是严格意义上的相等,会比较两边的数据类型和值大小值和引用地址都相等才相等
面试题:JS的事件循环(EventLoop)和宏任务和微任务
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
事件循环主要与任务队列有关,所以必须要先知道宏任务与微任务。
在任务队列中,有两种任务:宏任务和微任务。
宏任务:script 标签中的整体代码、setTimeout、setInterval、setImmediate、I/0、UI渲染
微任务:process.nextTick(node.js中进程相关的对象)、Promise
在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
- 同步任务最先执行,然后进入任务队列。
- 从任务队列中取出一个宏任务并执行。
- 检查微任务队列,执行并清空微任务队列,如果在微任务的执行中又加入了新的微任务,也会在这一步一起执行。
总结:注意,在一次事件循环中,实际上宏任务是先于微任务的,但是微任务会一次性把队列的执行完,所以如果要优先执行的话,应该放到微任务里。
面试题:JS作用域+this指向+原型 考题
JS作用域
全局作用域
最外层的全局作用域,任何地方都可以访问得到。在最外层作用域下使用
var
关键字会定义全局变量,也就是说会挂载在window
对象上,或者不使用关键字var、let、const
直接对变量名字进行赋值,JS也会自动为其创建为全局变量。函数作用域
函数作用域内的变量或者内部函数,对外都是封闭的,从外层的作用域无法直接访问函数内部的作用域,否则会报引用错误异常。解决方法:
闭包
。function f1() { var a = 1; var b = 2; var c = 3; } console.log(a, b, c) // ReferenceError: a, b, c is not defined // 原因变量a,b,c是定义在函数内部的变量,外部作用域是无法访问的
块级作用域
块级作用域指在代码块
{}
里面定义的变量,只会在当前代码块有效,如果外层作用域下想访问该变量,会报引用错误异常。使用关键字
let
或const
定义块级作用域的变量。for (let i = 0; i < 10; i++) { } console.log(i) // ReferenceError: i is not defined // 因为i只能在for循环内部有效,外部作用域是访问不到的。
函数作用域和块级作用域的区别:
块级作用域:即在 {}花括号内的域,由 { }包括,比如if {}块、for () {}块。 函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体都是有定义的。
var 和 let的区别:
var : 在调用函数时创建函数作用域,函数执行完毕,作用域销毁。
在函数作用域中可以访问到全局作用域的变量,而全局的访问不到函数作用域的变量;函数作用域可以在相同的作用域重复声明一个变量,存在变量声明提前(指的是变量声明之前就可以使用,只是声明之前,变量的值为 undefined)。let : (用法类似var) 声明的变量仅在块级作用域内有效,离开某一代码块,该变量就会销毁不存在;let 不允许在相同的作用域内重复声明一个变量,否则会报错;不存在变量声明提前,如果在声明之前使用,则会报错。
this指向
- this总是指向函数的直接调用者(而非间接调用者)
- 如果有new关键字,this指向new出来的那个对象
- 在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。
原型
在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。
原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
面试题:JS判断变量是不是数组,你能写出哪些方法?
var Arr= [1,2,3]; console.log(Arr instanceof Array); // instanceof方法 console.log(Arr.__proto__ ===Array.prototype);// 原型链方法 console.log(Array.isArray(Arr)); // Array.isArray方法 //Object.prototype.toString().call()可以获取到对象的不同类型 console.log(Object.prototype.toString.call(Arr)==="[object Array]") // 通用方法,还可以检测其他的 Object.prototype.toString.call(Arr) === '[object Function]';//检测是否是函数
面试题:slice是干嘛的、splice是否会改变原数组
1、slice() 方法
可以用来在我们的数组中提取指定的元素 ;
该方法不会改变原数组,只会将截取到元素封装到一个新数组。
2、splice()方法
它能够实现对数组元素的删除、插入、替换
操作,返回值为被操作的值,即原数组被改变
。
面试题:JS数组去重
一、最简单方法(indexOf 方法)
实现思路:新建一个数组,遍历要去重的数组,当值不在新数组的时候(indexOf 为 -1)就加入该新数组中;
function unique(arr){ var newArr = []; for(var i = 0; i < arr.length; i++){ if(newArr.indexOf(arr[i]) == -1){ newArr.push(arr[i]) } } return newArr; } var arr = [1,2,2,3,4,4,5,1,3]; var newArr = unique(arr); console.log(newArr);
2.利用两层循环+数组的splice方法
通过两层循环对数组元素进行逐一比较,然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的,因为进行比较时
NaN !== NaN
。function removeDuplicate(arr) { let len = arr.length for (let i = 0; i < len; i++) { for (let j = i + 1; j < len; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1) len-- // 减少循环次数提高性能 j-- // 保证j的值自加后不变 } } } return arr } let arr = [1,3,4,3,2,1,1,1] removeDuplicate(arr) console.log(arr)
3、ES6 数组去重
set的特点就是不会有重复元素
function unique(arr){ //Set数据结构,它类似于数组,其成员的值都是唯一的 return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成数组 } var arr = [1,2,2,3,5,3,6,5]; var res = unique(arr) console.log(res );
面试题:找出字符串出现最多次数的字符以及次数
var str = "zhaochucichuzuiduodezifu";
var o = {};
//遍历str,统计每个字符出现的次数
for (var i = 0, length = str.length; i < length; i++) {
//当前第i个字符
var char = str.charAt(i);
//char就是对象o的一个属性,o[char]是属性值,存储出现的次数
if (o[char]) { //如果char属性存在,属性值+1
o[char]++; //次数加1
} else { //char属性不存在为1(即字符第一次出现)
o[char] = 1; //若第一次出现,次数记为1
}
}
//输出的是完整的对象,记录着每一个字符及其出现的次数
//输出{a:1, c:3, d:2, e:1, f:1, h:3, i:3, o:2, u:5, z:3}
console.log(o);
//遍历对象,找到出现次数最多的字符和次数
var max = 0; //存储出现次数最多的次数
var maxChar = null; //存储出现次数最多的字符
for (var key in o) {
if (max < o[key]) {
max = o[key]; //max始终储存次数最大的那个
maxChar = key; //那么对应的字符就是当前的key
}
}
console.log("最多的字符是" + maxChar);
console.log("出现的次数是" + max);
面试题:new操作符具体做了什么
const a = new Foo(); //以下为new 操作符干的事情 var o = new Object(); //新建一个空对象 o.__proto__ = Foo.prototype; //将该空对象的原型指向构造函数的原型对象 Foo.call(o); //在空对象上调用构造函数 a = o; //赋值给变量
面试题:闭包
- 闭包就是能够读取其他函数内部变量的函数
- 闭包基本上就是一个函数内部返回一个函数
好处:
- 可以读取函数内部的变量
- 将变量始终保持在内存中
- 可以封装对象的私有属性和私有方法
坏处:
- 比较耗费内存、使用不当会造成内存溢出的问题
面试题:原型链
原型 && 原型链
原型关系:
对象
有__proto__
属性,函数
有prototype
属性;对象
由函数
生成;- 生成
对象
时,对象
的__proto__
属性指向函数
的prototype
属性原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个
函数对象
都有一个prototype
属性,这个属性指向函数的原型对象
。原型链:原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。
通俗的讲:每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
特点:
JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
面试题: JS继承有哪些方式
- 构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; }
- 原型链继承
将父类的实例作为子类的原型
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; Cat.prototype = new Animal(); //父类 Cat.prototype.name = 'cat';
- 拷贝继承
- 实例继承
- 组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal();
- 寄生组合继承
参考文献:JS实现继承的几种方式 - 幻天芒
面试题:说一下call、apply、bind区别
call
、apply
、bind
作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this
指向。示例:
求数组中的最大值:
apply
var arr=[1,10,5,8,3]; console.log(Math.max.apply(null, arr)); //10
apply
接受两个参数,第一个参数是this
的指向,第二个参数是函数接受的参数,以数组的形式传入改变
this
指向后原函数会立即执行,且此方法只是临时改变this
指向一次call
var arr=[1,10,5,8,3]; console.log(Math.max.call(null,arr[0],arr[1],arr[2],arr[3],arr[4])); //10
call
方法的第一个参数也是this
的指向,后面传入的是一个参数列表跟
apply
一样,改变this
指向后原函数会立即执行,且此方法只是临时改变this
指向一次bind
var arr=[1,10,5,8,12]; var max=Math.max.bind(null,arr[0],arr[1],arr[2],arr[3]) console.log(max(arr[4])); //12,分两次传参
bind方法和call很相似,第一参数也是
this
的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)改变
this
指向后不会立即执行,而是返回一个永久改变this
指向的函数从上面可以看到,
apply
、call
、bind
三者的区别在于:
- 三者都可以改变函数的
this
对象指向- 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
- 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入bind
是返回绑定this之后的函数,apply
、call
则是立即执行
面试题:sort背后原理是什么?
快速排序
面试题:深拷贝和浅拷贝
- 浅拷贝
- 浅拷贝就是可以将对象的基础类型复制,无法复制复杂数据类型
- 浅拷贝只是复制了某个对象的指针,新旧对象还是共用一块内存。如果一个对象改变了内存地址,就会影响到另一个对象。
- Object.assign
- 深拷贝
- 深拷贝就是为了解决无法复制复杂数据类型,对数据进行深程度拷贝
- 会创建一个一模一样的对象,开辟一个新的内存空间,新对象和原对象不会共享内存,修改新对象不会影响原对象。
- 可以通过 JSON.parse(JSON.stringify(object)) 来解决
面试题:localstorage、sessionstorage、cookie的区别
- cookie:主要用来保存登录信息,比如登录某个网站市场可以看到“记住密码”这就是通过cookie中存入一段辨别用户身份的数据来实现的
- sessionStorage:会话,是可以将一部分数据在当前的会话中保存下来,刷新页面数据依旧存在。但是页面关闭时,sessionStorage中的数据就会被清空。
- localStorage:是HTML5标准找那个新加入的技术,
localStorage
中的键值对总是以字符串的形式存储。localStorage
类似sessionStorage
,但其区别在于:存储在localStorage
的数据可以长期保留;1.存储大小
cookie:一般不超过4k
sessionStorage:5M甚至更多
localStorage:5M甚至更多
2. 数据有效期
cookie:一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,如果设置了时间,cookie就会存储在硬盘中,过期失效
sessionStorage:仅在当前浏览器窗口关闭之前有效,关闭页面或者浏览器会被清除
localStorage:永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久删除
3. 作用域
cookie:在所有同源窗口中都是共享的
sessionStorage:在同一个浏览器窗口是共享的(不同浏览器,即使是统一页面也不共享)
localStorage:在所有同源窗口中共享
4. 通信
cookie:cookie在浏览器和服务器之间来回传递,如果使用cookie保存过多数据会造成性能问题
sessionStorage:仅在客户端(浏览器)中保存,不参与服务器的通信
localStorage:仅在客户端(浏览器)中保存,不参与服务器的通信
5. 应用场景
cookie:判断用户是否登录过网站,以便实现下次自动登录或记住密码;保存事件信息
sessionStorage:敏感账号一次性登录,单页面用的较多
localStorage:用于长期登录,适于长期保存在本地的数据