手撕代码

285 阅读14分钟

2-3 css部分

  • 两栏布局
  • 三栏布局
  • 双飞翼和圣杯布局:和三栏布局要求相同,不过中间列要写在前面保证优先渲染。

1.自适应缩放要注意,要给max-widht或者min-width。就flex,float,position这三种方式,相对稳定的是flex。flex:auto,flex:none

代表1 1 auto 0 0 auto grow默认是0,shrink默认是1,basis默认是盒子宽度-auto

  • 三角形

border:1px solid #ccc 分解border-width border-style: solid; border-color: tomato transparent transparent transparent;

  • 使用 css 实现一个宽高自适应的正方形

1.直接用vw单位

2.padding-top:% padding的百分比值参照的不是容器的高度,而是宽度 。

3.利用第二点的性质,给元素添加伪元素,伪元素margin-top:30%。要记得触发父元素为BFC,不然撑不起来

  • 实现扇形(border+旋转)
  • 垂直居中(left:0,margin:0,top:0,right:0,margin:auto)
  • 清除浮动(伪元素clear:both),父级触发BFC
  • 弹出框
  • 导航栏(滑动)

CSS 部分完,总结,Flex 无敌。

2-4 js 部分

  • hash路由
window.addEventlistener('hashchange',(e)=>{
  location.hash
} )
1.监听
2.操作BOM
  • history路由
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。

2-4 css部分

  • 扩大可点击区域

关键实现:伪元素

具体分析:利用伪元素和定位达到鼠标移到边缘时候出现手型且可点击

    .expand-range {
      position: relative;
      cursor: pointer;
    }

    .expand-range::after {
      content: '';
      position: absolute;
      top: -10px;
      right: -10px;
      bottom: -10px;
      left: -10px;
    }

scss方式

@mixin expand-range($top: -10px, $right: $top, $bottom: $top, $left: $right, $position: relative,$cursor:pointer) {
  position: $position;
  cursor: $cursor;
  &:after {
    content: '';
    position: absolute;
    top: $top;
    right: $right;
    bottom: $bottom;
    left: $left;
  }
}
//使用:.test { @include expand-range($top: -5px) }
  • 巧用层叠上下文

    元素的层叠关系,背景,负z-index,盒子z-index

    利用伪元素实现圆角矩形并叠加在父元素的背景之上文字之下

/*边框内圆角*/
div {
  position: relative;
  z-index: 1;
  height: 200px;
  padding: 10px;
  background: #333;
}

div::after {
  content: '';
  position: absolute;
  left: 10px;
  top: 10px;
  right: 10px;
  bottom: 10px;
  z-index: -1;
  border-radius: 5px;
  background: cyan;
}
  • clip-path

clip-path: circle(50px at 50px 50px)50px 50px 的地方为圆心裁剪一个半径 50px 的圆;clip-path: ellipse(30px 40px at 50px 50px)50px 50px 的地方为圆心裁剪一个横向半径 30px,纵向半径 40px 的椭圆;clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%) 按照多个坐标剪裁一个多边形,此处是菱形。

  • min-content (自适应宽度)

min-content max-content 自适应宽度 width:min-content 根据内容的最小值自动缩放,反之

  • box-shadow

水平,垂直,blur,spread,color,inset(从外层的阴影(开始时)改变阴影内侧阴影)

使用box-shadow可以模拟实现多重边框,但是由于阴影不占空间,所以无法触发点击事件,鼠标hover边框时无法出现小手,所以需要配合inset关键字使用

弄清楚参数之后可以

单侧投影: box-shadow: 0 5px 4px -4px black;

逗号分隔设置多个阴影色: box-shadow: 5px 0 5px -5px black, -5px 0 5px -5px black;

不规则投影图形多个组合:filter: drop-shadow(2px 2px 10px rgba(0,0,0,.5));

  • 解析background

background-clip:属性规定背景的绘制区域 ,由于background属性默认会覆盖整个盒模型包括边框border(所以要裁切到规定规定的区域,让border显示) 半透明边框

还是得自己积累,这讲的花里胡哨

  • linear-gradient

至少两种颜色,默认上到下(height)

颜色后面的百分比是,高度为百分比是开始这个颜色

方向to left top 就是到左上角,那么就是右下角到左上角

直接给角度也是按从左到右或者其他的to left或者to right

linear-gradient(0deg, blue, green 40%, red);线性渐变

repeating-linear-gradient(red, yellow 10%, green 20%); 是否重复

radial-gradient(circle, red, yellow, green); 默认椭圆 径向渐变

  • animation

贝塞尔曲线

总结:背景颜色,渐变,动画,position,box-shadow

2-4 vue部分

  • 说说你对 SPA 单页面的理解,它的优缺点分别是什么?

仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。也就是说只有一个html,webpack打包,组件,模块化,

优点:前后端分离,页面的跳转不会重新加载整个页面,提高了用户体验

确定:SEO难度大,首屏加载速度慢。服务端渲染再次得到了需求

  • v-show 与 v-if 有什么区别?
  • Class 与 Style 如何动态绑定?(都是对象和数组,稍微有点区别而已,style数组的值是写好的在data上)
  • 怎样理解 Vue 的单向数据流?(props)
  • computed 和 watch 的区别和运用的场景?

依赖项,监听项,异步,缓存(computed计算缓存也可避免每次组件渲染都计算一次)

  • 直接给一个数组项赋值,Vue 能检测到变化吗?

array的变化追踪通过监听array原型上方法的改变,通过拦截器从新是操作数组

原型方法改变数组的方法有7种 push,pop,shift,unshift,splice,sort,和reverse

对Array的变化侦测是通过拦截原型的方式实现的。正式因为这种方式,其实有些数组操作Vue.js是拦截不到的,

this.list[0] = 2

this.list.length = 0

通过splice触发原型方法去改变数组,或者vue提供的this.$set(target,key,value)

  • 谈谈你对 Vue 生命周期的理解?

1.初始化,2.data,3.虚拟DOM未挂载,4.操作DOM

  • Vue 的父组件和子组件生命周期钩子函数执行顺序?

父组件操作虚拟DOM的时候才会碰到子组件

  • 在哪个生命周期内调用异步请求?

在created阶段,data可以获取了的,可以进行赋值的。

能更快获取到服务端数据,减少页面 loading 时间;

ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

  • 父组件可以监听到子组件的生命周期吗?

// Parent.vue

<Child @hook:mounted="doSomething" >

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

  • 谈谈你对 keep-alive 的了解?

Keep-alive 和router

router-view是router提供的组件 Keep-alive是vue内置组件

属性:include,exclude

<keep-alive>
	<router-view />
</keep-alive>
直接嵌套二级组件是不会缓存的
那么就会缓存在Ndode节点中了,就不会重新创建,那么也就不会触发生命周期了 activated,deactivated 
组件内路由导航:beforeRouteEnter,beforeRouteLeave  

如果想记住二级切换的是哪个组件
data(){
	return {
		path:/home
	}
}
activated(){
	this.$router.push(this.path)
},
deactivated(){},
beforeRouteLeave(to,from,next){
  this.path =this.$route.path
  next()
  }
这样就是记住上一个二级路由
  • 组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝

  • v-model 的原理?

在表单 input、textarea、select 等元素上创建双向数据绑定

<input v-model='something'>
    
相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:
<ModelChild v-model="message"></ModelChild>

子组件:
<div>{{value}}</div>

props:{
    value: String
},
methods: {
  test1(){
     this.$emit('input', '小红')
  },
},

  • Vue 组件间通信有哪几种方式?

Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信

props / $emit 适用 父子组件通信

parent/parent` / `children`:访问父 / 子实例

EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

$attrs/$listeners 适用于 隔代组件通信

provide / inject 适用于 隔代组件通信

Vuex 适用于 父子、隔代、兄弟组件通信

  • 能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

location.hash 的值就是 URL 中 # 后面的内容它的 location.hash 的值为 '#search':

www.word.com#search

URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;

hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;

可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;

我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。history.pushState() 和 history.repalceState()

在不进行刷新的情况下,操作浏览器的历史纪录

window.history.pushState(null, null, path);

window.history.replaceState(null, null, path);

pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

  • 什么是 MVVM?

View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建

Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口

MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

  • Vue 是如何实现数据双向绑定的?

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

  • Vue 框架怎么实现对象和数组的监听?

Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。

  • Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题

2-6 ES6部分

  • ES6面向对象

在ES6中新增加了类的概念,可以使用class关键字声明一个类

原理:类本身指向构造函数,所有方法定义在prototype上,可看作构造函数的另一种写法(Class === Class.prototype.constructor)

通过class关键字创建类,类名我们还是习惯性定义首写字母大写

类里面有个constructor函数,可以接受传递过来的参数,同时返回实例对象

constructor 函数只要new生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数

生成实例 new 不能省略

最后注意语法规范,创建类 类名后面不要加小括号,生成实例 类名后面要加小括号,构造函数不需要加function

我们类里面的所有函数不需要写function

多个函数方法之间不需要添加逗号分隔

class Name {
	constructor(x,y){
    this.x = x;
    this.y = y;
  }
  say(){}
  good(){}
}
// 继承
class Father{
  constructor(x,y){
    this.x = x;
    this.y = y;
	}
  say(){
    console.log('father')
  }
}
class Son extends Father{
  constructor(x,y){
    super(x,y)
  }
}

// 私有属性方法
const name = Symbol("name");
const print = Symbol("print");
class Person{
  constructor(age){
    this[name] = 'aking'
    this.age = age;
	}
  [print](){
    console.log(`${this[name]} is ${this.age} years old`)
  }
}
// 混合继承
function CopyProperties(target, source) {
    for (const key of Reflect.ownKeys(source)) {
        if (key !== "constructor" && key !== "prototype" && key !== "name") {
            const desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}
function MixClass(...mixins) {
    class Mix {
        constructor() {
            for (const mixin of mixins) {
                CopyProperties(this, new mixin());
            }
        }
    }
    for (const mixin of mixins) {
        CopyProperties(Mix, mixin);
        CopyProperties(Mix.prototype, mixin.prototype);
    }
    return Mix;
}
class Student extends MixClass(Person, Kid) {}

2-7 vue通信的细节

  • props

数据格式computed

数据监听watch

  • this.$xxx

this.$children,this.$parent this.$refs 这种通信方式,更加的简单直接获取vue实例,对vue实例下的数据和方法直接获取或者引用。

注意获取组件的位置,顺序是否正确

provide inject用法 和 react.context非常相似, provide相当于Context.Provider ,inject 相当于 Context.Consumer,让父组件通信不受到组件深层次子孙组件的影响。

provide提供内容不可能被兄弟组件获取到的,所以兄弟组件的通信不肯能靠这种方式来完成。

父组件对子组件的状态一无所知。也不能主动向子组件发起通信

通过this下面的数据直接获取vue实例这种方法比较暴力,因为我们所谓的组件,最终都会是一个对象,存放组件的各种信息,组件和组件通过this.$childrenthis.$parent指针关联起来。因为在项目中只有一个root根组件,理论上,我们可以找到通过this.$children this.$parent来访问页面上的任何一个组件 ,但是实际上如何精确匹配到目标组件,确是一个无比棘手的问题。

  • provide inject
// 父组件   
provide(){
       return {
           /* 将自己暴露给子孙组件 ,这里声明的名称要于子组件引进的名称保持一致 */
           father:this
       }
   },
// 这里我们通过provide把本身暴露出去。⚠️⚠️⚠️这里声明的名称要与子组件引进的名称保持一致
// 子组件
   /* 引入父组件 */
   inject:['father'],
     methods:{
       send(){
         this.father.sonSay(this.mes)
       }
     },
// 父组件
// 如果我们向外提供了方法,如果方法里面有操作this行为,需要绑定this
   provide(){
       return {
           /* 将通信方法暴露给子孙组件(注意绑定this) */
           grandSonSay:this.grandSonSay.bind(this),
           sonSay:this.sonSay.bind(this)
       }
   },   
   methods:{
      /* 接受孙组件信息 */
      grandSonSay(value){
          this.grandSonMes = value
      },
      /* 接受子组件信息 */ 
      sonSay(value){
          this.sonMes = value
      },
   },
// 子组件
/* 引入父组件方法 */
   inject:['sonSay'],
   methods:{
       send(){
           this.sonSay(this.mes)
       }
   },
  • vuex

vuexactions允许我们做一些异步操作,然后通过commit可以把数据传入对应的mutation,至于actions为什么可以执行异步,是因为里面底层通过Promise.resolve能够获取异步任务完成的状态。

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    fatherMes: 'fatherMes',
    sonMes: 'sonMes',
    fatherMesAsync: 'fatherMesAsync'
  },
  mutations: {
    sayFaher(state, value) {
      state.fatherMes = value
    },
    saySon(state, value) {
      state.sonMes = value
    },
    sayAsyncFather(state, value) {
      state.fatherMesAsync = value
    }
  },
  actions: {
    asyncSayFather({ commit }, payload) {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(payload)
          console.log(payload)
        }, 2000)
      }).then(res => {
        console.log('后执行')
        commit('sayAsyncFather', res)
      })
    }
  }
})

<template>
  <div class="father">
    <input v-model="mes" /> <button @click="send">同步:对子组件说</button
    ><br />
    <input v-model="asyncMes" />
    <button @click="asyncSend">异步:对子组件说</button><br />
    <div>子组件对我说:{{ sonMes }}</div>
  </div>
</template>
<script>
export default {
  /* 父组件 */
  name: 'father',
  data() {
    return {
      mes: '',
      asyncMes: ''
    }
  },
  computed: {
    sonMes() {
      return this.$store.state.sonMes
    }
  },
  mounted() {
    console.log(this.$store)
  },
  methods: {
    /* 触发mutations,传递数据给子组件 */
    send() {
      this.$store.commit('sayFaher', this.mes)
    },
    /* 触发actions,传递数据给子组件 */
    asyncSend() {
      this.$store.dispatch('asyncSayFather', this.asyncMes)
    }
  }
}
</script>

  • 总结:通信就是组件实例之间的数据相互传递,组件实例相互暴露。

vue最终版

  • v-bind="props"vbind="props" v-bind="attrs" v-on="$listeners"

这个配合 v-bind="$attrs" 在封装一些组件的时候非常有用,比如实现属性透传。在子组件中应当添加inheritAttrs: false(避免父作用域的不被认作props的特性绑定应用在子组件的根元素上)。

vm.$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

v-bind="$props": 可以将父组件的所有props下发给它的子组件

未识别的事件可通过v-on="$listeners"传入(.native绑原生事件是没用的)

post: {
  id: 1,
  title: 'My Journey with Vue',
	placeholder:'默认'
}
<!-- 利用 v-bind 可以传入一个对象的所有 property,类似 v-bind="Obj" -->
<blog-post v-bind="post"></blog-post>
<!--等价于-->
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

<input type="text" v-bind="$attrs"/>
// top组件,传递了name,age,gender,sdf四个属性到子组件center,然后接收了两个isClick()和asd()方法
<template>
  <section>
    <centers
      name="name"
      age="18"
      gender="666"
      sdf="asd"
      @isClick="isClick"
      @asd="asd"
    ></centers>
  </section>
</template>
<script>
  import centers from '~/components/center';
  export default {
    components: {
      centers
    },
    methods: {
      asd() {
        console.log(999);
      },
      isClick() {
        console.log(666);
      }
    }
  };
</script>
// 未识别的事件可通过v-on="$listeners"传入(.native绑原生事件是没用的) center组件
<template>
  <section>
    <div class="mt-10">
      <bottom v-bind="$attrs" v-on="$listeners" />
    </div>
  </section>
</template>

<script>
  import bottom from '~/components/bottom';
  export default {
    components: {
      bottom
    },
    props: {
      name: {
        type: String,
        default: 'default'
      },
      age: {
        type: String,
        default: 'default'
      }
    }
  };
</script>

//bottom组件
<template>
  <section>
    <div>
      {{ $attrs['gender'] }}  在$attrs里面只会有props没有注册的属性
      <br>
      {{ gender }}
    </div>
  </section>
</template>

<script>
  export default {
    props: {
      gender: {
        type: String,
        default: ''
      }
    },
    mounted() {
      console.log(this.$attrs);
      console.log(this.$listeners);
      this.$listeners.isClick();
      this.$listeners.asd();
    }
  };
</script>
  • Props 校验
Vue.component('my-component', {
    // 带有默认值的对象
    propA: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    }
})
Vue.component('my-component', {
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
})

  • 插槽

默认插槽

后备内容 默认内容

具名插槽

作用域插槽 v-slot:defalut = { item }

想在一个插槽中使用子组件的数据和事件

直接在子组件中通过 v-bind 的方式将数据或者事件传递给父组件中

    <!-- 具名插槽 -->
    <h3>具名插槽</h3>
    <Child2>
      <template v-slot:footer><div>我是底部</div></template>
      <template #header><div>我是头部</div></template>
      <template v-slot:default>
        <div>我是内容</div>
      </template>
    </Child2>


  <div class="child">
    <slot name="header"></slot>
    <slot></slot>
    <div>Hello, I am from Child.</div>
    <slot name="footer"></slot>
  </div>

  <div class="child">
    <div>Hello, I am from Child.</div>
    <!-- 将user和callMe通过 v-bind 的方式传递 -->
    <slot :user="user" :callMe="callMe"></slot>
  </div>
<!--然后在父组件中的插槽内,通过类似 v-slot:default="slotProps" 接受子组件传递过来的数据-->
    <Child3>
      <!-- slotProps 可以自定义-->
      <template v-slot:default="slotProps">
        <div>我的名字:{{slotProps.user.name}}</div>
        <div>我的年龄:{{slotProps.user.age}}</div>
        <button @click="slotProps.callMe">Clicl Me</button>
      </template>
    </Child3>
<!-- 以上 slotProps 可以自定义,而且可以使用解构赋值的语法 -->

<!-- 解构赋值 -->
<template v-slot:other="{ user, callMe}">
  <div>我的名字:{{user.name}}</div>
  <div>我的年龄:{{user.age}}</div>
  <button @click="callMe">Clicl Me</button>
</template>


  • 动态的指令参数

    以动态的将指令参数传递给组件。假设你有一个组件 ,有时候你需要绑定一个点击事件 click,有时候需要绑定一个双击事件 dblclick,


<template>
  ...
  <my-button @[someEvent]="handleSomeEvent()"/>
  ...
</template>

<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dblclick"
    }
  },
  
  methods:{
    handleSomeEvent(){
      // do something
    }
  }
  ...
</script>

  • hookEvent 的使用

可以模板声明式的监听子组件的生命周期钩子

监听子组件的生命周期,自己的实例生命周期致力于解决创建和销毁写在一起的形式

比如,我们调用了一个很耗费性能的第三方组件 List,这个组件可能需要渲染很久,为了更好的用户体验,我们想在 List 组件进行更新的时候添加一个 loading 的动画效果

<List @hook:updated="handleTableUpdated"></List >

另外,我们还可以通过下面的方式给一个 Vue 组件添加生命周期处理函数
vm.$on('hooks:created', cb)
vm.$once('hooks:created', cb)

mounted(){
	const thirdPartyPlugin = thirdPartyPlugin()
	this.$on('hook:beforeDestroy',()=>{
		thirdPartyPlugin.destroy()
})
}
  • key 值的使用

会遇到如 /path/:id 这样只改变 id 号的场景,但渲染不同的组件。由于 router-view 是复用的,单纯的改变 id 号并不会刷新 router-view

  1. v-for :key

  • CSS scoded 和深度作用选择器

因为父组件设置了 scoped 之后,父组件的样式将不会渗透到子组件中

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作

.a >>> .b { /* ... */ }
  • watch immediate deep $watch
这里 watch 的一个特点是,最初绑定的时候是不会执行的,要等到 id 改变时才执行监听计算。这可能导致我们页面第一次渲染出错

watch: {
  id: {
    handler(newValue) {
      this.getDetails(newValue);
    },
    // 代表在wacth里声明了id后这个方法之后立即先去执行handler方法
    immediate: true
    // 通过指定deep属性为true, watch会监听对象里面每一个值的变化
    deep: true
  }
}

但是如果要在页面初始化时候加载数据,我们还需要在created或者mounted生命周期钩子里面再次调用$_loadData方法。不过,现在可以不用这样写了,通过配置watch的立即触发属性,就可以满足需求了

我们可以在需要的时候通过this.$watch来监听数据变化。那么如何取消监听呢,上例中this.$watch返回了一个值unwatch,是一个函数,在需要取消的时候,执行 unwatch()即可取消

export default {
  data() {
    return {
      formData: {
        name: '',
        age: 0
      }
    }
  },
  created() {
    this.$_loadData()
  },
  methods: {
    // 模拟异步请求数据
    $_loadData() {
      setTimeout(() => {
        // 先赋值
        this.formData = {
          name: '子君',
          age: 18
        }
        // 等表单数据回填之后,监听数据是否发生变化
        const unwatch = this.$watch(
          'formData',
          () => {
            console.log('数据发生了变化')
          },
          {
            deep: true
          }
        )
        // 模拟数据发生了变化
        setTimeout(() => {
          this.formData.name = '张三'
        }, 1000)
      }, 1000)
    }
  }
}

  • v-once 和 v-pre 提升性能

我们知道 Vue 的性能优化很大部分在编译这一块,Vue 源码就有类似标记静态节点的操作,以在 patch 的过程中跳过编译,从而提升性能。

  • v-cloak 解决页面闪烁问题
// template 中
<div class="#app" v-cloak>
    <p>{{value.name}}</p>
</div>

// css 中
[v-cloak] {
    display: none;
}

  • 表单输入控制——表单修饰符/change事件/filter
<input v-model.number="age" type="number">
<input v-model.trim="msg">

给表单绑定事件,在事件处理中进行表单输入控制
<input v-model="value2" type="text" @change="inputChange(value2)" />

methods: {
  inputChange: function(val) {
    if (!val) return ''
    val = val.toString()
    this.value2 = val.charAt(0).toUpperCase() + val.slice(1)
  }
}

filter:
<input v-model="value1"  type="text" />

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

watch: {
  value1(val) {
     this.value1 = this.$options.filters.capitalize(val);
  }
}

  • 事件:特殊变量 $event

有时候,我们绑定事件后,想传入除了原生事件对象之外的其他参数

<!-- 注意这里调用的方法有两个参数 -->
<input v-model="value1" @change="inputChange('hello', $event)">

methods: {
  inputChange(msg, e) {
    console.log(msg, e);
  }
}
在自定义事件中,$event 是从其子组件中捕获的值

场景:你想监听 el-input 的传递过来的值的同时,传递其他的参数。

<el-input
      v-model="value2"
      @change="change($event, 'hello')"
      placeholder="Input something here"
    />
methods: {
  change(e, val) {
    console.log("event is " + e); // el-input 输入的值
    console.log(val); // hello
  }
}

  • 调试template
// 这里最好是判断一下,只有在测试环境中才使用
// main.js
Vue.prototype.$log = window.console.log;

// 组件内部
<div>{{$log(info)}}</div>

继续肝

  • require.context()

实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用 require.context(directory,useSubdirectories,regExp) 接收三个参数: directory:说明需要检索的目录 useSubdirectories:是否检索子目录 regExp: 匹配文件的正则表达式,一般是文件名

const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
  const name = path.basename(key, '.vue')
  modules[name] = files(key).default || files(key)
})
components:modules

  • .sync
// 父组件
<home :title.sync="title" />
//编译时会被扩展为
<home :title="title"  @update:title="val => title = val"/>

// 子组件
// 所以子组件可以通过$emit 触发 update 方法改变
mounted(){
  this.$emit("update:title", '这是新的title')
}

  • .EventBus
1.就是声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上; 2.类似于 Vuex。但这种方式只适用于极小的项目 3.原理就是利用onon和emit 并实例化一个全局 vue 实现数据共享


// 在 main.js
Vue.prototype.$eventBus=new Vue()

// 传值组件
this.$eventBus.$emit('eventTarget','这是eventTarget传过来的值')

// 接收组件
this.$eventBus.$on("eventTarget",v=>{
  console.log('eventTarget',v);//这是eventTarget传过来的值
})

  • 路由传参

三种方案对比 方案二参数不会拼接在路由后面,页面刷新参数会丢失 方案一和三参数拼接在后面,丑,而且暴露了信息

// 路由定义
{
  path: '/describe/:id',
  name: 'Describe',
  component: Describe
}
// 页面传参
this.$router.push({
  path: `/describe/${id}`,
})
// 页面获取
this.$route.params.id

// 路由定义
{
  path: '/describe',
  name: 'Describe',
  component: Describe
}
// 页面传参
this.$router.push({
  name: 'Describe',
  params: {
    id: id
  }
})
// 页面获取
this.$route.params.id

// 路由定义
{
  path: '/describe',
  name: 'Describe',
  component: Describe
}
// 页面传参
this.$router.push({
  path: '/describe',
    query: {
      id: id
  `}
)
// 页面获取
this.$route.query.id

  • 路由按需加载
{
  path:'/',
  name:'home',
  components:()=>import('@/components/home')
}

import()方法由es6提出,import()方法是动态加载,返回一个Promise对象,then方法的参数是加载到的模块。
类似于Node.jsrequire方法,主要import()方法是异步加载的。

  • 动态组件
<transition>
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
</transition>
  • components和 Vue.component
components:局部注册组件

export default{
  components:{home}
}

Vue.component:全局注册组件
Vue.component('home',home)
  • Vue.extend

组件构造器

// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{extendData}}</br>实例传入的数据为:{{propsExtend}}</p>',//template对应的标签最外层必须只有一个标签
  data: function () {
    return {
      extendData: '这是extend扩展的数据',
    }
  },
  props:['propsExtend']
})

// 创建的构造器可以挂载到元素上,也可以通过 components 或 Vue.component()注册使用
// 挂载到一个元素上。可以通过propsData传参.
new Profile({propsData:{propsExtend:'我是实例传入的数据'}}).$mount('#app-extend')

// 通过 components 或 Vue.component()注册
Vue.component('Profile',Profile)

  • mixins
场景:有些组件有些重复的 js 逻辑,如校验手机验证码,解析时间等,mixins 就可以实现这种混入 mixins 值是一个数组
const mixin = {
  created(){
    this.dealTime()
  },
  methods:{
    dealTime(){
      console.log('这是mixin的dealTime里面的方法')
    }
  }
}
export default{
  mixins:[mixin]
}
  • Vue.vue()

场景:我们使用element时会先import,再Vue.use()一下,实际上就是注册组件,触发install方法,这个在组件调用经常使用到;会自动组织多次注册相同的插件

  • install
var MyPlugin = {};
  MyPlugin.install = function (Vue, options) {
    // 2. 添加全局资源,第二个参数传一个值默认是update对应的值
    Vue.directive('click', {
      bind(el, binding, vnode, oldVnode) {
        //做绑定的准备工作,添加时间监听
        console.log('指令my-directive的bind执行啦');
      },
      inserted: function(el){
      //获取绑定的元素
      console.log('指令my-directive的inserted执行啦');
      },
      update: function(){
      //根据获得的新值执行对应的更新
      //对于初始值也会调用一次
      console.log('指令my-directive的update执行啦');
      },
      componentUpdated: function(){
      console.log('指令my-directive的componentUpdated执行啦');
      },
      unbind: function(){
      //做清理操作
      //比如移除bind时绑定的事件监听器
      console.log('指令my-directive的unbind执行啦');
      }
    })

    // 3. 注入组件
    Vue.mixin({
      created: function () {
        console.log('注入组件的created被调用啦');
        console.log('options的值为',options)
      }
    })

    // 4. 添加实例方法
    Vue.prototype.$myMethod = function (methodOptions) {
      console.log('实例方法myMethod被调用啦');
    }
  }

  //调用MyPlugin
  Vue.use(MyPlugin,{someOption: true })

  //3.挂载
  new Vue({
    el: '#app'
  });

  • Vue.nextTick

2.1.0 新增 场景:页面加载时需要让文本框获取焦点 用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

mounted(){ //因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
  this.$nextTick(() => {
    this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
  })
}
  • vue.derective

场景:官方给我们提供了很多指令,但是我们如果想将文字变成指定的颜色定义成指令使用,这个时候就需要用到Vue.directive

1.bind 只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。 2.inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中) 3.update: 被绑定与元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新 4.componentUpdate :被绑定的元素所在模板完成一次更新更新周期的时候调用 5.unbind: 只调用一次,指令月元素解绑的时候调用

// 全局定义
Vue.directive("change-color",function(el,binding,vnode){
  el.style["color"]= binding.value;
})

// 使用
<template>
<div v-change-color=“color”>{{message}}</div>
</template>
<script>
  export default{
    data(){
      return{
        color:'green'
      }
    }
  }
</script>

  • vue.filter
// 使用
// 在双花括号中
{{ message | capitalize }}

// 在 `v-bind` 中
<div v-bind:id="rawId | formatId"></div>

// 全局注册
Vue.filter('stampToYYMMDD', (value) =>{
  // 处理逻辑
})

// 局部注册
filters: {
  stampToYYMMDD: (value)=> {
    // 处理逻辑
  }
}

// 多个过滤器全局注册
// /src/common/filters.js
let dateServer = value => value.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3') 
export { dateServer }
// /src/main.js
import * as custom from './common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]))


  • 事件修饰符

.stop:阻止冒泡 .prevent:阻止默认行为 .self:仅绑定元素自身触发 .once: 2.1.4 新增,只触发一次 .passive: 2.3.0 新增,滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用

.native

.capture

  • 缓存和动画
//或include="a,b" :include="/a|b/",a 和 b 表示组件的 name //因为有些页面,如试试数据统计,要实时刷新,所以就不需要缓存 //路由标签 // c 表示组件的 name值

注:匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配

  • 全局路由钩子
// 1.router.beforeEach

router.beforeEach((to, from, next) => {
  console.log('全局前置守卫:beforeEach -- next需要调用') //一般登录拦截用这个,也叫导航钩子守卫
  if (path === '/login') {
    next()
    return
  }
  if (token) {
    next();
  } 
})

  • 组件路由钩子

1.beforeRouteEnter 在渲染该组件的对应路由被确认前调用,用法和参数与router.beforeEach类似,next需要被主动调用 此时组件实例还未被创建,不能访问this 可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数

beforeRouteEnter (to, from, next) {
  // 这里还无法访问到组件实例,this === undefined
  next( vm => {
    // 通过 `vm` 访问组件实例
  })
}

2.beforeRouteUpdate (v 2.2+) 在当前路由改变,并且该组件被复用时调用,可以通过this访问实例, next需要被主动调用,不能传回调

3.beforeRouteLeave 导航离开该组件的对应路由时调用,可以访问组件实例 this,next需要被主动调用,不能传回调

  • Vue.routerVue.router Vue.route
this.$router.push():跳转到不同的url,但这个方法回向history栈添加一个记录,点击后退会返回到上一个页面
this.$router.replace():不会有记录
this.$router.go(n):n可为正数可为负数。正数前进, 负数后退,类似 window.history.go(n)

表示当前跳转的路由对象,属性有: name:路由名称 path:路径 query:传参接收值 params:传参接收值 fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径 matched:路由记录副本 redirectedFrom:如果存在重定向,即为重定向来源的路由的名字

this.route.params.id:获取通过params/:id传参的参数this.route.params.id:获取通过 params 或/:id传参的参数 this.route.query.id:获取通过 query 传参的参数

  • router-view 的 key

场景:由于 Vue 会复用相同组件, 即 /page/1 => /page/2 或者 /page?id=1 => /page?id=2 这类链接跳转时, 将不在执行created, mounted之类的钩子

<router-view :key="$route.fullPath"></router-view>

这样组件的 created 和 mounted 就都会执行

  • vue-loader

它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。简而言之,webpack 和 Vue Loader 的结合为你提供了一个现代、灵活且极其强大的前端工作流,来帮助撰写 Vue.js 应用。

  • img 加载失败

场景:有些时候后台返回图片地址不一定能打开,所以这个时候应该加一张默认图片

// page 代码
<img :src="imgUrl" @error="handleError" alt="">
<script>
export default{
  data(){
    return{
      imgUrl:''
    }
  },
  methods:{
    handleError(e){
      e.target.src=reqiure('图片路径') //当然如果项目配置了transformToRequire,参考上面 33.2
    }
  }
}
</script>

  • 基础指令

v-if,v-else,v-show,v-for,v-text,v-html,v-on,v-model,v-bind v-pre,v-clock,v-once

  • vue-loader是什么?使用它的用途有哪些?

vue 文件的一个加载器,将 template/js/style 转换成 js 模块。

用途:js 可以写 es6style样式可以 scsslesstemplate 可以加 jade