跳到主要内容
Layer 1

如何动态加载组件?

  1. 引入可能被使用的组件
import slotDemo from './components/slotDemo'
import HelloWorld from './components/HelloWorld'
  1. 通过动态属性is来读取组件名
<component  :is="comName"/>

如何异步加载组件?

  1. 通过import在注册组件的时候引入组件
components: {
FormDemo: () => import(`../xxxComponent`)
}
  1. 通过条件渲染来加载组件
<FormDemo v-if="showFormDemo" />
<button @click="showFormDemo = true" >show form demo</button>

JustinVue阅读需 1 分钟

一、基本使用

由父组件向子组件中传递数据,子组件通过slot进行接收,不传递则显示的是默认的内容。

  • 父组件
<template>
<div id="app">
<slotDemo :url="website.url">
{{website.title}}
</slotDemo>
</div>
</template>

<script>
import slotDemo from './components/slotDemo'

export default {
name: 'App',
data() {
return {
website: {
url: 'https://www.baidu.com',
title: '这是根节点的title'
}
}
},
components: {
slotDemo
}
}
</script>
  • 子组件
<template>
<a :href="url">
<slot>
默认内容
</slot>
</a>
</template>

<script>
export default {
name: 'slotDemo',
props: ['url'],
}
</script>

二、父组件通过slot接收子组件传递过来的值

  1. 子组件通过slot进行动态属性传值
<template>
<a :href="url">
<slot :slotData = "website">
默认内容
</slot>
</a>
</template>

<script>
export default {
name: 'slotDemo',
props: ['url'],
data() {
return {
website: {
url: '这是子组件的URL',
title: '子组件的title'
}
};
}
};
</script>
  1. 父组件通过template v-slot进行接收
<template>
<div id="app">
<slotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</slotDemo>
</div>
</template>

三、具名插槽

image.png


JustinVue阅读需 1 分钟

Vue生命周期的主要阶段

创建前后

  1. beforeCreate:实例刚在内存中被创建出来,此时还没有初始化好data和methods属性。
  2. created:实例已经在内存中创建好,此时data和methods已经创建好,此时还没有开始编译模板。

载入前后

  1. beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面上。
  2. mounted:此时已经编译好模板,并挂载到了页面指定的容器中。

更新前后

  1. beforeUpdate:状态更新之前执行这个函数,此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点。
  2. updated:实例更新完毕之后调用此函数,此时data中的状态值和界面上显示的数据都已经完成了更新,界面已经被重新渲染好了。

销毁前后

  1. beforeDestroy:实例销毁之前调用,在这一步,实例仍然可用。
  2. destroyed阶段:Vue实例销毁后调用,调用后,Vue实例所指向的内容都会解除绑定,所有的事件监听器都会被移除,所有的子实例也会被销毁。

Vue父子组件的生命周期

image.png

Vue不同生命周期函数的作用

image.png

问题汇总

RQ1:Vue虚拟DOM渲染在哪个生命周期?

执行到beforeMount的时候虚拟DOM已经渲染完成,等beforeMount这个钩子函数执行完之后真实DOM已经渲染完成。

RQ2:created和mounted的区别,操作dom是在哪个里面操作?

created在模板渲染成HTML前调用,通常初始化某些属性值,然后再渲染视图。

mounted在模板渲染成HTML之后再调用,通常是初始化页面完成后,对DOM的节点进行操作。

RQ3:发起http请求在哪个钩子? 绑定事件在哪个钩子?取消事件在哪个钩子?

发起HTTP请求一般在created这个钩子函数中,但是如果设计到需要页面加载完成之后调用,则需要在mounted这个钩子中写。

绑定事件在mounted钩子函数中。

取消事件在beforeDestroy钩子函数中。


JustinVue阅读需 2 分钟

一、$nextTick有什么用?

  1. Vue是异步渲染的框架。
  2. data改变之后,DOM不会立刻渲染。
  3. $nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。
  4. 连续多次的异步渲染,$nextTick只会执行最后一次渲染后的结果。

二、$nextTick的原理

\$nextTick主要通过事件循环中的任务队列的方式异步执行传入的回调函数,首先会判断当前的执行环境是否支持Promise,MutationObserver,setImmediate,setTimeout。如果支持则创建对应的异步方法,这里的MutationObserver并不是监听DOM,而是利用其微任务特性。需要注意的是更新DOM的方法也是通过nextTick进行调用的,因此就可以实现传入$.nextTick的回调函数在DOM渲染完成之后执行这些微任务。

三、循环调用的话nextTick里面有容错机制吗?

多次调用 nextTick 会将方法存入队列 callbacks 中,通过这个异步方法清空当前队列。


JustinVue阅读需 1 分钟

一、methods

methods中存放的是事件的回调函数,具有以下几个特点:

  1. 可以通过Vue实例访问方法,在方法中最好不要使用箭头函数,因为涉及到this指向的问题。
  2. 重新渲染的时候,methods总会执行该函数。
  3. methods是函数调用,filters和computed、watch是属性调用。
  4. methods是不具有缓存性的。

二、filters

filters一般用于格式化输出的场景,比如日期格式化,filters过滤器可以进行串联调用,所以可以定义一些基础的filters,然后按需在组件内使用。filters不具有缓存性。

{{msg | fliterA | filterB}}

三、computed

computed是计算属性,会监听一个依赖属性,如果这个属性发生变化,就会更新视图,适用于计算比较消耗性能的计算场景,因为其具有缓存功能,这意味着其监听的数据如果没有发生变化,多次调用计算属性会返回之间的计算结果,不必再次执行函数。

四、watch

watch可以监听某一个数据的变化,watch要想监听对象中某个属性是否发生了变化,需要将deep属性置为true。但是watch无法拿到对象中属性变化的旧值,只能拿到新的对象。

  watch: {
name(newValue, oldValue) {
console.log("name", newValue, oldValue);
},
info: {
handler: function(newValue,oldValue) {
console.log('info',newValue,oldValue);
},
deep: true
}
},

问题汇总

RQ1:watch和computed的区别

  1. watch是监听一个数据是否发生变化,当这个数据发生变化才执行函数。
  2. computed则是只要其函数内部的代码指向的数据发生了变化,其就会执行函数,然后返回。
  3. computed具有缓存特性,watch则没有。

Justin面试题阅读需 2 分钟

题目描述

image.png

解题思路

思路一:反转比较法

回文数的一个特点是正着读和倒着读是一样的,那么我们可以定义一个临时变量来存储目标元素的反转,然后顺序比较每个元素是否相等,相等则返回true,反之false。

var isPalindrome = function(x) {
// 使用反转对比的方法来判断是否是回文数字
x = x.toString();
const temp = x.split('').reverse();
const xArr = x.split('');

for (let i = 0; i < xArr.length; i++) {
if (temp[i] != xArr[i]) {
return false;
}
}
return true;

};

思路二:使用递归比较首尾元素

首先比较首元素和尾元素是否一致,一致则去掉首尾元素,将其余元素记性递归判断。

var isPalindrome = function(x) {
// 使用递归判断
// 递归的介绍条件是输入的x小于等于1
if (x.toString().length <= 1) {
return true;
}
x = x.toString().split('');
let start = 0;
let end = x.length - 1;

if (x[start] === x[end]) {
return isPalindrome(x.slice(1,end).join(''))
} else {
return false;
}
};

总结

判断回文数是一道高频考题,思路也比较简单就是根据回文数的特点出发来进行解题。


JustinLeetCode阅读需 1 分钟

什么是函数柯里化?

函数柯里化主要用于给函数分步传递参数,每次传递参数进行处理,并返回一个更具体的函数来接受剩下的参数,这中间可以嵌套多层,直至返回最后的结果。

实现思路

  1. 接收一个处理数据的函数。
  2. 定义一个数组用于接收所有的参数。
  3. 返回一个函数,如果返回的函数接收的参数的长度是0,则返回fn执行的结果,如果不是0,则将参数push进数组中,并返回函数。

代码实现一(需要额外调用)

// 手写函数柯里化
const curring = function(fn) {
const args = [];
return function result(...rest) {
if (rest.length === 0) {
return fn(...args)
} else {
args.push(...rest);
return result;
}
}
}
const sum = (...arg) => {
return arg.reduce((pre,cur) => {
return pre + cur
},0)
}

curring(sum)(1)(2,5)(3)()

代码实现二(不需要额外调用)

// 手写函数科里化
// foo(1)(2)(3)
const sum = (arg) => {
return arg.reduce((pre,cur) => {
return pre + cur;
},0)
};

const foo = (...args1) => {
const sum1 = sum(args1);
const fn = (...args2) => {
const sum2 = sum(args2);
return foo(sum1 + sum2);
}
fn.toString = () => {
return sum1;
}
return fn;
}
// 这种方式只能使用== 不能使用===
foo(1)(2)(3) == 6

但是这种方式只能使用==,不能使用===


Justin面试题阅读需 2 分钟

方式一:通过constructor

通过constructor可以直接找到元素的构造函数类型,这种方法能够区分引用数据类型到底是哪种类型,请看下面的例子。

const arr = [1,2,3,5];
const date = new Date();
const num = 666;
const map = new Map();
const set = new Set();
const reg = new RegExp();
const str = '111';
const sym = Symbol(66);
const func = function(){}
// 需要注意的是null和undefined没有constructor

console.log(arr.constructor === Array); // true
console.log(date.constructor === Date); // true
console.log(num.constructor === Number); // true
console.log(map.constructor === Map); // true
console.log(set.constructor === Set); // true
console.log(reg.constructor === RegExp); // true
console.log(str.constructor === String); // true
console.log(sym.constructor === Symbol); // true
console.log(func.constructor === Function); // true

需要特别注意的是null和undefined没有constructor属性。

方式二:使用instanceof来判断引用类型到底属于哪种类型

const arr = [1,2,3,5];
const date = new Date();
const map = new Map();
const set = new Set();
const reg = new RegExp();
const func = function(){}

console.log(arr instanceof Array); // true
console.log(date instanceof Date); // true
console.log(map instanceof Map); // true
console.log(set instanceof Set); // true
console.log(reg instanceof RegExp); // true
console.log(func instanceof Function); // true

需要特别注意的是:instanceof 不适用于判断基本类型。

方式三:使用typeof来判断基本数据类型

需要注意的是typeof能够帮助我们判断出基本数据类型和函数,但是引用数据类型一般是object。

const arr = [1,2,3,5];
const date = new Date();
const num = Number(666);
const map = new Map();
const set = new Set();
const reg = new RegExp();
const str = '111';
const sym = Symbol(66);
const func = function(){}
// 需要注意的是null和undefined没有constructor

console.log(typeof arr); // object
console.log(typeof date); // object
console.log(typeof num); // number
console.log(typeof map); // object
console.log(typeof set); // object
console.log(typeof reg); // object
console.log(typeof str); // string
console.log(typeof sym); // symbol
console.log(typeof func); //function

方式四:通过Object.prototype.toString.call()精准确定类型(强烈推荐)

需要注意的是这个方法输出的是一个字符串,这个字符串object是小写的,后面的是大写的。

const arr = [1,2,3,5];
const date = new Date();
const num = Number(666);
const map = new Map();
const set = new Set();
const reg = new RegExp();
const str = '111';
const sym = Symbol(66);
const func = function(){}

console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(date)); // [object Date]
console.log(Object.prototype.toString.call(num)); // [object Number]
console.log(Object.prototype.toString.call(map)); // [object Map]
console.log(Object.prototype.toString.call(set)); // [object Set]
console.log(Object.prototype.toString.call(reg)); // [object RegExp]
console.log(Object.prototype.toString.call(str)); // [object String]
console.log(Object.prototype.toString.call(sym)); // [object Symbol]
console.log(Object.prototype.toString.call(func)); // [object Function]

问题汇总

RQ1:如何判断一个对象是一个空对象?

  1. 通过Reflect.ownKeys的长度为零。Reflect.ownKys()可以返回一个由目标对象自身的属性组成的数组。
const obj = {};

console.log(Reflect.ownKeys(obj).length === 0); // true
  1. 通过JSON.stringify()
const obj = {};

console.log(JSON.stringify(obj) === '{}'); // true

RQ2:有什么方法可以获取对象的key?

  1. 使用Object.keys()
  2. 使用Reflect.ownKeys()

RQ3:对象的中括号运算符和点运算符有什么区别?

中括号中可以用变量,但是点后面不能是一个变量。

const obj = {};

obj.name = '111';
const myName = 'name'
console.log(obj.myName); // undefined
console.log(obj[myName]); // 111

Justin面试题阅读需 3 分钟

前言

vue的组件通信无论是在工作中还是在面试中都是经常考到的知识,这一次让我们一起来系统的梳理下Vue的组件通信都有哪些方式吧~

一、父组件向子组件进行传值

核心:通过props就行传递。

  1. 在父组件中引入子组件。
  2. 通过在子组件的标签上进行传递。
  3. 子组件中通过声明props进行接收。

二、子组件向父组件传值

核心:通过this.$emit('父组件中的函数名',传递参数)

  1. 父组件给子组件绑定函数。
  2. 子组件通过this.$emit出发父组件给子组件绑定的函数。

三、子组件通过$parent来获取父组件实例的属性和方法

核心:在子组件中通过this.$parent.xxx来获取父组件的属性和方法

四、通过\$refs或$children获取子组件的属性或方法

通过$refs获取子组件的属性和方法

  1. 给子组件绑定ref属性。
  2. 通过this.$refs.son.xxx来调用子组件身上的属性和方法。

通过$children获取子组件的属性和方法

\$children属性无需绑定,即可直接使用,\$children获取的是一个数组,有多少个子组件就有多少个元素,\$children[0]代表的是第0个组件。

五、通过\$attrs和\$listeners获取父组件中的属性和方法

核心:主要包括下面的两点:

  1. 通过\$attrs获取父组件通过子组件props形式传递过来的,但是不在子组件props声明接收的属性。
<Son :obj="obj" :msg="msg" />

例如上面这个例子,在子组件中obj被props声明接收了,但是msg并没有声明接收,那么子组件的this.\$attrs这个对象中就包含了msg。

  1. 通过\$listeners可以获取到父作用域中的v-on事件监听器。

注意:this.\$listeners能够获取到的父组件中的事件监听器必须要通过v-on传递给子组件。

六、使用Event Bus实现跨组件通信

  1. 在main.js中对外暴露一个Vue实例
export const eventBus = new Vue()
  1. 在组件1中引入这个eventBus
import {eventBus} from '../main'
  1. 组件1中的数据发生变化的时候通过eventBus.$emit派发数据。
eventBus.$emit('add-age',1)
  1. 组件2在crated生命周期钩子函数中监听事件并修改数据。
created() {
eventBus.$on('add-age',(num) => {
this.ageData += num
})
}

七、父组件通过provide和inject向所有子组件传入数据,不管子组件层次有多深

特点是:父组件有一个provide来提供数据,子组件有一个inject来使用这个数据。

  1. 根组件
  provide: {
text: '叶子节点你好'
}
  1. 叶子组件
inject: ['text'],

对叶子节点来说,此时根组件传过来的值已经在this身上了。


Justin面试题阅读需 3 分钟

什么是BFC?

在系统性的阐述什么是BFC之前,我们首先介绍下CSS中常见的布局。1. 普通流。行内元素排成一行,一行不够则换行,块级元素,一个占一行。2. 浮动。元素会脱离普通流。3. 定位。元素会脱离普通流。BFC也属于普通流,设置为BFC的元素,相当于一种隔离了的元素,容器内部的元素不会在布局上影响外面的元素。

BFC指的是块级格式化上下文,一个元素如果具备了BFC的条件,那么这个元素会形成一个独立的渲染区域,内部元素的渲染不会影响外界。

如何触发BFC?

  1. 根元素(\

    )

  2. 浮动元素(元素的float不是none)

  3. 绝对定位元素(元素的position为absolute或fixed)

  4. display为下面几种: image.png

  5. overflow的值不为visible的块元素

  6. contain的值为layout、content或paint的元素

BFC的应用场景

1. 避免外边距重叠

在同一个BFC内两个相邻的盒子会出现外边距塌陷的问题,我们可以让这两个div分别位于两个不同的BFC中,则可以有效的避免外边距塌陷的问题。 codeSandBox在线演示

2. 清除浮动

BFC容器之所以能够清除浮动,是因为BFC容器可以包裹浮动元素。

codeSandBox在线演示

3. 阻止元素被浮动元素覆盖

如果一个元素设置了浮动,但是下面的元素没有设置浮动,下面的元素会顶上来,形成一个浮动覆盖的效果,我们可以通过给非浮动元素设置成BFC元素,可以防止被浮动元素覆盖。

codeSandBox在线演示

4. 使用BFC实现左侧盒子定宽,右侧盒子自适应布局

左侧设置为浮动,右侧浮动,右侧的宽度计算为100% - 左侧

codeSandBox在线测试

手写clearfix来清除浮动

    <style>
.clearfix::after {
content: '';
clear: both;
display: block;
}
</style>
</head>
<body>
<div class="clearfix">
<div style="width:100px;height:100px;background-color: red;float: left;"></div>
<div style="width:100px;height:100px;background-color: green;float: left;"></div>
</div>
<div>456</div>
<p>789</p>
</body>

Justin面试题阅读需 2 分钟