js面试题
81个JavaScript实例代码
Blob构造函数下载文本内容
什么是闭包?
闭包
是指存在自由变量的函数!在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、递归(对可遍历的属性进行递归)
// 一般来说,在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
浅拷贝方式:
- 1、Object.assign
- 2、(...) 扩展运算符
什么是箭头函数
- 箭头函数是在es6或更高版本中编写函数的表达式
解释JS中的高阶函数
- 高阶函数是JS函数式编程的最佳特性。它是以函数为参数并返回函数作为结果的函数。一些内置的高阶函数是map、filter、reduce 等等。
call、bind、apply的区别
- 1、都是用来改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向
- 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 动态操作标签元素
- 创建新节点:
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)
- 添加、删除、更新、插入:
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
所以最终的输出结果就是 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/