大家好,我是梅巴哥er
。本篇继续努力,总结知识点。
1,问:数组有哪些方法
答:
增加元素的: push, unshift, splice
删除元素的:pop, shift
查元素的:find, indexOf, includes, some, filter
判断数组的:Array.isArray()
转化成数组的:Array.from, Array.of, split
排序的:sort
拉平数组的:flat
遍历数组的:map, forEach, for
数组反转的:reverse
数组累加的:reduce
2,手写代码:用reduce实现map
Array.prototype.myMap = function(currentValue,thisArg){
let arr = []
thisArg = thisArg || []
this.reduce((pre,curr,index) => {
arr.push(currentValue.call(thisArg,curr,index))
},[])
return arr;
}
// 举例
var arr = [1, 2, 3]
arr.myMap(v => console.log(v))// 1 2 3
3,问:什么是事件流?添加事件监听的方法有哪些?什么是事件委托(代理)?
答:
- 事件流
- 当html元素产生一个事件时,这个事件会沿着元素节点和根节点之间的路径传播,路径所经过的节点都会收到该事件。这个传播过程,就叫事件流。
- 分为3个阶段
- 捕获阶段
- 目标阶段
- 冒泡阶段
- 添加事件监听的方法
- DOM0:节点.on事件名 = function(){}
- DOM2:节点.addEventListener(‘事件名’, function(){})
- 事件委托(也叫事件代理、事件委派)
- 把子节点的事件,绑定在父节点上,当子节点触发事件时,父节点可以通过冒泡原理接收到该事件,然后对事件进行监听和处理。
4,问:什么是宏任务、微任务?
答:
- JS是单线程的,当任务挂起时,任务会分为两类,即同步任务和异步任务。
- 执行时,会先执行主线程的同步任务,然后会读取放在任务队列里的异步任务。有要执行的异步任务,就放进主线程里执行。然后循环这个步骤(这就叫事件循环)
- 在读取和执行任务队列里的异步任务时,又把异步任务分为宏任务和微任务。按照各自的顺序来读取和执行。
- 执行方法是:先执行一个宏任务,执行完后,会检查有没有微任务要执行。有,则执行完微任务,再去执行下一个宏任务。没有,则跳到下一个宏任务,继续执行。
- 宏任务有哪些:
- 同步代码, I/O,setTimeout,setInterval, setImmediate, requestAnimationFrame
- 微任务有哪些:
- process.nextTick, Promise.then catch finally
- 宏任务优先级:
- 主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
- 微任务优先级:
- process.nextTick > Promise = MutationObserver
// 看下该代码块的输出结果
//主线程直接执行
console.log('1');
//丢到宏事件队列中
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
//微事件1
process.nextTick(function() {
console.log('6');
})
//主线程直接执行
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
//微事件2
console.log('8')
})
//丢到宏事件队列中
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
5,手撕代码:写个斐波那契数列
function fn(a) {
if(a === 1) {
return 0
} else if(a === 2) {
return 1
} else {
return fn(a-2) + fn(a-1)
}
}
for(var i = 1; i < 10; i++) {
console.log(fn(i))
}
// 0 1 1 2 3 5 8 13 21
6,手撕代码:写个希尔排序
7,手撕代码:js类的继承方式,说明区别和优缺点
答:
- 原型继承
- call继承(基本继承)
- 组合继承(原型+call)
- 寄生继承
- 寄生组合继承
- class类继承
// 1,原型继承
function Star(age) {
this.name = 'dilireba'
this.age = age
this.art = ['sing', 'dance']
}
Star.prototype.getName = function () {
console.log(this.name)
}
function Dili(age) {
this.age = age
}
Dili.prototype = new Star('18') //区分1:Star调用了几次
var dili1 = new Dili('20') // 区分2:子类实例能不能给父类传参数
var dili2 = new Dili('22')
dili1.getName() // 区分3:子类实例是否能调用父类原型上的属性和方法
console.log(dili1.age, dili2.age)
dili1.art.push('show') // 区分4:子类实例调用父类引用数据类型时,数据是否共享
console.log(dili1.art)
console.log(dili2.art)
console.log(dili1 instanceof Star) // 区分5:子类实例是否为父类的实例
// 2,call继承
function Star(age) {
this.name = 'dilireba'
this.age = age
this.art = ['sing', 'dance']
}
Star.prototype.getName = function () {
console.log(this.name)
}
function Dili(age) {
Star.call(this, age)
this.age = age
}
// Dili.prototype = new Star('18') //区分1:Star调用了几次
var dili1 = new Dili('20') // 区分2:子类实例能不能给父类传参数
var dili2 = new Dili('22')
// dili1.getName() // 区分3:子类实例是否能调用父类原型上的属性和方法
console.log(dili1.age, dili2.age)
dili1.art.push('show') // 区分4:子类实例调用父类引用数据类型时,数据是否共享
console.log(dili1.art)
console.log(dili2.art)
console.log(dili1 instanceof Star) // 区分5:子类实例是否为父类的实例
// 3,原型+call组合继承
function Star(age) {
this.name = 'dilireba'
this.age = age
this.art = ['sing', 'dance']
}
Star.prototype.getName = function () {
console.log(this.name)
}
function Dili(age) {
Star.call(this, age)
this.age = age
}
Dili.prototype = new Star('18') //区分1:Star调用了几次
var dili1 = new Dili('20') // 区分2:子类实例能不能给父类传参数
var dili2 = new Dili('22')
dili1.getName() // 区分3:子类实例是否能调用父类原型上的属性和方法
console.log(dili1.age, dili2.age)
dili1.art.push('show') // 区分4:子类实例调用父类引用数据类型时,数据是否共享
console.log(dili1.art)
console.log(dili2.art)
console.log(dili1 instanceof Star) // 区分5:子类实例是否为父类的实例
// 4,寄生继承(对象拷贝继承)
// 就是拷贝一份父对象,继承父对象的属性和方法,
// 然后往生成的新对象里,可以添加属性和方法,最终return新拷贝的对象
let Star = {
name: 'dilireba',
art: ['sing', 'dance'],
getName: function() {
console.log(this.name)
},
}
function Dili(par) {
let copy = Object.create(par)
// console.log(copy)
copy.getArt = function() {
console.log(this.art)
}
return copy
}
let dili1 = Dili(Star)
let dili2 = Dili(Star)
console.log(dili1.name, dili2.name)
dili1.art.push('show')
console.log(dili1.art, dili2.art)
dili1.getArt()
dili1.getName()
// 优点是可以添加更多属性和方法
// 缺点和原型一样,而且这个没有实例化,没有用到原型
// 5,寄生组合继承(原型拷贝 + call)
// 目前的最优解
function Star(age) {
this.name = 'dilireba'
this.age = age
this.art = ['sing', 'dance']
}
Star.prototype.getName = function() {
console.log(this.name)
}
function Dili(age) {
Star.call(this, age)
this.age = age
}
Dili.prototype = Object.create(Star.prototype)
// 注意这里要修改指向,
// 因为在JS里,原型的构造函数是要指向他自己的
// 如果不修改,就会指向父类了,不符合JS规则
Dili.prototype.constructor = Dili
var dili1 = new Dili('20')
var dili2 = new Dili('22')
dili1.getName()
console.log(dili1.age, dili2.age)
dili1.art.push('show')
console.log(dili1.art, dili2.art)
console.log(dili1 instanceof Star)
// 6,class类的extens继承
// 该方法经过Babel编译后,也是用的寄生组合继承方式
8,问:说说JS事件循环(Event Loop)
答:
- JS是单线程的脚本语言
- 主要用途是与用户实现交互和操作DOM
- 执行任务时,所有的任务被分为两种:同步任务和异步任务
- 同步任务:就是在主线程上排队执行的任务,前一个任务执行完毕,才能执行下一个任务
- 异步任务:就是不进入主线程,而是进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程里执行。
- 机制如下:
- 如果是同步任务,就在主线程中执行,形成一个执行栈
- 主线程之外,还有一个任务队列,用来放异步任务
- 当同步任务执行完后,就会检查任务队列。看下任务队列里有哪些异步任务要执行,如果有,就读取该任务,放进主线程里执行。
- 重复上面的第三个步骤,不断检查、读取和执行。
9,手撕代码:异步发起1000次请求,多线程进行,怎么写?
10,问:map和set区别
答:
- Set 和 Map 主要的应用场景在于 数据重组 和 数据储存,都可以遍历,都有增删查清的方法。
- set里面都是值,不重复且无序,有add, delete, has ,clear等方法
- map里面是键值对形式,有set, get, has, delet, clear等方法
11,问:js基本数据类型
答: string, number, boolean, null, undefined, symbol
12,问:Object.defineProperty()
答:
Object.defineProperty()
用于给对象增加或修改属性。- 语法:
Object.defineProperty(obj, attr, {
value: , // 该属性对应的值
writable: , // 值是否能改变
enumerable: , // 该属性是否允许被遍历
configurable: , // 以上的描述符是否能被修改,值是否能删除
get() {...}, // 获取属性时触发的函数
set() {...}, // 设置属性时触发的函数
})
13,问:js中变量提升是什么,有哪些危害,以及const let和var区别
答:
- 在ES5中,变量的声明会被提升到当前作用域的顶部。变量可以先使用,后声明。
// 例1
console.log(x) // x未声明,会报错
// 例2
console.log(x) // 会输出undefined,不会报错
var x // 变量声明被提升到顶部
// 例3
x = 1
console.log(x) // 输出1 .变量可以先使用,后声明
var x
// 例4
console.log(fn()) // 输出1 .声明式函数的提升是整个函数体被提升
function fn() {
return 1
}
// 例5
fn2() // 报错,只提升了变量,没提升函数体。执行时不知道fn2是函数
var fn2 = function () {
console.log(2)
}
// 来道题吧
var a = 0;
if (true) {
a = 1;
function a() {};
a = 21;
console.log(a)
}
console.log(a);
// 21 1
- var 声明的变量,会有变量提升,可以先使用后声明,可以重复声明,声明的值可以改变,有全局变量和局部变量,没有块级作用域
- let 没有变量提升,必须先声明后使用,不能用let重复声明,声明的值可以更改,会形成块级作用域
- const 和let基本一致,但是声明后的值不能改变,声明的数组和对象里的值是能改变的
14,手撕代码:css垂直居中
答:
// 不知道父盒子宽高的情况下,说几种方法吧
// flex
父盒子: display: flex;
align-items: center;
// margin + transform
子盒子:margin: 50% auto;
transform: translateY(-50%);
// postion + transform
父盒子:position: relative;
子盒子:position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
15,虚拟DOM
答:
虚拟DOM是什么
- 是用JS模拟DOM树的一种树形结构,它包含了整个DOM树的结构信息
虚拟DOM是用来做什么的
- 减少对真实DOM的操作,部分DOM的变化不再需要重新渲染整个页面,从而优化性能
虚拟DOM的原理是什么
- 1,用JS对象结构来表示真实DOM树结构,
- 2,在状态变更时,重新构造一棵新的DOM树。用新树对象结构和老树的对象结构做比较,记录两棵树的差异
- 3,把差异更新到老树上,老树对象结构插入页面,更新视图
16,问:前端网页渲染流程是怎样的
答:
浏览器拿到html文件后,开始渲染:
- 根据html文件,构建DOM树和CSSOM树。构建DOM树期间,如果遇到JS代码,阻塞DOM树和CSSOM树的构建,优先加载JS文件。JS文件加载完毕后,再继续构建DOM树和CSSOM树。
- 构建渲染树(Render Tree)
- 页面的重排重绘。页面渲染完成后,若JS操作了DOM节点,浏览器对页面进行重排或重绘。
以上。