箭头函数和普通函数有什么区别?
代码语言:javascript复制(1)箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常用的就是调用一个函数:
let fn = () => void doesNotReturn()
(2) 箭头函数没有自己的this
箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中的this的指向在它在定义时一家确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
(4) call()、apply()、bind()等方法不能改变箭头函数中的this指向
(5) 箭头函数不能作为构造函数使用
(6) 箭头函数没有自己的arguments
(7) 箭头函数没有prototype
(8) 箭头函数不能用作Generator函数,不能使用yeild关键字
position的属性有哪些,区别是什么
position有以下属性值:
属性值 | 概述 |
---|---|
absolute | 生成绝对定位的元素,相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。 |
relative | 生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。 |
fixed | 生成绝对定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。 |
static | 默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。 |
inherit | 规定从父元素继承position属性的值 |
前面三者的定位方式如下:
- relative: 元素的定位永远是相对于元素自身位置的,和其他元素没关系,也不会影响其他元素。
- fixed: 元素的定位是相对于 window (或者 iframe)边界的,和其他元素没有关系。但是它具有破坏性,会导致其他元素位置的变化。
- absolute: 元素的定位相对于前两者要复杂许多。如果为 absolute 设置了 top、left,浏览器会根据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了
position:relative/absolute/fixed
的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。如下两个图所示:
v-model语法糖是怎么实现的
代码语言:html复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- v-model 只是语法糖而已 -->
<!-- v-model 在内部为不同的输入元素使用不同的property并抛出不同的事件 -->
<!-- text和textarea 元素使用value property 和 input事件 -->
<!-- checkbox 和radio使用checked property 和 change事件-->
<!-- select 字段将value 作为prop 并将change 作为事件 -->
<!-- 注意:对于需要使用输入法(如中文、日文、韩文等)的语言,你将会发现v-model不会再输入法 组合文字过程中得到更新 -->
<!-- 再普通标签上 -->
<input v-model="sth" /> //这一行等于下一行
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />
<!-- 再组件上 -->
<currency-input v-model="price"></currentcy-input>
<!--上行代码是下行的语法糖 <currency-input :value="price" @input="price = arguments[0]"></currency-input> -->
<!-- 子组件定义 -->
Vue.component('currency-input', {
template: `
<span>
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</span>
`,
props: ['value'],
})
</body>
</html>
localStorage sessionStorage cookies 有什么区别?
代码语言:javascript复制localStorage:以键值对的方式存储 储存时间没有限制 永久生效 除非自己删除记录
sessionStorage:当页面关闭后被清理与其他相比不能同源窗口共享 是会话级别的存储方式
cookies 数据不能超过4k 同时因为每次http请求都会携带cookie 所有cookie只适合保存很小的数据 如会话标识
如何⽤webpack来优化前端性能?
⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。
- 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
- 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
- Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
- Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
- 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
Set 和 Map有什么区别?
代码语言:javascript复制1、Map是键值对,Set是值得集合,当然键和值可以是任何得值
2、Map可以通过get方法获取值,而set不能因为它只有值
3、都能通过迭代器进行for...of 遍历
4、Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储
代码输出问题
代码语言:javascript复制function A(){
}
function B(a){
this.a = a;
}
function C(a){
if(a){
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);
输出结果:1 undefined 2
解析:
- console.log(new A().a),new A()为构造函数创建的对象,本身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输出值为1;
- console.log(new B().a),ew B()为构造函数创建的对象,该构造函数有参数a,但该对象没有传参,故该输出值为undefined;
- console.log(new C(2).a),new C()为构造函数创建的对象,该构造函数有参数a,且传的实参为2,执行函数内部,发现if为真,执行this.a = 2,故属性a的值为2。
常见的水平垂直方式有几种?
代码语言:html复制//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 translate 来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
//利用绝对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 margin 负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}
//使用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要**考虑兼容的问题**,该方法在移动端用的较多:
.parent {
display: flex;
justify-content:center;
align-items:center;
}
//另外,如果父元素设置了flex布局,只需要给子元素加上`margin:auto;`就可以实现垂直居中布局
.parent{
display:flex;
}
.child{
margin: auto;
}
手写题:数组去重
代码语言:javascript复制Array.from(new Set([1, 1, 2, 2]))
compose
题目描述:实现一个 compose 函数
代码语言:javascript复制// 用法如下:
function fn1(x) {
return x 1;
}
function fn2(x) {
return x 2;
}
function fn3(x) {
return x 3;
}
function fn4(x) {
return x 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1 4 3 2 1=11
实现代码如下:
代码语言:javascript复制function compose(...fn) {
if (!fn.length) return (v) => v;
if (fn.length === 1) return fn[0];
return fn.reduce(
(pre, cur) =>
(...args) =>
pre(cur(...args))
);
}
实现模板字符串解析
描述:实现函数使得将 template
字符串中的{{}}
内的变量替换。
核心:使用字符串替换方法 str.replace(regexp|substr, newSubStr|function)
,使用正则匹配代换字符串。
实现:
代码语言:javascript复制function render(template, data) {
// 模板字符串正则 /{{(w )}}/, 加 g 为全局匹配模式, 每次匹配都会调用后面的函数
let computed = template.replace(/{{(w )}}/g, function(match, key) {
// match: 匹配的子串; key:括号匹配的字符串
return data[key];
});
return computed;
}
// 测试
let template = "我是{{name}},年龄{{age}},性别{{sex}}";
let data = {
name: "张三",
age: 18
}
console.log(render(template, data)); // 我是张三,年龄18,性别undefined
异步任务调度器
描述:实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有 limit
个。
实现:
代码语言:javascript复制class Scheduler {
queue = []; // 用队列保存正在执行的任务
runCount = 0; // 计数正在执行的任务个数
constructor(limit) {
this.maxCount = limit; // 允许并发的最大个数
}
add(time, data){
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(data);
resolve();
}, time);
});
}
this.queue.push(promiseCreator);
// 每次添加的时候都会尝试去执行任务
this.request();
}
request() {
// 队列中还有任务才会被执行
if(this.queue.length && this.runCount < this.maxCount) {
this.runCount ;
// 执行先加入队列的函数
this.queue.shift()().then(() => {
this.runCount--;
// 尝试进行下一次任务
this.request();
});
}
}
}
// 测试
const scheduler = new Scheduler(2);
const addTask = (time, data) => {
scheduler.add(time, data);
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 输出结果 2 3 1 4
代码输出结果
代码语言:javascript复制function foo() {
console.log( this.a );
}
function doFoo() {
foo();
}
var obj = {
a: 1,
doFoo: doFoo
};
var a = 2;
obj.doFoo()
输出结果:2
在Javascript中,this指向函数执行时的当前对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出2。
Promise.resolve
代码语言:javascript复制Promise.resolve = function(value) {
// 1.如果 value 参数是一个 Promise 对象,则原封不动返回该对象
if(value instanceof Promise) return value;
// 2.如果 value 参数是一个具有 then 方法的对象,则将这个对象转为 Promise 对象,并立即执行它的then方法
if(typeof value === "object" && 'then' in value) {
return new Promise((resolve, reject) => {
value.then(resolve, reject);
});
}
// 3.否则返回一个新的 Promise 对象,状态为 fulfilled
return new Promise(resolve => resolve(value));
}
代码输出结果
代码语言:javascript复制async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {
console.log('timer3')
}, 0)
console.log("start")
输出结果如下:
代码语言:javascript复制async1 start
async2
start
async1 end
timer2
timer3
timer1
代码的执行过程如下:
- 首先进入
async1
,打印出async1 start
; - 之后遇到
async2
,进入async2
,遇到定时器timer2
,加入宏任务队列,之后打印async2
; - 由于
async2
阻塞了后面代码的执行,所以执行后面的定时器timer3
,将其加入宏任务队列,之后打印start
; - 然后执行async2后面的代码,打印出
async1 end
,遇到定时器timer1,将其加入宏任务队列; - 最后,宏任务队列有三个任务,先后顺序为
timer2
,timer3
,timer1
,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。
Promise是什么?
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
代码语言:javascript复制const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保存初始化状态
var self = this;
// 初始化状态
this.state = PENDING;
// 用于保存 resolve 或者 rejected 传入的值
this.value = null;
// 用于保存 resolve 的回调函数
this.resolvedCallbacks = [];
// 用于保存 reject 的回调函数
this.rejectedCallbacks = [];
// 状态转变为 resolved 方法
function resolve(value) {
// 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态为 pending 时才能转变,
if (self.state === PENDING) {
// 修改状态
self.state = RESOLVED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 状态转变为 rejected 方法
function reject(value) {
// 保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态为 pending 时才能转变
if (self.state === PENDING) {
// 修改状态
self.state = REJECTED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 将两个方法传入函数执行
try {
fn(resolve, reject);
} catch (e) {
// 遇到错误时,捕获错误,执行 reject 函数
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {
throw error;
};
// 如果是等待状态,则将函数加入对应列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果状态已经凝固,则直接执行对应状态的函数
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
代码输出结果
代码语言:javascript复制function Foo(){
Foo.a = function(){
console.log(1);
}
this.a = function(){
console.log(2)
}
}
Foo.prototype.a = function(){
console.log(3);
}
Foo.a = function(){
console.log(4);
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
输出结果:4 2 1
解析:
- Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4
- let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。
- obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2
- Foo.a() ; 根据第2步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1
代码输出结果
代码语言:javascript复制console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
代码输出结果如下:
代码语言:javascript复制1
4
7
5
2
3
6
代码执行过程如下:
- 首先执行scrip代码,打印出1;
- 遇到第一个定时器setTimeout,将其加入到宏任务队列;
- 遇到Promise,执行里面的同步代码,打印出4,遇到resolve,将其加入到微任务队列;
- 遇到第二个定时器setTimeout,将其加入到红任务队列;
- 执行script代码,打印出7,至此第一轮执行完成;
- 指定微任务队列中的代码,打印出resolve的结果:5;
- 执行宏任务中的第一个定时器setTimeout,首先打印出2,然后遇到 Promise.resolve().then(),将其加入到微任务队列;
- 执行完这个宏任务,就开始执行微任务队列,打印出3;
- 继续执行宏任务队列中的第二个定时器,打印出6。
async/await 如何捕获异常
代码语言:javascript复制async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
两栏布局的实现
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:
- 利用浮动,将左边元素宽度设置为200px,并且设置向左浮动。将右边元素的margin-left设置为200px,宽度设置为auto(默认为auto,撑满整个父元素)。
.outer {
height: 100px;
}
.left {
float: left;
width: 200px;
background: tomato;
}
.right {
margin-left: 200px;
width: auto;
background: gold;
}
- 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置overflow: hidden; 这样右边就触发了BFC,BFC的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。
.left{
width: 100px;
height: 200px;
background: red;
float: left;
}
.right{
height: 300px;
background: blue;
overflow: hidden;
}
- 利用flex布局,将左边元素设置为固定宽度200px,将右边的元素设置为flex:1。
.outer {
display: flex;
height: 100px;
}
.left {
width: 200px;
background: tomato;
}
.right {
flex: 1;
background: gold;
}
- 利用绝对定位,将父级元素设置为相对定位。左边元素设置为absolute定位,并且宽度设置为200px。将右边元素的margin-left的值设置为200px。
.outer {
position: relative;
height: 100px;
}
.left {
position: absolute;
width: 200px;
height: 100px;
background: tomato;
}
.right {
margin-left: 200px;
background: gold;
}
- 利用绝对定位,将父级元素设置为相对定位。左边元素宽度设置为200px,右边元素设置为绝对定位,左边定位为200px,其余方向定位为0。
.outer {
position: relative;
height: 100px;
}
.left {
width: 200px;
background: tomato;
}
.right {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 200px;
background: gold;
}