js学习总结(待补充)
1.JS中有哪些数据类型?
答:基本数据类型(String、Number、Boolean、Undefined、Null、Symbol)、引用数据类型(Object)
2.对象(数组)的深克隆和浅克隆
let obj = { a:100, b:[10,20,30], c:{ x:10 }, d:/^\d+$/ }; let arr = [10,[100,200],{ x:10, y:20 }];
深克隆方案:
function deepClone(params={}){ if(typeof params !== 'object'){ return params } if(params == null){ return null } if(params instanceof RegExp){ return new RegExp(params) } if(params instanceof Date){ return new Date(params) } let result = new params.constructor for(key in params){ if(params.hasOwnProperty(key)){ result[key] = deepClone(params[key]) } } return result }
3.堆栈内存和闭包作用域面试题
堆:存储引用类型值的空间
栈:存储基本类型值和指定代码的环境
let a={},b='0',c=0 a[b] = '水' a[c] = '火' console.log(a[b])
答:火,对象的属性名中数字和字符串等效
let a ={},b=Symbol('1'),c=Symbol('1') a[b]='水' a[c] = '火' console.log(a[b])
答:水,Symbol的特点,都是唯一的
let a={},b={n:'1'},c={m:'2'} a[b]='水' a[c] = '火' console.log(a[b])
答:火,key会转化成字符串[Obejct object],Object.prototype.toString / valueOf
var test = (function(i){ return function(){ alert(i*=2) } })(2) test(5)
答:‘4’ 这题涉及了闭包和立即执行函数(IIFE)以及执行上下文,alert弹出的结果都要转化为字符串
var a = 0, b = 0 function A(a){ A = function(b){ alert(a + b++) } alert(a++) } A(1) A(2)
答:‘1’,‘4’ 解析:a++表示先展示a,再自增,全局的ab和函数里的ab没关系
4.谈谈你对闭包的理解?
答:当一个嵌套的内部(子)函数访问了嵌套的外部(父)函数的变量(函数)时,就产生了闭包,延长了局部变量的生命周期,又不会造成全局污染,但是占用内存较多,不易被释放。在防抖和节流中会用到闭包。
5.什么是执行上下文和执行上下文栈?
答:全局执行上下文和全局执行上下文栈(在执行全局代码前将window确定为全局执行上下文对象,对全局数据进行预处理,添加为window的属性和方法,给this赋值,然后将window对象压入栈中,代码执行完毕后出栈,回收执行上下文),函数执行上下文和函数执行上下文栈(在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象,对局部数据进行预处理,压入对应的栈中,函数执行完毕后放出对象,回收执行上下文)
6.什么是作用域和作用域链?
答:作用域:是一段代码所在的区域,控制着变量和函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期,分为全局作用域和函数作用域以及ES6的块作用域。
作用域链:多个上下级关系的作用域形成的链,它的方向是从下到上的(从内到外),查找变量时就是沿着作用域来查找的。
7.作用域和执行上下文有什么区别?
答:和执行上下文不同,作用域在函数定义时就确定了,且作用域是静态的,只要函数定义好了就一直存在,且不会再变化。而执行上下文环境是动态的,调用函数时创建,函数调用结束时执行上下文环境就会自动释放。
8.深拷贝和浅拷贝的实现原理,以及为什么要深拷贝?
答:深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型来说的,浅拷贝只是复制指向某个对象的指针,新旧对象还是共享同一块内存,而深拷贝会在栈中开辟一个新内存,创造出一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。同一个Array或者Object赋值给两个不同变量时,变量指向的是同一个内存地址,是浅拷贝,而大多数实际项目中,我们想要的结果是两个变量(初始值相同)互不影响,所以就要使用到深拷贝。
9.typeof和instanceof的区别?
答:typeof用于判断数据类型,返回值为6个字符串,分别为string
、Boolean
、number
、function
、object
、undefined
。但是typeof
在判断null
、array
、object
以及函数实例时,得到的都是object
。这使得在判断这些数据类型的时候,得不到真是的数据类型,由此引出instanceof,instanceof用来判断对象,代码形式为obj1 instanceof obj2(obj1是否是obj2的实例),obj2必须为对象,否则会报错!其返回值为布尔值。
10.如何区别Object和Array?
数组表示有序数据的集合,对象表示无序数据的集合。通过instanceof、isArray方法来区分Object和Array。
11.一道面向对象面试题?
function Foo(){ getName = function(){ console.log(1) } 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() getName() Foo().getName() getName() new Foo.getName() new Foo().getName() new new Foo().getName()
答:2,4,1,1,2,3, 涉及变量、函数提升,执行上下文,原型和原型链,运算符优先级,this,new方法
12.谈谈你对原型和原型链的理解?
答:每个构造函数一旦创建都有prototype指针指向它的原型对象(构造函数.prototype)。而原型对象(构造函数.prototype)会默认生成一个constructor指针又指向构造函数。在创建该构造函数的实例时,实例有一个内部属性__proto__指向该原型对象。原型对象内创建的所有方法属性会被所有实例共享。而构造函数的原型对象作为一个对象实例,它的__proto__指针就指向Object的原型对象,这样就形成了一个原型链,当实例对象查找一个属性或者方法时,自身如果没有就会通过__proto__在原型链上一级一级的查找,找到了就返回,如果没有找到就返回undefined。
13.谈谈对this的理解?
答:解析器在调用函数时,每次都会向函数内部传递进一个隐含的参数,这个参数就是this,this指向的是一个对象,我们称之为函数执行的上下文对象,根据函数调用方式的不同,this会指向不同的对象,以函数的形式调用时,this永远都是window,以方法的形式调用时,this就是调用方法的那个对象,箭头函数是没有this,箭头函数里的this指向的是外面的第一个不是箭头函数的函数的this,如果没有,就是window。可以利用call,apply,bind改变this。
14.用js实现new方法?
function Person(name,age){ this.name = name this.age = age } Person.prototype.sayName = function(){ console.log(this.name) } function New(func){ return function(){ var o = {__proto__:func.prototype} func.apply(o,arguments) return o } } const child = New(Person)('大雄',15) child.sayName()
15.一道面试题让你彻底掌握JS中的EventLoop
async function async1(){ console.log('async1 start'); await async2() console.log('async1 end'); } async function async2(){ console.log('async2'); } console.log('script start'); setTimeout(() => { console.log('setTimeout'); }, 0); async1() new Promise(function(resolve){ console.log('promise1'); resolve() }).then(function(){ console.log('promise2'); }) console.log('script end'); // script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
涉及知识点:同步异步、微队列宏队列、async和await、Promise
16.面试题
setTimeout(() => { console.log(1); }, 20); console.log(2); setTimeout(() => { console.log(3); }, 10); console.log(4); console.time('AA'); /* for (let i = 0; i < 90000000; i++) { } console.timeEnd('AA'); */ console.log(5); setTimeout(() => { console.log(6); }, 8); console.log(7); setTimeout(() => { console.log(8); }, 15); console.log(9); //2 4 5 7 9 3 1 6 8 有循环 //2 4 5 7 9 6 3 8 1 无循环
关键点:多个setTimeout进入宏队列后,出来的顺序是由延迟时间来决定的
var x = 2 var y = { x:3, z:(function(x){ this.x*=x x+=2 return function(n){ this.x*=n x+=3 console.log(x); } })(x) } var m = y.z m(4) //7 y.z(5)//10 console.log(x,y.x);//16,15
关键点:在函数内部this.x表示全局变量x,x表示局部变量,x+=3先在函数内部找x,函数内部没找到就沿着作用域找,一直到全局作用域,找到后返回,得到的值会成为局部变量x的值。
17.JS中DOM节点的插入操作有哪些?
appendChild、insertBefore(a,b)
18.如何解决跨域?
答:jsonp跨域(利用了标签发送GET请求“天然跨域”,不受同源策略的限制,但是只能解决GET请求跨域问题)
cors跨域(在后台的每个ajax路由中加入response.set('Access-Control-Allow-Origin','http://127.0.0.1:8848'))
nginx跨域
proxy跨域(利用http-proxy-middleware中间件设置代理浏览器实现跨域)
19.防抖(debounce)与节流(throttle)区别与实现?
答:防抖(触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间)
const debounce = (fn,time) =>{ let timeout = null return function(){ clearTimeout(timeout) timeout = setTimeout(() => { fn.apply(this,arguments) }, time); } }
节流(高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。)
const throttle = (fn, time) => { let flag = true; return function() { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, arguments); flag = true; }, time); } }
20.冒泡和捕获?
答:
冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点
捕获:和冒泡恰恰相反,事件捕获流的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件
DOM2级事件流包含三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
阻止事件冒泡(e.stopPropagation()和IE的e.cancelBubble = true)
阻止默认行为(e.preventDefault()以及IE使用e.returnValue = false)
21.用js实现一个promise?
function MyPromise(excutor){ this.status = 'pending' this.data = undefined this.calbacks = [] let resolve = (value) =>{ if(this.status !== 'pending'){ return } this.data = value this.status = 'resolved' if(this.calbacks.length>0){ setTimeout(()=>{ this.calbacks.forEach(calbacksObj =>{ calbacksObj.onResolved(value) }) }) } } let reject = (reason) =>{ if(this.status === 'pending'){ return } this.data = reason this.status = 'rejected' if(this.calbacks.length>0){ setTimeout(()=>{ this.calbacks.forEach(calbacksObj=>{ calbacksObj.onRejected(reason) }) }) } } try{ excutor(resolve,reject) }catch(error){ reject(error) } } Promise.prototype.then = function(onResolved,onRejected){ const self = this onResolved = typeof onResolved === 'function' ? onResolved : value=>value onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} function handle(calback){ try{ const result = calback(self.data) if(result instanseof Promise){ result.then(resolve,reject) }else{ resolve(result) } }catch(err){ reject(err) } } return new Promise((resolve,reject)=>{ if(this.status === 'pending'){ this.calbacks.push({ onResolved(value){ handle(onResolved) }, onRejected(reason){ handle(onRejected) } }) }else if(this.status === 'resolved'){ setTimeout(()=>{ handle(onResolved) }) }else{ setTimeout(()=>{ handle(onRejected) }) } }) } Promise.prototype.catch = function(onRejected){ return this.then(undefined,onRejected) } Promise.resolve = function(value){ return new Promise((resolve,reject)=>{ if(value instanceof Promise){ value.then(resolve,reject) }else{ resolve(value) } }) } Promise.reject = function(reason){ return new Promise((resolve,reject)=>{ reject(reason) }) } Promise.all = function(promises){ const values = new Array(promises.length) //用来保存所有成功value的数组 let resolveCount =0 return new Promise((resolve,reject)=>{ //遍历promises获取每个promise的结果 promises.forEach((p,index)=>{ Promise.resolve(p).then( value =>{ resolveCount++ values[index] = value //如果全部成功了,将return的promise改为成功 if (resolveCount === promises.length) { resolve(values) } }, reason =>{//只要一个失败,return的promise就失败 reject(reason) } ) }) }) } Promise.race = function(promises){ //返回一个promise return new Promise((resolve,reject)=>{ //遍历promises获取每个promise的结果 promises.forEach((p,index)=>{ Promise.resolve(p).then( value =>{//一旦有成功的,将return变为成功 resolve() }, reason =>{//一旦有失败的,将return变为失败 } ) }) }) }
22.post请求和get请求的区别?
答:1.GET把参数包含在URL中,POST通过request body传递参数
2.Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制
23.localStorage、sessionStorage和cookie的区别?
答:1、cookie数据始终在同源的http请求中携带,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。
2、cookie存储数据不会超过4k,而sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
3、sessionStorage只在当前浏览器窗口关闭之前存在,localStorage始终有效,窗口或浏览器关闭也一直保存,cookie只在设置的cookie过期时间之前有效。
24.简述ajax的过程?
答:实例化一个xhr对象,连接服务器,准备数据(xhr.open),发送请求(xhr.send),监听状态变化,接收服务器返回。
25.宏任务和微任务?
答:宏任务(整体script,setTimeout,setInterval)微任务(Promise,process.nextTick)
当主线程的同步任务执行完毕后,先执行宏任务,将宏任务放入宏任务的eventqueue,然后执行所有微任务,将微任务放入到微任务的eventqueue。但是当往外拿回调函数时是先拿微任务的,其次是宏任务的。
26.数组的常用方法?
答:
改变原数组的方法:splice(添加/删除)、pop(删除数组的最后一个元素)、push(向数组末尾添加元素)、shift(删除数组的第一个元素)、unshift(向数组开头添加元素)、reverse(颠倒数组中元素位置)、sort(数组排序)
不改变原数组的方法:slice(浅拷贝数组的元素,选取一个或多个元素)、join(将数组转换为有一个字符串)、concat(合并数组)、indexOf(查找数组中是否存在某个元素)、lastIndexOf(查找元素在数组中的最后一位)、findIndex(查找数组中给定值的索引,没有就返回-1)、every(如果数组中的每个元素都符合条件,则返回true,否则为false)、some(和every正好相反,只要有一个符合就返回true)、includes(类似some,但是不是用回调,直接给一个参数值比较就可以了)
数组的遍历方法:forEach(遍历数组并不改变原数组)、map(数组中元素为对象时使用,返回每个元素中的某个属性形成新数组)、filter(遍历数组,返回符合条件的元素形成新数组)、reduce(通过初始值遍历数组,累加得到最终值)、for of
27.怎么实现数组去重?
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
双重for循环+splice
const unique1 = 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); // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能 len--; j--; } } } return arr; }
ndexOf方法去重(for)
const unique2 = arr => { const res = [arr[0]]; for (let i = 1; i < arr.length; i++) { if (res.indexOf(arr[i]) === -1) res.push(arr[i]); } return res; }
indexOf方法去重(Array.prototype.filter.call)
const uniq = arr => { return Array.prototype.filter.call(arr,function(item,index){ return arr.indexOf(item) === index }) } console.log(uniq(arr));
set与解构赋值去重(利用ES6中set数据不重复的特性,Set函数可以接收一个数组参数进行初始化进行去重,然后通过结构赋值得到一个数组)
console.log([...new Set(arr)]);
Array.from与set去重(Array.from方法可以将Set结构转换为数组结果)
const uniq = arr => { return Array.from(new Set(arr)) } console.log(uniq(arr));
相邻元素去重(sort排序和for循环)
const uniq = arr => { if (!Array.isArray(arr)) { console.log('type error!') return; } arr = arr.sort() var arrry= [arr[0]]; for (var i = 1; i < arr.length; i++) { if (arr[i] !== arr[i-1]) { arrry.push(arr[i]); } } return arrry; } console.log(uniq(arr));
利用includes
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var array =[]; for(var i = 0; i < arr.length; i++) { if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值 array.push(arr[i]); } } return array } console.log(unique(arr))
28.数据扁平化?
const arr = [1, [2, [3, [4, 5]]], 6];
使用flat()
const res1 = arr.flat(Infinity);
利用正则
const res2 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
使用reduce+递归
const flatten = arr => { return arr.reduce((pre, cur) => { return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []) } const res4 = flatten(arr);
函数递归
const res5 = []; const fn = arr => { for (let i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { fn(arr[i]); } else { res5.push(arr[i]); } } } fn(arr); console.log(res5)
29.类数组转化为数组?
答:Array.from(类数组)
Array.prototype.slice.call(类数组)
[...类数组]
Array.prototype.concat.apply([],类数组)
30.打印出当前网页使用了多少种HTML元素?
const req = ()=>{ return [...new Set([...document.querySelectorAll('*')].map(el=>el.tagName))].length } console.log(req());
31.对象的遍历方法?
答:for in循环
for(let property in obj){
console.log(property)
}
//在使用之前,需要加入obj.hasOwnProperty(property)检查是否是对象的自有属性
32.es6合并对象的方法?
Object.assign
let obj1 = {name:'a',age:18}; let obj2 = {name:'b',gender:'man'}; let obj3 = {}; Object.assign(obj3,obj1,obj2); console.log(obj3);//{name: "b", age: 18, gender: "man"}
三点运算符
let obj1 = {name:'a',age:18}; let obj2 = {name:'b',gender:'man'}; let obj3 = {...obj1,...obj2}; console.log(obj3);//{name: "b", age: 18, gender: "man"}
33.常用的数组排序方法?
冒泡排序法:将数组中的相邻元素进行比较(第一次比较次数为数组length-1,依次递减),较大值通过两两比较移动到数组最末尾,然后循环比较。
let arr = [3,1,2,4,5,7,6] let len = arr.length for (let i = len-1; i >0; i--) { for (let j = 0; j < i; j++) { if (arr[j]>arr[j+1]) { let temp = arr[j] arr[j] = arr[j+1] arr[j+1] = temp } } } console.log(arr);
选择排序法:从第一个元素开始,依次和后面的元素进行对比,筛选出最小值,然后将最小值和选中元素位置互换。然后结束循环,开始下一次循环。
let arr = [3,1,2,4,5,7,6] let len = arr.length for (let i = 0; i < len-1; i++) { let min = i for (let j = min+1; j < len; j++) { if (arr[min]>arr[j]) { min = j } } let temp = arr[min] arr[min] = arr[i] arr[i] = temp } console.log(arr);
插入排序法:从第二个数组元素开始,依次和前面的元素进行对比,如比选中元素大,和选中元素互换位置,如比选中元素小,结束这次循环,开始下一次循环。
let arr = [3,1,2,4,5,7,6] let len = arr.length for (let i = 1; i < len; i++) { let temp = arr[i] let j = i while (arr[j-1]>temp && j>0) { arr[j] = arr[j-1] j-- } arr[j] = temp } console.log(arr);
快速排序法:选择数组首尾中三个元素从小到大排序,然后大于中间元素的元素分为一组,小于中间元素的分为一组,这两组继续上述操作,最后用concat连接
let arr = [3,1,2,4,5,7,6] //变换位置 let swap = function(arr,m,n){ let temp = arr[m] arr[m] = arr[n] arr[n] = temp } //返回center let median = function(arr){ let center = Math.floor(arr.length/2) let right = arr.length - 1 let left = 0 if (arr[left]>arr[center]) { swap(arr,left,center) } if (arr[center]>arr[right]) { swap(arr,center,right) } if (arr[left]>arr[center]) { swap(arr,left,center) } return center } let QuickSort = function(arr){ if (arr.length === 0) { return [] } let center = median(arr) let c = arr.splice(center,1) let l = [] let r = [] for (let i = 0; i < arr.length; i++) { if (arr[i]<c) { l.push(arr[i]) }else{ r.push(arr[i]) } } return QuickSort(l).concat(c,QuickSort(r)) } console.log(QuickSort(arr));
34.http和https的区别?
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议,比http协议安全。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
35.es6新特性?
const和let(const和let不能变量提前声明,块级作用域,let在同一块级作用域中只能重新赋值不能重新声明,const则是既不能重新赋值也不能重新声明)
模板字面量(` `)
解构赋值
for...of循环(可以循环任何可迭代类型的数据(String、Array、Map、Let),不包括Object数据类型,默认情况下对象不能迭代)
三点运算符(如果你需要结合多个数组,在有展开运算符之前,必须使用Array的concat()方法。)
箭头函数
Promise
class
36.箭头函数和普通函数的区别?
1、箭头函数是匿名函数,不能作为构造函数,不能使用new
2、箭头函数不能绑定arguments,取而代之用rest参数
3、箭头函数没有原型属性
4、箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
37.call,apply,bind的区别?
bind返回对应函数, 便于稍后调用; apply, call则是立即调用。
38.es5和es6作用域区别?
es5只有全局作用域和函数作用域,而es6新增了块级作用域(就是{}包含的代码块),块级作用域里的变量不能在其他作用域里使用,而全局作用域的变量可以在函数作用域中使用。
39.什么是暂时性死区?
在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。
40.ajax和websocket区别?
Ajax,即异步JavaScript和XML,是一种创建交互式网页应用的网页开发技术;
WebSocket是HTML5一种新的协议。
websocket建立的是长连接,在一个会话中一直保持连接;而ajax是短连接,数据发送和接受完成后就会断开连接。
websocket一般用于前后端实时数据交互,而ajax前后端非实时数据交互。
Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以相互推送信息。
41.js处理异步的方式有哪些?
1、回调函数
2、事件监听
3、Promise
4、async/await
5、setTimeout
42.如何判断当前脚本运行在浏览器还是node环境中?
通过判断Global对象是否为window,如果不是window,则当前脚本没有运行在浏览器中。
43.请解释一下JS的同源策略?
同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。
44.class类如何创建实例?通过什么实现继承?如何调用父类的构造方法?通过什么定义构造方法?
通过extends来实现类的继承
通过super调用父类的构造方法
49.讲下DOM对象的三种查询方式?
getElementById()根据元素id来查找 ,getElementsByTagName(tag):根据元素的tag名字来查找
getElementsByName(name) 根据元素名字来查找
50.怎么样创建元素节点和文本节点,怎么样删除节点?
51.用那个属性可以快速的给一个节点加一段html内容?