miansisa
B站JS面试题合集
| JS的拷贝 | 和原数据是否指向同一对象 | 第一层数据为一般数据类型 | 第一层数据不是原始数据 |
|---|---|---|---|
| 赋值 | 是 | 改变会使原始数据一同改变 | 改变会使原数据一同改变 |
| 浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原始数据一同改变 |
| 深拷贝 | 否 | 改变不会使原始数据一同改变 | 改变不会使原始数据一同改变 |
防抖函数函数理解和实现
当持续触发事件 一定时间内没有再次触发事件 事件处理函数才会执行一次 如果设定的时间到来之前 又一次触发了事件 就重新开始延时
触发事件 一段时间内 没有触发 事件执行 肯定是定时器
节流函数
当持续触发事件的时候 保证一段时间内 只调用一次事件处理函数 一段时间内 只做一件事
setTimeout setInterval 区别
setInteval定时调用 clearInterval() 用于取消setInteval()函数设定的定时执行操作
settimeout 为0有什么用
js是单线程的,基于事件循环的 setTimeout函数是异步的,异步的事件会加入一个队列,会等到当前同步的任务执行完毕后,再执行setTimeout队列的任务,所以,通过设置任务在延迟0毫秒后执行,可以改变执行的先后顺序,延迟该任务发生,改变它所调用的函数的优先级,使之异步执行。
js 事件循环机制event-loop
function func1(){
console.log(1)
}
function func2(){
setTimeout(()=>{
console.log(2)
},0)
func1()
console.log(3)
}
func2()
// 1 3 2
当其执行的时候 func2 进入调用栈 首先 会将settimeout中的2 放入 消息队列 接着执行 func1() 输出1 接着 输出 3 最后再执行消息队列里的 2 最终结果为 1 3 2
var p=new Promise(reslove=>{
console.log(4);
reslove(5)
})
function func1(){
console.log(1);
}
function func2(){
setTimeout(() => {
console.log(2);
}, 0);
func1()
console.log(3);
p.then(reslove=>{
console.log(reslove);
})
}
func2()
js中的宏任务和微任务
事件循环EventLoop
https://i.postimg.cc/SxwK8C8Q/120.jpg
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->...
宏任务包含:
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务
microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包含:
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
如图:
局部作用域
let const 区别 let 声明的变量会产生块级的作用域,var 不会产生作用域,const声明的常量也会产生块作用域
箭头函数 参数
普通函数没有arguments动态参数 箭头函数没有arguments动态参数,但是有剩余参数 ...args
const getSum(...arr)=>{
let sum=0;
for(let i=0;i<arr.length;i++){
sum+=arr[i]
}
return sum;
}
console.log(1,2,3);
箭头函数
在箭头函数出现之前,每一个新函数会根据它是被如何调用的来定义这个函数的this值, 但是箭头函数不会创建自己的this,它只从自己的作用域的上一层沿用this
在开发中,使用箭头函数前需要考虑函数中的this的值,事件回调函数使用箭头函数时,this为全局的window, 因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数。
2种情况 需要加分号 ;
// 1. 自执行函数
let uname='阿飞'
;(function(){}) ()
//2.数组解构
let a=1;
let b=2;
let uname='dae'
[b,a]=[a,b];
局部作用域:函数内部
块级作用域:大括号内部
全局作用域:函数外部
作用域链: 作用域串联起来的结构 作用: 提供查找变量的机制
预解析
代码执行之前先要解析,
变量: 把变量的声明语法提升到作用域的最前面,只声明不赋值 (var)
函数: 把函数的声明语法提升到当前作用域的最前面,只声明不调用
JS垃圾回收机制: 如果内存不用就会回收清除
闭包
内层函数使用外层函数的变量
function fn(){
let num=123;
return function(){
num++;
}
}
//防止变量污染 延申范围 原本只能在内部使用的 现在可以在外部使用,
缺点: 如果大量非正常使用 会引起内存泄漏
函数进阶
动态参数: arguments
剩余参数: ... 当其接受值的时候 则称为剩余参数 展示值的时候称为 展开运算符
箭头函数: let fn=(uname)=>({uname:uname});
解构赋值
1.数组解构
变量的数量大于单元值时,多余的变量将被赋值为undefined
变量的数量小于单元值时,可以通过什么获取剩余所有的值 答: 剩余参数...获取剩余单元值,但只能置于最末位
按需取值
const [,a,,,b]=['1','2','3',4,'5','6'];
console.log(a,b);
2.对象解构
如果对象中不存在这个属性,那么默认的就是undefined
也可是剩余值
冒号改变量名
const pig = [{ name: '佩奇', age: 6 }];
const [{ name: uname, age }] = pig;
console.log('冒号改名字--------------', uname);
创建对象的三种方式
1.字面量创建对象
属性名: 属性值
键值对 成员:组成对象的成员
对象中的成员是无序列的
属性: 对象.属性 用于固定属性的访问
对象['属性'] : 用于动态属性的访问
const obj = {
uname: 'dada',
age: 22,
say: function() {
console.log('say hi ');
}
}
for (let key in obj) {
// 变量表示对象的值
console.log(obj[key]);
}
2.利用new Object创建对象
const o=new Object({name:'佩奇'})
console.log(o);
3.利用构造函数创建对象
function Person(uname, age, sex) {
this.uname = uname;
this.age = age;
this.sex = sex;
}
// 实例化对象
const o1 = new Person('张三', 22, '男');
console.log(o1);
构造函数的作用是什么?怎么写尼? 构造函数是来快速的创建多个类似的对象 大写字母开头的函数
new关键字调用函数的行为被称为?实例化
构造函数内部需要写return嘛? 返回值是什么? 不需要 构造函数自动返回创建的对象
new的执行过程
1.立刻创建对象 2.this指向这个对象 3.执行函数内的代码(添加属性、方法) 4.返回这个对象
实例成员: 实例对象具有的属性和方法即为实例成员 只有实例化对象才可以使用实例成员 (实例属性和实例方法)
静态成员: 构造函数身上的属性和方法,称为静态成员 只有构造函数才可以使用静态成员
内置构造函数
Object、Array、String、Number
Object
内置的构造函数,用于创建普通对象
Object.values:获取对象所有的值
Object.keys: 获取对象所有键
Object.assign(新,旧) 浅拷贝
let obj = {
uname: '字典打',
age: 12,
gender: '男',
index: 3,
score: 39
}
// let o = {};
// const re = Object.values(obj);
// const rs = Object.keys(obj);
// // Object.assign(新,旧) 浅拷贝
// Object.assign(o, obj);
// o.uname = 'wawa';
// console.log(o, obj);
let newObj = {
language: '汉语',
skin: '黄皮肤'
}
Object.assign(newObj, obj);
console.log(newObj);
Array
String
面向对象编程
封装性、继承性、多态性、
面向过程
优点:性能比面向对象高,适合跟硬件联系密切的东西,
缺点:没有面向对象易维护、易复用、易扩展。
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加的灵活、易于维护。
缺点:性能比面向对象过程低
构造函数 存在浪费内存的问题
原型
每一个构造函数都有一个prototype属性,指向另一个对象,所以我们称为原型对象
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存,
原型是什么?
原型函数是构造函数的属性 这个属性指向一个对象(原型对象)
原型对象的作用?
共享方法、可以把那些不变的方法,直接定义在prototype对象上
构造函数和原型对象里的this指向谁?
实例化对象
数组扩展
const arr = [1, 2, 3, 24];
// Array.prototype.max = function() {
// return Math.max(...this)
// // 不支持直接放数组 使用...进行扩展
// }
// console.log(arr.max());
// 利用数组扩展实现 伪数组转换真
// let o = {
// 0: 'a',
// 1: 'b',
// 2: 'c',
// 3: 'd',
// 4: 'e',
// length: 5
// };
// const ars = Array.from(o);
Array.prototype.sum = function() {
return this.reduce((pre, cur) => pre + cur, 0);
}
console.log(arr.sum());
constructor
每个原型对象里面都有一个属性,constructor
作用: 该属性指向该原型对象的构造函数,
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但这样会覆盖构造函数原型对象原来的内容,这样子修改后的原型对象constructor就不再指向当前构造函数了,此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数。
每个构造函数都有一个属性prototype指向对象,每个原型对象都有一个constructor指向构造函数
function person(uname) {
this.uname = uname;
}
person.prototype = {
// 为了防止原型对象被覆盖了,加一个constructor指向构造函数
constructor: person,
eat: function() { console.log('eat'); },
sing: function() { console.log('sing'); },
dance: function() { console.log('dance'); },
say: function() { console.log('say'); },
}
console.log(person.prototype);
// console.log(person.prototype.constructor); //ƒ Object() { [native code] }
对象原型 _ _proto _ _
对象都会有一个属性 _ proto _ 指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有一个 _ proto _ 原型的存在。
1.每个构造函数都有一个属性,
prototype,指向了一个对象,称为原型对象
作用:共享方法
2.每个原型对象都有一个属性:constructor 用于指向构造函数
3.每个对象都有一个属性:_ proto _
用来指向原型对象: 非标准属性
每个构造函数都有一个属性prototype指向对象,每个原型对象都有一个constructor指向构造函数
实例化对象如果找成员找不到,它就会通过 _ proto _指向原型对象去寻找。
原型继承
继承是面向对象的另一种特征,通过继承进一步提升代码封装的程度,js中大多数是借助原型对象来实现继承的特性。
// let p = {
// head: 1,
// skin: 2,
// eyes: 2
// }
//把公共的方法封装到person的函数中, Man使用的化,则将其
function Person() {
this.head = 1;
this.skin = 2;
this.eyes = 2;
}
function Man() {
this.language = '汉语';
}
// 原型
// Man.prototype = p;
Man.prototype = new Person();
// 添加constructor
Man.prototype.constructor = Man;
const c1 = new Man();
Man.prototype.smoking = function() {}
console.log(c1);
function English() {
this.language = 'English';
}
// 继承 把公共的构造函数(父类)的实例化对象赋值给 要继承的构造函数(子类)的原型继承
English.prototype = new Person();
English.prototype.constructor = English;
const e1 = new English();
console.log(e1);
原型链
原型链: 原型串联起来的链状结构
作用:提出查找成员的机制
原项链——查找规则
当访问一个对象的属性(包括方法)时,首先会查找这个对象自身有没有该属性。
如果没有就查找它的原型(__proto__指向的prototype原型对象).
如果还没有就查找原型对象的原型(Object的原型对象)
依此类推直到找到Object为止(null)
__proto__对象的原型的意义就在于为对象成员查找机制提供了一个方向,或者说一条路线
可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上.
// 构造函数
function Star() {}
// 实例化对象
const ee = new Star();
Object.prototype.basketball = function() { console.log('Object篮球'); }
Star.prototype.basketball = function() { console.log('star篮球'); }
ee.basketball();
console.log(ee);
console.log(String instanceof Object);
拷贝
直接赋值和浅拷贝的区别
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响。
浅拷贝怎么理解?
拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
如果属性值是引用数据类型则拷贝的是地址
深拷贝
浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
方法:1.通过递归实现深拷贝 2.lodash/closeDeep 3.通过JSON.stringify()实现
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数.
递归函数的作用和循环效果类似
由于递归很容易发生 “栈溢出”(stack overflow),所以必须要加退出条件return
const obj = {
uname: '阿飞',
age: 22,
gender: '男',
color: ['red', 'blue', 'yellow', 'pink'],
message: {
score: 99,
index: 6,
}
}
const o = {};
// 利用递归
function CloneDeep(newObj, oldObj) {
for (let key in oldObj) {
// oldObj[key]
if (oldObj[key] instanceof Array) {
newObj[key] = [];
CloneDeep(newObj[key], oldObj[key]);
} else if (oldObj[key] instanceof Object) {
newObj[key] = {};
CloneDeep(newObj[key], oldObj[key]);
} else {
// debugger
newObj[key] = oldObj[key];
}
}
}
CloneDeep(o, obj);
obj.message.index = 212;
console.log(o, obj);
异常处理
throw 提升代码的健壮性
throw抛出异常之后,程序会终止执行
Error对象经常配合throw使用
function fn() {
try {
const p = document.querySelector('.p');
p.style.background = 'red';
} catch (error) {
throw new Error('初三的你');
// console.log(error.message);
} finally {
console.log('demoe');
}
}
fn()
箭头函数
箭头函数中的this指向与普通函数的不同,也不受调用方式的影响,事实上箭头函数中并不存在this!
1.箭头函数会默认帮我们绑定外层的this的值,所以在箭头函数中this的值和外层的this是一样的
2.箭头函数中的this引用的就是最近作用域中的this
3.向外层作用域中,一层一层的查找this,直到有this的定义。
函数内不存在this,沿用上一级的, 不适用 (构造函数、原型函数、dom事件函数等)
适用 需要使用上层this的地方
改变this指向 call\apply\bind()
call
// call fn.call(thisArgs,args1,args2,args3) 使用call方法调用函数,同时指定被调用函数中this的值
// thisArgs 在fn函数运行时指定的this的值
function fn() {
console.log(this); //调用者
}
const obj = { uname: '阿飞', age: 22 };
fn.call(obj) //指向obj
// call 作用: 调用函数,并可以改变调用函数里面的this指向,call里面的第一个参数是this,其余是实参,传递参数 fn.call(obj,1,2)
apply
/**
* 使用apply方法调用函数,同时指定被调用函数中的this的值
* fn.apply(thisArgs,[argsArray])
* thisArgs:在fn函数运行时指定的this的值
* argsArray:传递的值,必须包含在数组里面
* 返回值就是函数的返回值,因为它就是调用函数
* apply主要跟数组有关系,比如使用Math.max()求数组的最大值
*/
function fn(a, b) {
console.log(this, a, b);
}
// fn();
const obj = { uame: 'wa1', age: 22 };
fn.apply(obj, [12, 22]); //指向调用者 obj
const arr = [2, 6, 8, 4, 3, 7];
console.log(Math.max.apply(null, arr)); //等同于Math.max(...arr)
bind
/***
* bind()方法不会调用函数,但是可以改变函数内部this指向
* fun.bind(thisArgs,arg1,arg2,...)
* 枫丹林
* thisArgs:在fun函数运行时指定的this值
* arg1,arg2:传递的其他参数
* 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
* 因此当我们只是想改变this指向,并且不想调用这个函数的时候(不想让它立刻执行),可以使用bind,比如改变定时器内部的this指向。
*/
// function soudo() {
// console.log(this);
// }
// const obj = { uname: 'obj1', age: 122 };
// const re = soudo.bind(obj)();
// re();
// --------------------bind的使用------------------------
// 想要改变this,但是不希望立刻指向函数的时候用Build
document.querySelector('input').addEventListener('click', function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false;
}.bind(this), 3000)
})
性能优化-节流
节流和防抖的区别是?
节流:连续触发事件但是在n秒中只执行一次函数,比如可以利用节流实现1s之内 只触发一次鼠标移动事件
防抖:如果在n秒内又触发事件,则会重新计算函数执行时间
2.节流和防抖的使用场景是?
节流:鼠标移动,页面尺寸发生变化,滚动条滚动等开销比较大的时候
防抖:搜索框输入,设定每次输入完毕n秒后发送请求,如果期间还有输入,则重新计算时间
内层函数用到外层函数的变量:闭包
Map 和Set的区别
Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。
Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。
Set是以"[value,value]"的形式储存元素,Map是以"[key,value]"的形式储存。
Map可用get()通过键查找特定值并返回,而set不行。
Set 本身是一种构造函数,用来生成 Set 数据结构。
Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用。
Map本质上是键值对的集合,类似集合,可以遍历(forEach),方法很多可以跟各种数据格式转换。
区别:1、set指的是“集合”结构,而Map指的是“字典”结构;2、set是以“[value, value]”的形式储存元素,而Map是以“[key, value]”的形式储存;3、Map可用get()通过键查找特定值并返回,而set不行。
- Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。
- Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。
集合 与 字典 的区别:
- 共同点:集合、字典 可以储存不重复的值
- 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存
集合(Set):
ES6 新增的一种新的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。
Set 本身是一种构造函数,用来生成 Set 数据结构。
Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用。
const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))
for (let i of s) {
console.log(i) // 1 2 3 4
}
// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)] // [1, 2, 3]
操作方法:
- add(value):新增,相当于 array里的push。
- delete(value):存在即删除集合中value。
- has(value):判断集合中是否存在 value。
- clear():清空集合。
遍历方法:遍历方法(遍历顺序为插入顺序)
- keys():返回一个包含集合中所有键的迭代器。
- values():返回一个包含集合中所有值得迭代器。
- entries():返回一个包含Set对象中所有元素得键值对迭代器。
- forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。
字典(Map):
是一组键值对的结构,具有极快的查找速度。
const m = ``new` `Map()`
`const o = {p: ``'haha'``}`
`m.set(o, ``'content'``)`
`m.get(o) ``// content`
`m.has(o) ``// true`
`m.``delete``(o) ``// true`
`m.has(o) ``// false
操作方法:
- set(key, value):向字典中添加新元素。
- get(key):通过键查找特定的数值并返回。
- has(key):判断字典中是否存在键key。
- delete(key):通过键 key 从字典中移除对应的数据。
- clear():将这个字典中的所有元素删除。
遍历方法:
- Keys():将字典中包含的所有键名以迭代器形式返回。
- values():将字典中包含的所有数值以迭代器形式返回。
- entries():返回所有成员的迭代器。
- forEach():遍历字典的所有成员。
总结:
Set:
- 指的是“集合”结构
- [value, value],键值与键名是一致的(或者说只有键值,没有键名)。
- 不能通过键查找特定值
Map:
- 指的是“字典”结构
- [key, value],键值与键名是不一致的
- Map可用get()通过键查找特定值并返回
js基础
变量和类型
- js规定了几种语言类型 7种 number、string、Boolean、null、undefined、Symbol、Object
- js对象的底层数据结构是什么?
3.symbol
symbol可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。
var a={ [mySymbol]:'hello'}; 使用同一个symbol值,可以使用Symbol.for
-
js中的变量在内存中的具体存储形式
js的数据类型分为简单数据类型和复杂数据类型,在内存中,简单数据类型以固定的大小存储在栈中,复杂数据类型存储在堆中,且大小不固定,同时在栈中会存储其指向堆地址的指针。
-
基本类型对应的内置对象,以及它们之间的装箱拆箱操作
Boolean、number、string 特殊的引用类型 ,每当读取一个基本类型值的时候,后台会创建一个对应的基本包装类型对象,从而能够去调用一些方法来操作这些基本类型。
装箱: 就是把基本类型转换为对应的内置对象,分为隐式和显式装箱。
-
理解值类型和引用类型
引用类型的,我们可以为其添加属性和方法,对于基本类型的值,我们不能为其动态的添加属性。
复制变量的值 基本类型的值 是存在栈中,当一个变量向另一个变量复制时,就会重新创建一个变量的新值然后将其复制到为新变量分配的位置上,此时两个变量各自拥有独立的空间,互不影响。
引用类型存储在堆中,同时在栈中会有相应的堆地址,指向其在的堆的位置。如果我们要复制一个引用类型时,复制的不是堆内存中的值,而是将栈内存中的地址复制过去,两个对象实际上指向了堆中的同一个地方。因此,改变其中一个就会影响到另一个对象。
-
null undefined区别
两者在做全等比较时 不相等 null :object类型 代表“空值” 代表一个空对象指针
undefined: undefined类型
1、undefined不是关键字,而null是关键字;
var undefined="" //undefined
var null="" //会报错
2、undefined和null被转换为布尔值的时候,两者都为false;
3、undefined在和null进行==比较时两者相等,全等于比较时两者不等
4、使用Number()对undefined和null进行类型转换时前者为NaN,后者为0
5、undefined本质上是window的一个属性,而null是一个对象;
display:block 设置元素显示的方式,block是以块状元素的方式显示,单独占一行
vue的生命周期
创建前、后 beforeCreate生命周期函数执行的时候,data和method还没有初始化
在created执行时,data 和method初始化完成。
渲染前、后 : beforeMount 执行时,编译好了模板字符串,但没有真正的渲染到页面中
Mounted 已经渲染完成,可以看到页面
数据更新前、后
beforeUpdate 已经拿到最新的数据,但还没有渲染到视图。
updated 更新后 渲染到了视图中
销毁前、后: beforeDestroy 实例准备销毁,data method 指令 还是可用状态
destroyed 实例完成销毁,data method 指令不可用
vue双向数据绑定的原理
通过object.defineProperty属性,重写data的set和get函数来实现的
vue通过数据劫持的方式来做数据绑定,最核心的是通过Object.defineProperty()来实现对属性的劫持 ,
在设置或者获取的时候我们可以在get和set方法里加入其他触发函数,达到监听数据变动的目的
MVVM
Model:对应data中的数据 V :view 模板 VM (ViewModel)视图模型 Vue 实例对象
data中的所有属性,最后都出现在了vm身上。 vm身上所有的属性及Vue原型上所有属性。在vue模板中都可以直接使用。


浙公网安备 33010602011771号