前端面试题汇总 -前端框架

2 篇文章 0 订阅
1 篇文章 0 订阅

1. vue基本使用

(1)computed和watch

  • computed有缓存,data不变则不会重新计算,可以提高性能
  • watch如何深度监听?
watch: {
	name(oldVal, val){
		console.log('watch name', oldVal, val)
		// 值类型,可正常拿到oldVal和val
	}
	info:{
		handler(oldVal, val){
			console.log('watch info', oldVal, val)
			// 引用类型,拿不到oldVal。因为指针相同,此时已经指向了新的val
		},
		deep: true  // 深度监听
	}
}
  • watch监听引用类型,拿不到oldValue

(2)class和style

  • 使用动态属性
<p :class="{black: isBlack, yellow: isYellow}">使用class</p>
<p :style="styleData">使用style</p>
  • 使用驼峰式写法
data(){
	return {
		styleData: {
			fontSize: '40px',
			color: 'red'
		}
	}
}

(3)条件渲染

  • v-if 和 v-else 的用法,可使用变量,也可以使用===表达式
  • v-if 和 v-show 的区别?
  • v-show 通过CSS display 控制显示和隐藏
  • v-if 组件真正的渲染和销毁,而不是显示和隐藏
  • v-if 和 v-show 的使用场景?

一次性的或更新不频繁的,选择 v-if
频繁切换的,选择 v-show

(4)循环(列表)渲染

  • 如何遍历对象? —— 也可以用v-for
  • key的重要性。key 不能乱写(如 random 或者 index),尽量与业务相关联
  • v-for 和 v-if 不能一起使用!

为何在v-for中用key?

  • 必须用key,且不能是index和random
  • diff算法中通过tag和key
  • 减少渲染次数,提升渲染性能

(5)事件

  • event 参数,自定义参数
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
<script>
methods:{
	increment1(event){
		console.log('event', event.__proto__.constructor) // 是原生的event对象,没有进行过任何装饰
		console.log(event.target)  //button对象
		console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和React不一样
	}
	increment2(val, event){
	}
}
</script>
  • 事件修饰符,按键修饰符
    事件修饰符
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素出发的事件现在此处理,然后才交由内部元素进行处理 -->
<div v-on:click:capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

按键修饰符

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @ckick.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
  • 【观察】事件被绑定到哪里?

事件被挂载到当前元素

(6)表单

  • v-model
  • 常见表单项 textarea checkbox radio select
  • 修饰符 lazy number trim
<p>输入框</p>
<input type="text" v-model.trim="name" />  // 截掉前后的空格
<input type="text" v-model.lazy="name" />  // 全部输入结束后显示
<input type="text" v-model.number="age" />  // 转化为数字

<p>多行文字</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 时不允许的!-->

<p>复选框</p>
<input type="checkbox" v-model="checked" />

<p>多个复选框</p>
<input type="checkbox" id="jack" value="Jack" v-model="checked" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checked" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checked" />
<label for="mike">Mike</label>

<p>单选</p>
<input type="radio" id="male" value="male" v-model="gender" />
<label for="male"></label>
<input type="radio" id="female" value="female" v-model="gender" />
<label for="female"></label>

<p>下拉列表</p>
<select v-model="selected">
	<option disabled value="">请选择</option>
	<option>A</option>
	<option>B</option>
	<option>C</option>
</select>

<p>下拉列表(多选)</p>
<select v-model="selectedList" multiple>
	<option disabled value="">请选择</option>
	<option>A</option>
	<option>B</option>
	<option>C</option>
</select>

2. vue组件使用

(1)props 和 $emit

(2)组件间通讯 - 自定义事件

Index.vue


<template>
    <div>
        <Input @add="addHandler"/>
        <List :list="list" @delete="deleteHandler"/>
    </div>
</template>

<script>
import Input from './Input'
import List from './List'

export default {
    components: {
        Input,
        List
    },
    data() {
        return {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
        addHandler(title) {
            this.list.push({
                id: `id-${Date.now()}`,
                title
            })
        },
        deleteHandler(id) {
            this.list = this.list.filter(item => item.id !== id)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('index created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('index mounted')
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('index before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('index updated')
    },
}
</script>

List.vue

<template>
    <div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {{item.title}}

                <button @click="deleteItem(item.id)">删除</button>
            </li>
        </ul>
    </div>
</template>

<script>
import event from './event'

export default {
    // props: ['list']
    props: {
        // prop 类型和默认值
        list: {
            type: Array,
            default() {
                return []
            }
        }
    },
    data() {
        return {

        }
    },
    methods: {
        deleteItem(id) {
            this.$emit('delete', id)
        },
        addTitleHandler(title) {
            // eslint-disable-next-line
            console.log('on add title', title)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('list created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('list mounted')

        // 绑定自定义事件
        event.$on('onAddTitle', this.addTitleHandler)
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('list before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('list updated')
    },
    beforeDestroy() {
        // 及时销毁,否则可能造成内存泄露(及时解绑自定义事件)
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
</script>

Input.vue

<template>
    <div>
        <input type="text" v-model="title"/>
        <button @click="addTitle">add</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: ''
        }
    },
    methods: {
        addTitle() {
            // 调用父组件的事件
            this.$emit('add', this.title)

            // 调用自定义事件
            event.$emit('onAddTitle', this.title)

            this.title = ''
        }
    }
}
</script>

event.js

import Vue from 'vue'

export default new Vue()

Vue组件如何通讯?

  • 父子组件:props和$emit
  • 组件之间无关系:自定义事件
  • vuex通讯

(3)组件生命周期

单个组件:
created 和 mounted 区别?

created 只是把 vue 的实例初始化了,它只是存在于内存中的一个变量而已,并没有开始渲染。mounted 时真正在网页上绘制完成了。大部分时候需要在 mouted 里进行处理。

  • 挂载阶段
  • 更新阶段
  • 销毁阶段

在这里插入图片描述

父子组件:

  • 父组件先created,子组件再created
  • 子组件先mounted,父组件再mounted
  • 父组件先beforeUpdate,子组件再beforeUpdate
  • 子组件先updated,父组件再updated

3. vue高级特性

(1)自定义 v-model

index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- 自定义 v-model -->
        <p>{{name}}</p>
        <CustomVModel v-model="name"/>
    </div>
</template>

<script>
import CustomVModel from './CustomVModel'

export default {
    components: {
        CustomVModel
    },
    data() {
        return {
            name: '双越'
        }
    }
}
</script>

CustomVModel.vue

<template>
    <!-- 例如:vue 颜色选择 -->
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change1 和 model.event1 要对应起来
        3. text1 属性对应起来
    -->
</template>

<script>
export default {
    model: {
        prop: 'text1', // 对应 props text1
        event: 'change1'
    },
    props: {
        text1: String,
        default() {
            return ''
        }
    }
}
</script>

(2)$nextTick

  • Vue 是异步渲染
  • data 改变之后,DOM不会立刻渲染,会异步渲染
  • $nextTick 会在DOM渲染之后被触发,以获取最新DOM节点
<template>
  <div id="app">
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {{item}}
        </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
      return {
        list: ['a', 'b', 'c']
      }
  },
  methods: {
    addItem() {
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
        // 2. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
        this.$nextTick(() => {
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          // eslint-disable-next-line
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
</script>

(3)slot

父组件往自组件里面插点东西

a. 基本使用

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- slot -->
        <SlotDemo :url="website.url">
            {{website.title}}
        </SlotDemo>
    </div>
</template>

<script>
import SlotDemo from './SlotDemo'
export default {
    components: {
        SlotDemo
    },
    data() {
        return {
            name: '双越',
            website: {
                url: 'http://imooc.com/',
                title: 'imooc',
                subTitle: '程序员的梦工厂'
            }
        }
    }
}
</script>

SlotDemo.vue

<template>
    <a :href="url">
        <slot>
            默认内容,即父组件没设置内容时,这里显示
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {}
    }
}
</script>
b. 作用域插槽

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- slot -->
        <ScopedSlotDemo :url="website.url">
            <template v-slot="slotProps">
                {{slotProps.slotData.title}}
            </template>
        </ScopedSlotDemo>
    </div>
</template>

<script>
import ScopedSlotDemo from './ScopedSlotDemo'
export default {
    components: {
        ScopedSlotDemo
    },
    data() {
        return {
            name: '双越',
            website: {
                url: 'http://imooc.com/',
                title: 'imooc',
                subTitle: '程序员的梦工厂'
            }
        }
    }
}
</script>

ScopedSlotDemo.vue

<template>
    <a :href="url">
        <slot :slotData="website">
            {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {
            website: {
                url: 'http://wangEditor.com/',
                title: 'wangEditor',
                subTitle: '轻量级富文本编辑器'
            }
        }
    }
}
</script>
c. 具名插槽
<!-- NamedSlot组件 -->
<div class="container">
	<header>
		<slot name="header"></slot>
	</header>
	<main>
		<slot></slot>
	</main>
	<footr>
		<slot name="footer"></slot>
	</footer>
</div>
<NamedSlot>
	<!-- 缩写 <template #header> -->
	<template v-slot:header>
		<h1>将插入 header slot 中</h1>
	</template>

	<p>将插入到 main slot 中,即未命名的 slot</p>
	
	<template v-slot:footer>
		<p>将插入到 footer slot 中</p>
	</template>
</NamedSlot>

(4)动态组件

  • :is="component-name"用法
  • 需要根据数据,动态渲染的场景。即组件类型不确定。

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- 动态组件 -->
        <!-- <component :is="NextTickName"/> -->
        <div v-for="(val, key) in newsData" :key="key">
        	<component :is="val.type"/>
        </div>
    </div>
</template>

<script>
import NextTick from './NextTick'

export default {
    components: {
        NextTick
    },
    data() {
        return {
            // NextTickName: "NextTick",
            newsData: {
            	1: {
            		type: 'text'
            	}
            	2: {
            		type: 'text'
            	}
            	3: {
            		type: 'image'
            	}
            }
        }
    }
}
</script>

(5)异步组件

  • import() 函数
  • 按需加载,异步加载大组件

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- 异步组件 -->
        <FormDemo v-if="showFormDemo"/>
        <button @click="showFormDemo = true">show form demo</button>
    </div>
</template>

<script>
export default {
    components: {
        FormDemo: () => import('../BaseUse/FormDemo')
    },
    data() {
        return {
            showFormDemo: false
        }
    }
}
</script>

(6)keep-alive

  • 缓存组件
  • 频繁切换,不需要重复渲染
  • Vue常见性能优化方式之一

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- keep-alive -->
        <KeepAlive/>
    </div>
</template>

<script>
import KeepAlive from './KeepAlive'

export default {
    components: {
        KeepAlive
    },
    data() {
        return {
            name: '双越',
            website: {
                url: 'http://imooc.com/',
                title: 'imooc',
                subTitle: '程序员的梦工厂'
            },
            // NextTickName: "NextTick",
            showFormDemo: false
        }
    }
}
</script>

KeepAlive.vue

<template>
    <div>
        <button @click="changeState('A')">A</button>
        <button @click="changeState('B')">B</button>
        <button @click="changeState('C')">C</button>

        <keep-alive> <!-- tab 切换 -->
            <KeepAliveStageA v-if="state === 'A'"/> <!-- v-show -->
            <KeepAliveStageB v-if="state === 'B'"/>
            <KeepAliveStageC v-if="state === 'C'"/>
        </keep-alive>
    </div>
</template>

<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'

export default {
    components: {
        KeepAliveStageA,
        KeepAliveStageB,
        KeepAliveStageC
    },
    data() {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state) {
            this.state = state
        }
    }
}
</script>

KeepAliveStateA.vue

<template>
    <p>state A</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('A mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('A destroyed')
    }
}
</script>

KeepAliveStateB.vue

<template>
    <p>state B</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('B mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('B destroyed')
    }
}
</script>

KeepAliveStateC.vue

<template>
    <p>state C</p>
</template>

<script>
export default {
    mounted() {
        // eslint-disable-next-line
        console.log('C mounted')
    },
    destroyed() {
        // eslint-disable-next-line
        console.log('C destroyed')
    }
}
</script>

(7)mixin

  • 多个组件有相同的逻辑,抽离出来
  • mixin并不是完美的解决方案,会有一些问题
  • Vue3提出的Composition API旨在解决这些问题

Index.vue

<template>
    <div>
        <p>vue 高级特性</p>
        <hr>

        <!-- mixin -->
        <MixinDemo/>
    </div>
</template>

<script>
import MixinDemo from './MixinDemo'

export default {
    components: {
        MixinDemo
    },
    data() {
        return {
            name: '双越',
            website: {
                url: 'http://imooc.com/',
                title: 'imooc',
                subTitle: '程序员的梦工厂'
            },
            // NextTickName: "NextTick",
            showFormDemo: false
        }
    }
}
</script>

MixinDemo.vue

<template>
    <div>
        <p>{{name}} {{major}} {{city}}</p>
        <button @click="showName">显示姓名</button>
    </div>
</template>

<script>
import myMixin from './mixin'

export default {
    mixins: [myMixin], // 可以添加多个,会自动合并起来
    data() {
        return {
            name: '双越',
            major: 'web 前端'
        }
    },
    methods: {
    },
    mounted() {
        // eslint-disable-next-line
        console.log('component mounted', this.name)
    }
}
</script>

mixin.js

export default {
    data() {
        return {
            city: '北京'
        }
    },
    methods: {
        showName() {
            // eslint-disable-next-line
            console.log(this.name)
        }
    },
    mounted() {
        // eslint-disable-next-line
        console.log('mixin mounted', this.name)
    }
}

mixin的问题:

  • 变量来源不明确,不利于阅读
  • 多mixin可能会造成命名冲突
  • mixin和组件可能出现多对多的关系,复杂度较高

4. vuex

在这里插入图片描述
(1)state
(2)getters
(3)action
只有action里面可以做异步操作。
(4)mutation
(5)用于Vue组件
a. dispatch
b. commit
c. mapState
d. mapGetters
e. mapActions
f. mapMutations

5. vue-router

(1)路由模式(hash、H5 history)

  • hash模式(默认),如http://abc.com/#/user/10
  • H5 history模式,如http://abc.com/user/20
const router = new VueRouter({
	mode: 'history',  // 使用 h5 history 模式
	routes: [...]
})
  • 后者需要server端支持,因此无特殊需求可选择前者

(2)路由配置(动态路由、懒加载)

  • 动态路由
const User = {
	// 获取参数如10 20
	template: '<div>User {{$route.params.id}}</div>'
}
const router = new VueRouter({
	routes: [
		// 动态路径参数 以冒号开头。能命中‘/user/10’,‘/user/20’等格式的路由
		{ path: '/user/:id', component: User}
	]
})
  • 懒加载
export default new VueRouter({
	routes: [
		{
			path: '/',
			component: () => import(
				/* webpackChunkName: "navigator" */
				'./../components/Nabigator'
			)
		},
		{
			path: '/feedback',
			component: () => import(
				/* webpackChunkName: "feedback" */
				'./../components/FeedBack'
			)
		}
	]
})

6. 组件化

(1)“很久以前”的组件化

  • asp、jsp、php已经有组件化了
  • nodejs中也有类似的组件化
  • 现在vue和react等的组件化和之前的jsp等的组件化有一个本质的区别,那就是数据驱动视图。
  • 传统组件,只是静态渲染,更新还要依赖于操作DOM
  • vue是通过MVVM,React是通过setState来数据驱动视图
  • 即我们不再去自己操作DOM,而是改变数据,框架帮我们根据数据重新渲染视图
  • 这一点使得我们在用vue和react开发时更加关注数据和业务逻辑,而不是关注于如何添加修改DOM,使前端开发到了一个新的平台上。
    在这里插入图片描述

View中有点击等事件ViewModel会监听到,然后修改Model中的数据,Model中的数据变化会修改View视图。

7. 响应式

  • 组件 data 的数据一旦变化,立即触发视图的更新
  • 实现数据驱动视图的第一步
  • 核心API - Object.defineProperty
  • 由于Object.defineProperty的一些缺点,Vue3.0使用Proxy实现响应式

Proxy有兼容性问题

  • Proxy兼容性不好,且无法polyfill

Object.defineProperty基本用法

const data = {}
const name = 'zhangsan'
Object.defineProperty(data, 'name', {
	get: function(){
		console.log('get')
		return name
	}
	set: function(){
		console.log('set')
		name = newVal
	}
});

// test
console.log(data.name)  // get zhangsan
data.name = 'lisi'  // set

Object.defineProperty缺点

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性 / 删除属性(需要 Vue.set, Vue.delete 去解决)
  • 无法原生监听数组,需要特殊处理 - 重新定义原型,重写push、pop等方法,实现监听
// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value)

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听
                observer(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组

8. vdom和diff

参考:
(1)virtual dom 虚拟DOM
(2)MVVM

9. 模板编译

  • 模板是vue开发中最常用的部分,即与使用相关联的原理
  • 它不是html,有指令、插值、JS表达式,能实现判断、循环
  • vue template compiler 将模板编译为render函数
  • 执行render函数生成vnode
  • 基于vnode再执行patch和diff
  • 使用webpack vue-loader,会在开发环境下编译模板
  • 模板转换成html的过程,叫做模板编译

(1)with语法

  • 改变{ }内自由变量的查找规则,当作obj属性来查找
  • 如果找不到匹配的obj属性,就会报错
  • with要慎用,它打破了作用于规则,易读性变差

(2)vue模板编译

const template = `<p>{{message}}</p>`
// with(this){return _c('p', [_v(_s(message))])}

_c: createElement 相当于h函数
_v: createTextVNode
_s: toString
_l: renderList

(3)表达式

const template = `<p>{{flag ? message : 'no message found'}}</p>`
// width(this){return _c('p', [_v(_s(flag? message: 'no message found'))])}

(4)属性和动态属性

const template = `
	<div id="div1" class="container">
		<img :src="imgUrl" />
	</div>
`
// with(this){return _c('div', {staticClass: "container", attrs: {"id": "div1"}}, [_c('img', {attrs: {"src": imgUrl}})])}

(5)条件

const template = `
	<div>
		<p v-if="flag === 'a'">A<p>
		<p v-else>B</p>
	</div>
`
// with(this){return _c('div', [(flag === 'a') ? _c('p', [_v("A")]) : _c('p', [_v("B")])])}

(6)循环

const template = `
	<ul>
		<li v-for="item in list" :key="item.id">{{item.title}}</li>
	</ul>
`
// with(this){return _c('ul', _l((list), function(item){return _c)("li", {key: item.id}, [_v(_s(item.title))])}), 0)}

(7)事件

const template = `
	<button @click="clickHandler">submit</button>
`
// with(this){return _c('button', {on: {"click": clickHandler}}, [_v("submit")])}

(8)v-model

const template = `<input type="text" v-model="name" />`
// 主要看input事件
// with(this){return _c("input", {directives: [{name: "model", rawName: "v-model", value: (name), expression: "name"}], attrs: {"type": "text"}, domProps: {"value": (name)}, on: {"input": function($event){if($event.target.composing)return; name=$event.target.value}}})}

(9)vue组件中使用render代替template

  • 在有些复杂情况中,不能用template,可以考虑用render
  • React一直都用render(没有模板)
Vue.component('heading', {
	// template: 'xxx',
	render: function(createElement){
		return createElement(
			'h' + this.level,
			[
				createElement('a', {
					attrs: {
						name: 'headerId',
						href: '#' + 'headerId'
					}
				}, 'this is a tag')
			]
		)
	}
})

10. 渲染过程

在这里插入图片描述
(1)初次渲染过程

  • 解析模板为render函数(或在开发环境已完成, vue-loader)
  • 触发响应式,监听data属性getter、setter
  • 执行render函数,生成vnode,patch(ele,vnode)

执行render函数会触发getter

<p>{{message}}</p>

<script>
export default{
	data(){
		return {
			message: 'hello', // 会触发get
			city: '北京'  // 不会触发get,因为模板没用到,即和视图没关系
		}
	}
}
</script>

(2)更新过程

  • 修改data,触发setter(此前在getter中已被监听)
  • 重新执行render 函数,生成newVnode
  • patch(vnode, newVnode)

(3)异步渲染

  • $nextTick
  • 汇总data的修改,一次性更新视图
  • 减少DOM操作次数,提高渲染性能
  • $nextTick
<template>
  <div id="app">
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {{item}}
        </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
      return {
        list: ['a', 'b', 'c']
      }
  },
  methods: {
    addItem() {
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
        // 2. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
        this.$nextTick(() => {
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          // eslint-disable-next-line
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
</script>

11. 前端路由

(1)前端路由原理

  • hash
  • H5 history

(2)网页url组成部分

// http://127.0.0.1:8881/01-hash.html?a=100&b=20#/aaa/bbb
location.protocol  // 'http:'
location.hostname  // '127.0.0.1'
location.host  // '127.0.0.1:8881'
location.port  // '8881'
location.pathname  // '01-hash.html'
location.search  // '?a=100&b=20'
location.hash  // '#/aaa/bbb'

(3)hash的特点

  • hash变化会触发网页跳转,即浏览器的前进、后退
  • hash变化不会刷新页面,SPA必需的特点
  • hash永远不会提交到server端(前端自生自灭)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hash test</title>
</head>
<body>
    <p>hash test</p>
    <button id="btn1">修改 hash</button>

    <script>
        // hash 变化,包括:
        // a. JS 修改 url
        // b. 手动修改 url 的 hash
        // c. 浏览器前进、后退
        window.onhashchange = (event) => {
            console.log('old url', event.oldURL)
            console.log('new url', event.newURL)

            console.log('hash:', location.hash)
        }

        // 页面初次加载,获取 hash
        document.addEventListener('DOMContentLoaded', () => {
            console.log('hash:', location.hash)
        })

        // JS 修改 url
        document.getElementById('btn1').addEventListener('click', () => {
            location.href = '#/user'
        })
    </script>
</body>
</html>

(4)H5 history

  • 用url规范的路由,但跳转时不刷新页面
  • history.pushState
  • history.onpopstate
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>history API test</title>
</head>
<body>
    <p>history API test</p>
    <button id="btn1">修改 url</button>

    <script>
        // 页面初次加载,获取 path
        document.addEventListener('DOMContentLoaded', () => {
            console.log('load', location.pathname)
        })

        // 打开一个新的路由
        // 【注意】用 pushState 方式,浏览器不会刷新页面
        document.getElementById('btn1').addEventListener('click', () => {
            const state = { name: 'page1' }
            console.log('切换路由到', 'page1')
            history.pushState(state, '', 'page1') // 重要!!
        })

        // 监听浏览器前进、后退
        window.onpopstate = (event) => { // 重要!!
            console.log('onpopstate', event.state, location.pathname)
        }

        // 需要 server 端配合,可参考
        // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
    </script>
</body>
</html>

(5)如何选择哪一种?

  • to B的系统推荐用hash,简单易用,对url规范不敏感
  • to C的系统,可以考虑选择H5 history,但需要服务端支持
  • 能选择简单的,就不用复杂的,要考虑成本和收益

12. 双向数据绑定 v-model 的实现原理

const template = `<input type="text" v-model="name" />`
// 主要看input事件
// with(this){return _c("input", {directives: [{name: "model", rawName: "v-model", value: (name), expression: "name"}], attrs: {"type": "text"}, domProps: {"value": (name)}, on: {"input": function($event){if($event.target.composing)return; name=$event.target.value}}})}
  • input元素的value = this.name
  • 绑定input事件 this.name = $event.target.value
  • data 更新触发re-render

13. 为何组件data必须是一个函数?

因为使用时,export default 其实是当作一个类来使用,对类进行实例化。如果data不是函数的话,多个实例化的属性是能够共享的,修改一个,其他的也会改变。如果data是函数的话,data中的变量在闭包中,不会被其他实例化修改。

14. ajax请求应该放在哪个生命周期?

  • mounted
  • JS是单线程的,ajax异步获取数据
  • 放在mounted之前没有用,只会让逻辑更加混乱

15. 如何将组件所有的props传递给自组件?

$props

<User v-bind="$props" />

16. 多个组件有相同的逻辑,如何抽离?

  • mixin
  • 以及mix的一些缺点

17. 何时要使用异步组件?

  • 加载大组件
  • 路由异步加载

18. 何时需要使用keep-alive?

  • 缓存组件,不需要重复渲染
  • 如多个静态tab页的切换
  • 优化性能

19. 何时需要使用beforeDestroy?

  • 解绑自定义事件 event.$off
  • 清除定时器
  • 解绑自定义的DOM事件,如window scroll等

20. Vuex中 action 和 mutation 有何区别?

  • action 中处理异步,mutation 不可以
  • mutation 做原子操作
  • action 可以整合多个 mutation

21. 请描述响应式原理

  • 监听data变化
  • 组件渲染和更新的流程

22. 简述diff算法过程

  • patch(elem, vnode) 和 patch(vnode, newVnode)
  • patchVnode 和 addVnodes 和 removeVnodes
  • updateChildren (key 的重要性)

23. Vue常见性能优化方式

  • 合理使用 v-show 和 v-if
  • 合理使用 computed,因为computed有缓存
  • v-for 时加 key,以及避免和v-if同时使用
  • 自定义事件、DOM事件及时销毁,不销毁可能倒置内存泄漏
  • 合理使用异步组件
  • 合理使用 keep-alive
  • data 层级不要太深,响应式时计算深度太多,会卡
  • 使用 vue-loader 在开发环境做模板编译(预编译)
  • webpack 层面的优化
  • 前端通用的性能优化,如图片懒加载
  • 使用SSR

24. Vue3 升级内容

  • 全部用 ts 重写(响应式、vdom、模板编译等)
  • 提升性能,代码量减少
  • 会调整部分API

25. 社区热门知识点:Proxy重写响应式

(1)基本使用

const data = {
	name: 'zhangsan',
	age: 20
}

// const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
	get(target, key, receiver){
		// 只处理本身(非原型的)属性
		const ownKeys = Reflect.ownKeys(target)
		if(ownKeys.includes(key)){
			console.log('get', key)
		}
		
		const result = Reflect.get(target, key, receiver)
		console.log('get', key)
		return result  // 返回结果
	},
	set(target, key, val, receiver){
		// 重复的数据,不处理
		if(val === target[key]){
			return true
		}

		const result = Reflect.set(target, key, receiver)
		console.log('set', key, val)
		console.log('result', result)
		return result  // 是否设置成功
	},
	deleteProperty(target, key){
		const result = Reflect.deleteProperty(target, key)
		console.log('deleteProperty', key)
		console.log('result', result)
		return result  // 是否删除成功
	}
})

// test
proxyData.age  // get age   // 20
proxyData.age = 30  //set age 30  // result true
delete proxyData.age  //delete property age   // result true

// data 为数组时
proxyData.push('d')
// get length
// set 3 d

Reflect.ownKeys([10, 20, 30])  //["0", "1", "2", "length"]
Reflect.ownKeys({a: 10, b: 20})   //["a", "b"]

(2)Reflect 作用

  • 和 Proxy 能力一一对应
  • 规范化、标准化、函数式
const obj = {a: 100, b: 200}

// 假如想判断 obj 中是否存在 a
'a' in obj  // true

// 用Reflect
Reflect.has(obj, 'a')  // true

// 删除a
delete obj.a  // true

// 用Reflect写法
Reflect.deleteProperty(obj, 'b')  // true
  • 替代掉Object上的工具函数
const obj = {a: 100, b: 200}

Object.getOwnPropertyNames(obj)  // ["a", "b"]

Reflect.ownKeys(obj)  // ["a", "b"]

(3)Proxy 实现响应式

  • 深度监听,性能更好
  • 可监听 新增 / 删除 属性
  • 可监听数组变化
  • 能规避Object.defineProperty的问题
  • Proxy无法兼容所有浏览器,无法polyfill

proxy-observe.js

// 创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        // 不是对象或数组,则返回
        return target
    }

    // 代理配置
    const proxyConf = {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('get', key) // 监听
            }
    
            const result = Reflect.get(target, key, receiver)
        
            // 深度监听
            // 性能如何提升的?
            return reactive(result)
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true
            }
    
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('已有的 key', key)
            } else {
                console.log('新增的 key', key)
            }

            const result = Reflect.set(target, key, val, receiver)
            console.log('set', key, val)
            // console.log('result', result) // true
            return result // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key)
            console.log('delete property', key)
            // console.log('result', result) // true
            return result // 是否删除成功
        }
    }

    // 生成代理对象
    const observed = new Proxy(target, proxyConf)
    return observed
}

// 测试数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        city: 'beijing',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
}

const proxyData = reactive(data)

26、React 实现 Todo List 设计

(1)state 数据结构设计

  • 用数据描述所有的内容
  • 数据要结构化,易于程序操作(遍历、查找)
  • 数据要可扩展,以便增加新的功能

(2)组件设计、组件通讯

  • 从功能上拆分层次
  • 尽量让组件原子化
  • 容器组件(只管理数据)& UI组件(只显示视图)
<App> {/* 只负责管理数据 */}
	<Input /> {/* 只负责输入,将数据结果给父组件 */}
	<List> {/* 只负责显示列表,从父组件获取数据 */}
		<ListItem /> {/* 显示单条数据,删除、切换完成状态*/}
		<ListItem />
		<ListItem />
	</List>
</App>

(3)结合redux

27、Vue设计购物车(组件结构,vuex state 数据结构)

(1)data 数据结构设计

  • 用数据描述所有的内容
  • 数据要结构化,易于程序操作(遍历、查找)
  • 数据要可扩展,以便增加新的功能

(2)组件设计、组件通讯

  • 从功能上拆分层次
  • 尽量让组件原子化
  • 容器组件(只管理数据)& UI组件(只显示视图)

(3)结合vuex

<App> <!-- 管理所有数据 -->
	<ProductionList><!-- 商品列表 -->
		<ProductionListItem />
		<ProductionListItem />
		<ProductionListItem />
	</ProductionList>
	<CartList><!-- 购物车列表和总和 -->
		<CartItem />
		<CartItem />
	</CartList>
</App>

28、baseURL配置

(1)在config/dev.env.js 和 config/prod.env.js 中配置开发环境和生产环境的默认路径

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"https://xxx.xx.com/vue-admin"',
})
module.exports = {
  NODE_ENV: '"production"',
  BASE_API: '"https://xxx.xx.com/vue-admin"',
}

(2)在需要的地方引入

axios.default.baseURL = process.env.BASE_API
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值