【面试】前端面试之JS篇
“你未来的样子,藏在现在的努力里”
文章目录
- ES6的常用语法有哪些?
- 深拷贝和浅拷贝的区别?
- 针对数组和对象,你如何来实现浅复制和深复制?
- == 和 === 的 区别以及==如何隐式转换?
- 什么是闭包?什么时候会用到?有什么优缺点?
- var 、let和const的区别是什么?
- 如何改变this的指向,call/apply/bind的区别是什么?
- 什么是原型链?如何实现继承,在项目中有没有使用过?
- js实现继承的几种方式?
- new 操作符具体干了些什么?
- 常见的操作数组的方法有哪些?
- js如何判断一个变量的类型,typeof和instanceof的区别是什么?
- 简述同步和异步的区别
- js中的异步操作?
- 如何理解作用域和作用域链?
- 创建自定义方法的方式
- 创建对象的四种方式?
- 用过哪些设计模式?
- JS的事件循环机制
- JS的宏任务和微任务
- 原生js和vue分别怎么阻止事件冒泡?
- 有了解过回调地域吗?怎么解决的?
- var的作用域?
- js实现判断浏览器类型?
- 手写递归函数
- js常用工具库
- 请简述移动端事件冒泡及解决方案?
- 请简述数组的操作方法?(增删改查)
- 以上哪些方法不会改变原始数组?哪些会?
- 数组方法 map、forEach、for、for of 、for in 有什么区别?
- 数组是可迭代的吗?对象是可迭代的吗?
- map和set方法出来的数据有什么区别?
- Object.assign和解构出来的数据有什么区别?
- JSON.parse(JSON.stringify)有什么缺点?
js的数据类型有哪些?
基本数据类型
:Boolean Number String null undefined
Symbol(es6新增,一种数据类型,它的实例是唯一的且不可改变的)复杂数据类型
:Object
两种数据类型的区别是:存储的位置不同
①基本的数据类型直接存储到栈stack中的简单数据段,占据空间小,大小固定,属于被频繁使用数据,所以放在栈中储存
②复杂数据类型存储在堆中,占据空间大,大小不固定,如果存储在栈中,将会影响程序运行的性能,所以他在栈中存储了指针,该指针指向对应堆中的地址,当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中取得值
ES6的常用语法有哪些?
1.
let与const
①let 是声明变量,具有块级作用域,区别于var,不会进行变量提升
②const是只读常量,一旦声明,常量的值就不能改变,且一旦声明就必须赋值
③let与const共同点:A.都不能重复声明 B.都存在块级作用域 C.只在声明所在块级所有域有效
2.
模板字符串--反引号
①字符串和变量拼接 变量用${}
②字符串换行
let str=`hello`
let str1=`bb${str}`
let str2=`<div>
<span>333</span></div>`
3.
箭头函数
(箭头函数中的 this 指的不是window,是对象本身)
①用箭头(=>
)定义函数
②匿名函数和普通函数有什么区别?
A.箭头函数体内的this对象,就是定义所在的对象,即是上下文使用环境对象,假如没有,即是指向window (普通函数是谁调用谁 this就指向谁)
B.不可以当做构造函数,也就是说,不可用使用new命令,否则会抛出错误.
C.普通函数存在变量提升问题
D.不绑定自己的 this,arguments,super或 new.target
setTimeout(()=>{
//this:window
console.log(this);//this是window
},100)
//setTimeout特殊,是因为setTimeout这个方法是挂在到window对象上,
//setTimeout方法执行时,执行回调函数,回调函数中的this指向调用setTimeout的对象 window
4.
解构
(可以直接返回一个新的数组,但也是浅拷贝)按照一定的模式,将数组或对象的变量进行赋值
//对象赋值
let obj={"name":"xiaoming","age":"18"}
let {age}=obj //创建一个对象叫做age=obj.age
//函数赋值
function(obj){
console.log(obj.name)
console.log(obj.age)
}
function({name,age}){
console.log(name)
console.log(age)
}
//个人觉得 函数赋值其实也是一种意义上的对象赋值
5.扩展运算符(
...
) `取出对象所以可遍历的属性,进行浅拷贝
//作用类似于Object.assign()`
let obj={"name":"xiaoming","age":"18"}
let newobj={...obj}
let newobj1={...obj,"age":"7"}
let newobj2={...obj,"sex":"man","type":"human"}
console.log(newobj) //{name: 'xiaoming', age: '18'}
console.log(newobj1)//{name: 'xiaoming', age: '7'}
console.log(newobj2)//{name: 'xiaoming', age: '18', sex: 'man', type: 'human'}
console.log(obj)//{name: 'xiaoming', age: '18'}
6.
Promises
(ES6 对 Promise 有了原生的支持,一个 Promise 是一个等待被异步执行的对象,当它执行完成后,其状态会变成 resolved 或者 rejected)异步编程的解决方案【解决回调地狱】
new Promise((resolve,reject)=>{
$.ajax({
url:'/getDetail',
type:'GET',
success:res=>{resolve(res)},
error:err=>{reject(err)}
})
}).then(res=>{},err=>{})
7.
Classes
(增加了类的概念,其实ES5中已经可以实现类的功能了,只不过使用Class实现可以更加清晰,更像面向对象的写法)
深拷贝和浅拷贝的区别?
所谓的
浅拷贝
,不过就是只复制第一层对象,但是当对象的属性是引用类型时,实质复制的是其引用地址,当引用值改变时,另一个也会跟着变化(就是
他们的堆内存当中使用的是相同的地址)命运相同
但是深拷贝
的话,就是赋值的时候,新的对象的数据存放是直接开辟了一个新的地址,这个时候,当新对象发生改变的时候,旧的对象就不会跟着发生改变了。两个独立的个体
深拷贝可以通过递归的方式。(这个我们要自己尝试写一下)
针对数组和对象,你如何来实现浅复制和深复制?
这里是引用
== 和 === 的 区别以及==如何隐式转换?
两个等于号:==它在作比较时会尝试去自动转换
三个等于号:===它在作比较时不会进行自动的转换
===的判别方式
不同类型,直接false
原始类型值相同,返回true
复合类型(对象,数组,函数)的数据进行比较时,看他们是否指向同一个对象
undefined和null与自身严格相等
==的判别方式
- 布尔值会在比较之前转换成数值 false-0 true-1
- 如果一个操作数是字符串,另一个是数值,比较值前会将字符串转换成数值
- 如果一个操作数是对象,另一个不是,则调用对象的valueOf()方法,在用基本数据类型按照之前的规则进行
- null和undefined是相等的
- 比较之前不能将null和undefined转换
- NaN不等于任何值,包括他自己
- 如果两个操作数都是对象,则比较他们是不是同一个对象,如果都指向同一个对象,则为true,否则为false
undefined和null的区别是什么?
- null是用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象,转换成数值 0 (
对象原型链的终点
)- undefined 表示变量已经声明,但还没赋值的时候 默认值为undefined,转换成NaN,函数没有返回值时,默认返回undefined
什么是闭包?什么时候会用到?有什么优缺点?
前沿知识: 局部作用域:
1.js中不同的函数拥有不同的局部作用域
2.在函数内部创建的变量或函数,只能提供给当前函数内部使用
3.函数内部创建的变量,函数外无法访问
闭包:(一个函数可以访问到另一个函数 作用域里的变量)
//因为函数的作用域,
函数外部不能访问函数内部的局部变量
,需要在函数内部定义一个函数,这个函数就可以读取到第一个函数的变量,将内部函数返回即可
1.函数里面包含着子函数,父函数可以访问子函数的局部变量
2.通过return将子函数暴露在全局作用域,子函数就形成闭包
3.通过闭包,父函数的局部变量没有被销毁,可以通过闭包去调用,但同时,这个局部变量也不会被全局变量污染。
function aaa() {
var a = 0;
return function () {
alert(a++);
};
}
var fun = aaa();
fun(); //1
//简单点就是return一个函数
闭包的应用场景:
1.setTimeout(原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果)
2.回调函数
3.用闭包模拟私有方法
优点:避免全局变量的污染,同时,局部变量没有被销毁,驻留在内存中,还可以被访问
缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法
是,在退出函数之前,将不使用的局部变量全部删除。
var 、let和const的区别是什么?
简单介绍 let 的用法类似于 var ,但是 let 只在所在的代码块内有效,所以我们一般使用 let 替代 var 。而 const
用来声明常量。
让我们先看一看这张表:
声明方式 | 变量提升 | 暂时性死区 | 重复声明 | 初始值 | 作用域 |
---|---|---|---|---|---|
var | 允许 | 不存在 | 允许 | 不需要 | 除块级 |
let | 不允许 | 存在 | 不允许 | 不需要 | 块级 |
const | 不允许 | 存在 | 不允许 | 需要 | 块级 |
1.
var
令会在预解析的时候,会预解析,发生变量提升的现象,而为了预防变量提升时导致意料之外的行为,es6规定let和const命令不发生变量提升
。
2.在相同的作用域内,let和const命令声明的变量是不允许重复声明的
,而var是允许重复定义的。
3.const声明是只读的常量,一旦声明了,就必须立刻初始化
,声明之后值不能改变。
如何改变this的指向,call/apply/bind的区别是什么?
相同点:
1️⃣可以通过bind、call和apply属性改变this的指向
2️⃣第一个参数都是this对象的指向
差别:
1️⃣调用方式不同:bind直接调用的时候,不会执行方法,而是会返回一个新的函数,新的函数里面有bind调用的方法;call/apply可以直接执行该函数
2️⃣传参不同:bind可以直接在新构建的函数里面传递参数
,也可以在传递的第二个参数里面写参数;call在第二个参数可写传参,apply需要将参数放入一个数组当中进行传递
什么是原型链?如何实现继承,在项目中有没有使用过?
为了解决自定义构造函数创建对象存在的内存浪费问题,使用原型 原型
要想访问原型对象有两种方式,一种通过构造函数的prototype,一种是通过new出来的实例对象的__proto__访问的,__proto__不是标准属性,兼容问题ie678不识别,__开头的属性是私有的,内部的
原型链:任何对象都是有__proto__属性,指向他的原型对象,而原型对象也是对象,那么原型对象也有自己的__proto__属性指向他的原型对象,也就是原型对象的原型对象
,这样子形成的链式结构就是原型链。
如何实现继承? 原型链的继承
- 原型式继承
- 构造继承
- 实例继承
- 借用构造函数(call,apply)
- 组合继承(原型链+构造函数)
- es6使用 class extends继承
在项目中有没有使用过? 封装一些处理问题的套路,复用方法
js实现继承的几种方式?
父类代码:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
1.
原型链继承
(将父类的实例作为子类的原型)
缺点:一个实例改变了引用类型的属性,其他实例都会发生改变
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
2.
构造继承
使用父类构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)【可以实现多继承】
缺点:函数无法复用,每调用一次都会创建一个函数对象
function Cat(name){
Animal.call(this); //核心代码 改变this的指向,
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
3.
实例继承
为父类实例添加新特性,作为子类实例返回
**缺点:**实例是父类的实例,不是子类的实例、不支持多继承
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
4.
拷贝继承
缺点: 效率较低,内存占用高(因为要拷贝父类的属性) 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
5.
组合继承
(①即是子类的实例,也是父类的实例②不存在引用属性共享问题③可传参④函数可复用)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用缺点: 调用了两次父类构造函数,生成了两份实例
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// 组合继承也是需要修复构造函数指向的。
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
6.
寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类构造的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点。
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
Cat.prototype.constructor = Cat; // 需要修复下构造函数
new 操作符具体干了些什么?
1.创建了一个空对象,并且this变量引用了该对象,同时还继承了该函数的原型
2.执行函数,上下文this会指定为这个新实例对象
3.如果构造函数返回一个对象,则这个对象会取代new出来的结果,否则会返回这个实例对象,新创建的对象由this所引用,并且最后隐式返回this
常见的操作数组的方法有哪些?
sort()
:对数组进行排序
unshift()
:将参数添加到原数组开头,并返回数组的长度shift()
:把数组的第一个元素删除,并返回第一个元素的值
splice()
:向/从数组中添加/删除项目,然后返回被删除的项目
slice()
:可从已有的数组中返回选定的元素。slice(开始截取位置,结束截取位置)
reverse()
:用于颠倒数组中元素的顺序。
push()
:可向数组的末尾添加一个或多个元素,并返回新的长度,(用来改变数组长度)
pop()
:用于删除并返回数组的最后一个(删除元素)元素,如果数组为空则返回undefined ,把数组长度减 1
join()
:用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。
concat()
:用于连接两个或多个数组,并返回一个新数组,新数组是将参数添加到原数组中构成的
js如何判断一个变量的类型,typeof和instanceof的区别是什么?
- 一般简单的使用 typeof 或 instanceof 检测(这两种检测的不完全准确)
- 完全准确的使用 原生js中的 Object.prototype.toString.call 或 jquery中的 $.type 检测
- typeof可以检测出数据类型,但是array、josn、null的检测不出,需要用instanceof来进行检测
- instanceof用来判断该对象是谁的实例,会随着原型链一直找,其语法是object instanceof constructor,返回值为true或false。
简述同步和异步的区别
同步是阻塞模式,异步是非阻塞模式
同步
就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,知道收到返回信息才继续执行下去;
异步
就是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态,当有消息返回时,系统会通知进程进行处理,这样可以提高执行的效率。
js中的异步操作?
1、定时器 2、事件绑定 3、ajax (一般情况下,我们是用异步的,但是也可以用同步的) 4、回调函数
如何理解作用域和作用域链?
前沿知识:js代码执行前会创建上下文环境,这个上下文环境包括变量、作用域链和this
作用域(scope):变量访问规则的有效范围
- 作用域外无法访问作用域内的变量
- 离开作用域后,作用域的变量的内存空间会被清除
作用域链:简单理解就是从当前环境向父级一层一层查找变量的过程
创建自定义方法的方式
构造函数模式
优点:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型 缺点:每个方法都要在每个实例上重新创新一边,person1/person2都有一个名为sayName()的方法,但是这两个方法不是同一个Function的实例,因为es中的函数都是对象,所有每次定义一个函数都是实例化了一个对象
2.
原型模式
优点:减少了代码的重复,也可以用标识来创建对象
缺点:1.它省略了为构造函数传递初始化参数这一环节,结果所有的势力在默认情况下都将取得相同的属性值
2.原型中所有的属性都是被很多势力共享的,这种共享对于函数来说非常适合,对于那些包含基本值的属性,也还说的过去,但是对于包含引用类型值的属性来说,就是一个问题了,因为实例一般都有属于自己的全部属性。
创建对象的四种方式?
1.
new Object()
缺点:创建对象麻烦,添加属性和方法也麻烦
2.对象的字面量{}
缺点:每次都只能创建一个对象,无法批量创建对象
3.工厂函数实现批量创建对象
缺点:创建的对象无法识别具体类型
4.自定义构造函数
为了解决自定义构造函数创建对象存在的内存浪费问题,使用原型
用过哪些设计模式?
工厂模式
主要好处是可以消除对象之间的耦合,通过使用工程方法而不是new关键字,将所有实例化的代码集中在一个位置防止代码的重复`
但工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题因为根本无法搞清楚他们到底是哪个对象的实例。
待完善
JS的事件循环机制
JS 执行是单线程的,它是基于事件循环的
JS代码分为同步代码和异步代码
同步任务进入主线程执行,异步任务进入事件列表(Event Table)注册函数,当指定的事情完成时,Event Table会将这个函数移入事件队列(Event Queue)。主线程任务为空时,会去Event Queue读取相应的函数,进入主线程执行。
js解析器会不断地重复检查主线程执行栈是否为空,然后一直重复地去事件队列里取。
这就是事件的循环机制
JS的宏任务和微任务
“任务队列”中的任务(task)被分为两类,分别是宏任务(macro task)和微任务(micro task)
宏任务:在一次新的事件循环的过程中,遇到宏任务时,宏任务将被加入任务队列,但需要等到下一次事件循环才会执行。常见的宏任务有 setTimeout、setImmediate、requestAnimationFrame
微任务:当前事件循环的任务队列为空时,微任务队列中的任务就会被依次执行。在执行过程中,如果遇到微任务,微任务被加入到当前事件循环的微任务队列中。简单来说,只要有微任务就会继续执行,而不是放到下一个事件循环才执行。常见的微任务有 MutationObserver、Promise.then
总的来说,在事件循环中,微任务会先于宏任务执行。而在微任务执行完后会进入浏览器更新渲染阶段,所以在更新渲染前使用微任务会比宏任务快一些。
通俗一点讲就是:
宏任务(macrotask)和微任务(microtask)
表示异步任务的两种分类,在挂起任务时,js引擎会将所有的任务按照类别分到这两个队列当中,首先遇到宏任务,会先执行宏任务,将宏任务放入eventqueue,然后再执行微任务,将微任务放入event queue,(这两个queue不是一个queue),当往外拿的时候,是先从微任务当中拿掉回调函数,然后再从宏任务的queue上拿宏任务的回调函数。
nextTick的原理
先来看一个很常见的场景:
created(){
this.id = 10
this.list = []
this.info = {}
}
众所周知,
Vue基于数据驱动视图
,数据更改会触发setter函数,通知watcher进行更新,若是像上面的情况,是否代表需要更新三次,而且在实际开发当中更新也不止那么少。更新的过程需要经过繁杂的操作,例如模版编译、dom diff、频繁进行更新的性能当然会更差
。
Vue作为一个优秀的框架,当然不会那么‘直男’全部照单全收,Vue内部实际就是将watcher加入到一个queue数组中,最后再触发queue所有watcher的run方法来更新,并且加入queue的过程中还会对watcher进行去重操作,因为在一个vue实例中data内定义的数据都是存储同一个渲染wathcer,所以以上的场景中数据即使更新了三次,最终也只会执行一次更新页面的逻辑。
为了达到这种效果,Vue使用的是异步更新,等待所有数据同步修改完成后,再去执行更新的逻辑
异步更新内部最重要的就是nextTick方法,它负责将异步任务加入队列和执行异步任务,Vue也将它暴露出来提供给用户使用,
当数据修改完成后,立即获取相关的dom还没那么快进行更新,使用nextTick便可以解决这一个问题。
异步更新机制使用微任务或宏任务,基于事件循环运行,在 Vue 中对性能起着至关重要的作用,它对重复冗余的 watcher 进行过滤。而
nextTick 根据不同的环境,使用优先级最高的异步任务
。这样做的好处是等待所有的状态同步更新完毕后,再一次性渲染页面。用户创建的 nextTick 运行页面更新之后,因此能够获取更新后的DOM。
这位小哥的文章讲的更详细,还有源码的讲解
https://www.cnblogs.com/sexintercourse/p/13296337.html
原生js和vue分别怎么阻止事件冒泡?
事件冒泡:事件从事件目标(target)开始,往上冒泡直到页面的最上一级的标签。
原生js阻止事件冒泡的方式:
一、event.stopPropagation( );
<div>
<p>段落文本内容
<input type="button" value="点击" />
</p>
</div>
// 为所有div元素绑定click事件
$("div").click( function(event){
alert("div-click");
} );
//为所有p元素绑定click事件
$("p").click( function(event){
alert("p-click");
} );
//为所有button元素绑定click事件
$(":button").click( function(event){
alert("button-click");
// 阻止事件冒泡到DOM树上
event.stopPropagation(); // 只执行button的click,如果注释掉该行,将执行button、p和div的click (同类型的事件)
} );
阻止了事件冒泡,但不会阻击默认行为
vue阻止事件冒泡的方式:用stop
这里是引用
<div @click="test1()">
<span @click.stop="test2()">按钮1</span>
<span>按钮2</span>
</div>
为了防止 点击div里面的按钮1触发到div的click,只需要在按钮1的click当中加个.stop
有了解过回调地域吗?怎么解决的?
回调地域:函数作为参数层层嵌套
解决方式:
1.保持代码简洁
2.模块化
3.处理每一个错误
4.创建模块时的一些经验法则
5.es6/生成器
Promises:编写异步代码的一种方式,它仍然以自顶向下的方式执行,并且由于鼓励使用try/catch样式错误处理,从而能够处理更多类型的错误
var的作用域?
var的变量提升(hoisting)
使用var在函数作用域或全局使用他,它都会进行变量提升
但just提升声明,赋值还是在原先的地方进行赋值
JavaScript var的作用域和提升:https://blog.csdn.net/Liuqz2009/article/details/103712738
js实现判断浏览器类型?
function brower() {
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
var isChrome = userAgent.indexOf("Chrome") > -1;
var isOpera = userAgent.indexOf("isOpera") > -1;
var isIE =
userAgent.indexOf("compatible") &&
userAgent.indexOf("MSIE") > -1 &&
!isOpera;
}
手写递归函数
function deepClone(obj) {
if (typeof obj !== "object" && obj === "undefined") return obj;
let res;
if (obj instanceof Array) {
res = [];
} else {
res = {};
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//判断是否是原型链上的属性
res[key] = deepClone(obj[key]);
}
}
return res;
}
js常用工具库
Lodash
Lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库
Lodash 中文文档
Lodash 官网
path-to-regexp
path-to-regexp只要用于url 字符串的正则匹配,包含parse、exec、compile等方法
path-to-regexp使用说明
moment
Moment.js 是一个 JavaScript 日期处理类库,用于解析、检验、操作、以及显示日期
Moment中文文档
moment.js中文网
请简述移动端事件冒泡及解决方案?
事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到document对象,可以想象是把一颗石头投入水中,泡泡会一直从水底冒向水面,而事件捕获
的顺序则相反。
举个栗子:
<div onclick="alert('1')">
<ul onclick="alert('2')"><li onclick="alert('3')"> </li> </ul>
</div>
这会依次输入3>2>1 从li到ul到div,这就是事件冒泡。
而在移动端中,事件的冒泡常常会导致bug,例如页面有个弹窗,当我们滑动弹窗时,最底下的页面也在跟着动,这就是事件冒泡导致的问题,我们需要进行阻止冒泡
。
方法如下:
1️⃣e.stopPropagation()
e.stopPropagation() //在点击事件函数中加入
2️⃣window.event.cancelBubble = true (谷歌,IE8兼容,火狐不支持)
window.event.cancelBubble = true
3️⃣vue中可以通过事件修饰符对冒泡行为进行阻止
<div @click.stop="alert('1')">
<ul @click.stop="alert('2')"><li @click.stop="alert('3')"> </li> </ul>
</div>
请简述数组的操作方法?(增删改查)
增
:push(数组尾增加元素)、unshift(数组前增加元素)、concat(合并两个或多个数组)
删
:pop(删除最后一项)、shift(删除第一项)
改
:slice(截取数组)、splice(添加删除数组)、reverse(颠倒数组元素中的顺序)、toString(数组转字符串)
查
:indexOf(是否包含该数组)、findIndex、map、foreach、find、reduce、filter(过滤)、some(过滤,只要有一个)、includes(查询是否存在)
以上哪些方法不会改变原始数组?哪些会?
不会改变: slice、concat、toString、map
会改变: splice、push、unshift、pop、shift、reverse、forEach
数组方法 map、forEach、for、for of 、for in 有什么区别?
forEach
:一般用于遍历数组,有两个参数,一个item,一个index,不能return,在原数组操作,会改变数组
map
:一般用于遍历数组,需要return,否则会得到undefined,会返回一个新的数组,不会改变原数组
for in(es5)
:一般用于遍历对象,可以循环数组索引、对象的属性,遍历出来的key是字符串类型,较少用于遍历数组元素
缺点:1️⃣遍历出来的元素顺序有可能是乱序的,开销较大2️⃣ 原型链上的所有属性都将被访问
for of(es6)
:一般用于遍历数组,遍历出的是数组中的值,支持map和Set对象遍历
for/forEach/for of都不能遍历对象,那我们要怎么让他变成可以呢?
借用一个方法,Object.values(obj)/Object.keys(obj)/Object.entries,把对象变成类似数组的对象(迭代对象产生数组),就可以啦~
Object.keys()
方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)
Object.values()
返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同
let obj={'address':'广州','sex':'女','name':'小明'}
let obj1=Object.values(obj)
for(let item of obj1){
console.log(item,'item')
}
数组是可迭代的吗?对象是可迭代的吗?
可迭代对象
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for…of 循环中使用的对象。
数组是可迭代的。但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的。
如果从技术上讲,对象不是数组,而是表示某物的集合(列表,集合),for…of 是一个能够遍历它的很好的语法,因此,让我们来看看如何使其发挥作用。
map和set方法出来的数据有什么区别?
map
:是键值对的数组结构,是以数组里面[[‘key’,‘value’]]形式存在的,new Map(二维数组)
set
:是不重复的值的集合,new Set(数组) 返回的是set对象
(1) 初始化需要的值不一样,Map需要的是一个二维数组,而Set 需要的是一维 Array 数组
(2) Map 和 Set 都不允许键重复
(3) Map的键是不能修改,但是键对应的值是可以修改的;Set不能通过迭代器来改变Set的值,因为Set的值就是键。
(4) Map 是键值对的存在,值也不作为健;而 Set 没有 value 只有 key,value 就是 key;
Object.assign和解构出来的数据有什么区别?
Object.assign
:
语法:Object.assign(target, source1, source2) 返回target
实践:往函数内传入对象参数的时候,如果函数内对obj进行操作可能会改变外部参数,为了让其不相互影响,可使用Object.assign
解构
:
let user = {
name: ‘jenny’,
id: 18
}
let {name, id, job} = user
console.log(name, id, job) // jenny 18 undefined
JSON.parse(JSON.stringify)有什么缺点?
JSON.parse(JSON.stringify):可用于深拷贝
缺点
:
1️⃣如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象
2️⃣如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;