Skip to content

Vue2 基础 ​

Vue 实例 ​

创建 Vue 实例 ​

js
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  methods: {
    greet() {
      console.log(this.message)
    }
  }
})
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  methods: {
    greet() {
      console.log(this.message)
    }
  }
})

实例选项 ​

  • el — 挂载的 DOM 元素选择器
  • data — 组件数据,必须是函数(组件中)或对象(根实例)
  • methods — 组件方法,this 指向组件实例
  • computed — 计算属性,基于依赖缓存结果
  • watch — 侦听属性,监听数据变化执行操作

实例属性和方法 ​

通过 vm.$xxx 访问实例属性和方法:

js
vm.$data        // 组件 data 对象
vm.$props       // 接收的 props
vm.$el          // 挂载的 DOM 元素
vm.$options     // 初始化选项
vm.$parent      // 父组件实例
vm.$root        // 根组件实例
vm.$children    // 子组件实例数组
vm.$refs        // 持有注册了 ref 的 DOM 元素或组件
vm.$slots       // 插槽内容
vm.$attrs       // 未被 props 接收的属性
vm.$listeners   // 父组件传递的事件(Vue2)

vm.$watch       // 侦听数据变化
vm.$set         // 向响应式对象添加属性
vm.$delete      // 删除对象的响应式属性
vm.$emit        // 触发自定义事件
vm.$forceUpdate // 强制更新
vm.$nextTick    // DOM 更新后执行回调
vm.$destroy     // 销毁实例
vm.$data        // 组件 data 对象
vm.$props       // 接收的 props
vm.$el          // 挂载的 DOM 元素
vm.$options     // 初始化选项
vm.$parent      // 父组件实例
vm.$root        // 根组件实例
vm.$children    // 子组件实例数组
vm.$refs        // 持有注册了 ref 的 DOM 元素或组件
vm.$slots       // 插槽内容
vm.$attrs       // 未被 props 接收的属性
vm.$listeners   // 父组件传递的事件(Vue2)

vm.$watch       // 侦听数据变化
vm.$set         // 向响应式对象添加属性
vm.$delete      // 删除对象的响应式属性
vm.$emit        // 触发自定义事件
vm.$forceUpdate // 强制更新
vm.$nextTick    // DOM 更新后执行回调
vm.$destroy     // 销毁实例

模板语法 ​

插值 ​

vue
<template>
  <div>
    <!-- 文本插值 -->
    <p>{{ message }}</p>
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>

    <!-- 原始 HTML -->
    <span v-html="rawHtml"></span>

    <!-- 属性绑定 -->
    <img :src="imageSrc" :alt="altText">
    <div :class="{ active: isActive }"></div>

    <!-- 动态属性名 -->
    <button :[key]="value">动态属性</button>
  </div>
</template>
<template>
  <div>
    <!-- 文本插值 -->
    <p>{{ message }}</p>
    <p>{{ number + 1 }}</p>
    <p>{{ ok ? 'YES' : 'NO' }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>

    <!-- 原始 HTML -->
    <span v-html="rawHtml"></span>

    <!-- 属性绑定 -->
    <img :src="imageSrc" :alt="altText">
    <div :class="{ active: isActive }"></div>

    <!-- 动态属性名 -->
    <button :[key]="value">动态属性</button>
  </div>
</template>

表达式限制 ​

每个绑定只能包含单个表达式,以下情况不支持:

js
// 错误:这不是表达式,是语句
{{ var a = 1 }}

// 错误:流程控制也不支持
{{ if (ok) { return message } }}
// 错误:这不是表达式,是语句
{{ var a = 1 }}

// 错误:流程控制也不支持
{{ if (ok) { return message } }}

data — 数据 ​

组件中的 data 必须是函数 ​

js
// 组件中
export default {
  data() {
    return {
      count: 0,
      user: { name: '张三', age: 18 }
    }
  }
}
// 组件中
export default {
  data() {
    return {
      count: 0,
      user: { name: '张三', age: 18 }
    }
  }
}

响应式原理 ​

Vue2 通过 Object.defineProperty 将 data 的属性转为 getter/setter,因此:

  • 对象:只有初始化时存在的属性才是响应式的
  • 数组:通过索引直接修改元素或修改 length 不会触发更新
js
// 对象 — 需要用 $set 添加新属性
this.$set(this.obj, 'newKey', 'value')
// 或
this.obj = { ...this.obj, newKey: 'value' }

// 数组 — 需要用变异方法或 $set
this.$set(this.list, index, newValue)
this.list.splice(index, 1, newValue)
// 对象 — 需要用 $set 添加新属性
this.$set(this.obj, 'newKey', 'value')
// 或
this.obj = { ...this.obj, newKey: 'value' }

// 数组 — 需要用变异方法或 $set
this.$set(this.list, index, newValue)
this.list.splice(index, 1, newValue)

methods — 方法 ​

vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="add(10)">+10</button>
  </div>
</template>

<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    },
    add(num) {
      this.count += num
    }
  }
}
</script>
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="add(10)">+10</button>
  </div>
</template>

<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    },
    add(num) {
      this.count += num
    }
  }
}
</script>

注意: methods 中的方法不要使用箭头函数,会导致 this 无法指向组件实例。

computed — 计算属性 ​

计算属性基于依赖缓存,只有当依赖发生变化时才会重新计算。

vue
<template>
  <div>
    <p>原价:{{ price }}</p>
    <p>折扣价:{{ discountPrice }}</p>
    <p>反转文字:{{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      price: 100,
      message: 'Hello'
    }
  },
  computed: {
    // 简写:只有 getter
    discountPrice() {
      return this.price * 0.8
    },
    reversedMessage() {
      return this.message.split('').reverse().join('')
    },
    // 完整写法:包含 getter 和 setter
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(val) {
        const names = val.split(' ')
        this.firstName = names[0]
        this.lastName = names[1]
      }
    }
  }
}
</script>
<template>
  <div>
    <p>原价:{{ price }}</p>
    <p>折扣价:{{ discountPrice }}</p>
    <p>反转文字:{{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      price: 100,
      message: 'Hello'
    }
  },
  computed: {
    // 简写:只有 getter
    discountPrice() {
      return this.price * 0.8
    },
    reversedMessage() {
      return this.message.split('').reverse().join('')
    },
    // 完整写法:包含 getter 和 setter
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(val) {
        const names = val.split(' ')
        this.firstName = names[0]
        this.lastName = names[1]
      }
    }
  }
}
</script>

computed vs methods ​

特性computedmethods
缓存基于依赖缓存每次调用都执行
调用方式作为属性使用 &#123;&#123; reversedMsg &#125;&#125;作为方法调用 &#123;&#123; reverseMsg() &#125;&#125;
返回值必须 return可选
异步不支持支持

watch — 侦听器 ​

当数据变化时执行异步或开销较大的操作。

vue
<template>
  <div>
    <input v-model="question">
    <input v-model="obj.deep.nested">
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      obj: { deep: { nested: '' } }
    }
  },
  watch: {
    // 基本用法
    question(newVal, oldVal) {
      console.log(`从 ${oldVal} 变为 ${newVal}`)
      this.fetchAnswer()
    },
    // 监听对象内部属性(需要深监听)
    obj: {
      handler(newVal, oldVal) {
        console.log('obj 变化了', newVal)
      },
      deep: true,    // 深度监听
      immediate: true // 初始化时立即执行一次
    },
    // 监听对象的具体属性
    'obj.deep.nested'(newVal, oldVal) {
      console.log('nested 变化了', newVal)
    }
  },
  methods: {
    fetchAnswer() {
      setTimeout(() => {
        console.log('请求答案...')
      }, 1000)
    }
  }
}
</script>
<template>
  <div>
    <input v-model="question">
    <input v-model="obj.deep.nested">
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      obj: { deep: { nested: '' } }
    }
  },
  watch: {
    // 基本用法
    question(newVal, oldVal) {
      console.log(`从 ${oldVal} 变为 ${newVal}`)
      this.fetchAnswer()
    },
    // 监听对象内部属性(需要深监听)
    obj: {
      handler(newVal, oldVal) {
        console.log('obj 变化了', newVal)
      },
      deep: true,    // 深度监听
      immediate: true // 初始化时立即执行一次
    },
    // 监听对象的具体属性
    'obj.deep.nested'(newVal, oldVal) {
      console.log('nested 变化了', newVal)
    }
  },
  methods: {
    fetchAnswer() {
      setTimeout(() => {
        console.log('请求答案...')
      }, 1000)
    }
  }
}
</script>

简写形式 ​

js
watch: {
  // 无其他配置项时可直接写函数
  question(newVal, oldVal) {
    console.log(newVal)
  }
}

// 或者使用 vm.$watch
vm.$watch('question', (newVal, oldVal) => {
  console.log(newVal)
}, { deep: true, immediate: true })
watch: {
  // 无其他配置项时可直接写函数
  question(newVal, oldVal) {
    console.log(newVal)
  }
}

// 或者使用 vm.$watch
vm.$watch('question', (newVal, oldVal) => {
  console.log(newVal)
}, { deep: true, immediate: true })

computed 与 watch 的选择 ​

  • computed:依赖其他值计算出新值,有缓存
  • watch:数据变化时执行异步操作或复杂逻辑
js
// 使用 computed(推荐)
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

// 使用 watch(适合异步)
watch: {
  firstName(val) {
    setTimeout(() => {
      this.fullName = val + ' ' + this.lastName
    }, 1000)
  }
}
// 使用 computed(推荐)
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

// 使用 watch(适合异步)
watch: {
  firstName(val) {
    setTimeout(() => {
      this.fullName = val + ' ' + this.lastName
    }, 1000)
  }
}

其他实例方法 ​

$nextTick ​

数据变化后等待 DOM 更新完成再执行操作。

js
export default {
  data() {
    return { message: 'Hello' }
  },
  methods: {
    updateAndLog() {
      this.message = 'Updated'
      // DOM 还未更新
      console.log(document.querySelector('.msg').textContent) // 还是 'Hello'

      this.$nextTick(() => {
        // DOM 已更新
        console.log(document.querySelector('.msg').textContent) // 'Updated'
      })
    }
  }
}
export default {
  data() {
    return { message: 'Hello' }
  },
  methods: {
    updateAndLog() {
      this.message = 'Updated'
      // DOM 还未更新
      console.log(document.querySelector('.msg').textContent) // 还是 'Hello'

      this.$nextTick(() => {
        // DOM 已更新
        console.log(document.querySelector('.msg').textContent) // 'Updated'
      })
    }
  }
}

$forceUpdate ​

强制 Vue 实例重新渲染。需要手动强制更新的场景很少,通常说明数据设计有问题。

js
this.$forceUpdate()
this.$forceUpdate()

Vue.set / this.$set ​

向响应式对象添加新属性,确保新属性也是响应式的。

js
// 添加对象属性
this.$set(this.user, 'age', 18)

// 修改数组项
this.$set(this.list, 0, { name: 'new' })

// 全局方式
Vue.set(this.user, 'age', 18)
// 添加对象属性
this.$set(this.user, 'age', 18)

// 修改数组项
this.$set(this.list, 0, { name: 'new' })

// 全局方式
Vue.set(this.user, 'age', 18)

Vue.delete / this.$delete ​

删除对象的属性,触发视图更新。

js
this.$delete(this.user, 'age')
this.$delete(this.user, 'age')