Vue学习笔记(一)

一、Vue实例

new Vue(options)

每个 Vue 实例与容器一一对应,并且在真实开发中往往只有一个 Vue 实例,一个 Vue 实例配合对个 组件实例 使用,可以将 Vue 实例称为 vm ( MVVM中的VM ),组件实例称为 vc ( VueComponent )

1.Vue 工作准备

  • 引入 Vue
  • new Vue(options) 创建实例,options 是一个配置对象

2. options 配置对象

  • el (挂载点)

    1. ‘#root’,挂载的容器的 css 选择器
    2. doucment.querySelect(’#root’),挂载的容器 dom 元素

    注意: 在创建实例时可以不设定 el 属性,可以通过手动调用 Vue 提供的一个 api 方法 vm.$mount(el),传入 el 的值,并手动挂载。若在配置项中添加 el 属性,在 Vue 底层实现中会帮我们调用这个方法

  • data 数据对象

    1. 对象式

    2. 函数式

      比较:

    3. 对象式的 data 属性在组件复用时可能会出现数据之间存在引用关系的情况;

    // Student
    export default {
    data: {
      name: '小王',
      age:18
    },
    methods: {
      changeAge() {
        this.age = 20
      }
    }
    }
    // Course1
    import student from Student
    student.methods.changeAge = 20
    // Course2
    import student from Student
    console.log(student.data.age) // 20
    

    如上述代码,在其他组件引入同一个组件时,如果 data 属性采用的是对象式的方式,则引入该组件的其他组件获得的是同一个 data 对象,因此就可能因为引用关系造成数据的污染。

    **注意:**之所以其他组件引入该组件时获取是同一个引用,是因为 ES6 语法的 import 拿到的其实是一个只读引用(类似于 Linux 中的 ‘符号链接’),每当模块中的原始值改变时 import 加载的值也会随之改变。

    1. 函数式的 data 属性,在 Vue 底层获取配置对象上用户定义的数据时会调用 data 函数,并且每次调用 data 函数返回的都是一个新的拷贝

      // Student
      export default {
        data(){
          return {
            name: '小王',
            age:18
          }
        },
        methods: {
          changeAge() {
            this.age = 20
          }
        }
      }
      // Course1
      import student from Student
      student.methods.changeAge = 20
      
      // Course2
      import student from Student
      console.log(student.data.age) // 18
      

    如上述代码,在其他组件引入同一个组件时,如果 data 属性采用的是函数式的方式,则引入该组件的其他组件获得的是 data 对象的一个拷贝(深拷贝),因此不会出现因为复用组件导致数据污染的情况。

3. Vue 中的 this 指向

  • 在 Vue 中,只要是有 Vue 管理的函数都不能写成箭头函数(箭头函数的特性),需要写成普通函数的形式;

  • 因为 Vue 中默认帮我们绑定了函数的 this 指向为当前实例对象,为了访问到定义在配置对象上的属性和方法,需要 this 指向为当前的实例对象。

  • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

    注意: 实例对象分为根实例对象和组件实例对象,根实例对象是 new Vue() 得到的实例对象,而组件实例对象是我们定义配置属性或调用 Vue.extend 这个 api 后并’注册使用’而得到的,详情见后面的非单文件组件与单文件组件。

二、指令

v-bind:简写:’:’

单向数据绑定,数据只能从 data 流向页面;

  • 扩展

    • 绑定样式

      • 绑定 class

        写法 :class=“xxx” xxx 可以是字符串、对象、数组。

        ​ 字符串写法适用于:类名不确定,要动态获取。

        ​ 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。

        ​ 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

      • 绑定 style

        写法 :style="{fontSize: xxx}" 其中 xxx 是动态值。

        ​ :style="[a,b]" 其中a、b是样式对象。\

    • 示例

      <template>
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="mood">{{name}}</div> <br/><br/>
        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="['atguigu1','atguigu2','atguigu3']">{{name}}</div> <br/><br/>
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="{
                atguigu1:false,
                atguigu2:false,
              }">{{name}}</div> <br/><br/>
        <!-- 绑定style样式--对象写法 -->
        <div class="basic" :style="{
                fontSize: '40px',
                color:'red',
              }">{{name}}</div> <br/><br/>
        <!-- 绑定style样式--数组写法 -->
        <div class="basic" :style=":[
                {
                  fontSize: '40px',
                  color:'blue',
                },
                {
                  backgroundColor:'gray'
                }
              ]">{{name}}</div>
      </template>
      <script>
      	export default {
            data:{
              name:'尚硅谷',
              mood:'normal',
            },
      	}
      </script>
      <style>
        .normal{
          background-color: skyblue;
        }
        .atguigu1{
          background-color: yellowgreen;
        }
        .atguigu2{
          font-size: 30px;
          text-shadow:2px 2px 10px red;
        }
        .atguigu3{
          border-radius: 20px;
        }
      </style>
      

v-model:

双向数据绑定,数据不仅能从data流向页面,还可以从页面流向data

  • 双向数据绑定往往用在表单类元素上(input、form、select),因为往往只有表单类元素需要有数据的交互传递

  • v-model:value 简写形式为 v-model,因为 v-model 默认收集的为value 值

  • 收集表单数据:

    ​ 若:<input type="text"/>,则 v-model 收集的是 value 值,用户输入的就是 value 值。

    ​ 若:<input type="radio"/>,则 v-model 收集的是 value 值,且要给标签配置 value 值。

    ​ 若:<input type="checkbox"/>

    ​ 1.没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)

    ​ 2.配置input的value属性:

    ​ (1) v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)

    ​ (2) v-model 的初始值是数组,那么收集的的就是 value 组成的数组

    ​ 备注:v-model的三个修饰符:

    ​ lazy:失去焦点再收集数据

    ​ number:输入字符串转为有效的数字

    ​ trim:输入首尾空格过滤

v-on:简写:’@’

  • methods 属性中定义的方法需写成普通函数的形式,否则 this 指向不为当前实例

  • @click=“demo($event, params)”,既想获取事件对象,又想传入参数的格式

  • 事件修饰符

    1. prevent:阻止默认事件(常用),如页面跳转、表单提交,原生采用 e.preventDefault()阻止 ;

    2. stop:阻止事件冒泡(常用),原生采用 e.stopPropagation() 阻止;

    3. once:事件只触发一次(常用);

    4. capture:使用事件的捕获模式 ;

      事件分为捕获、冒泡两阶段,即从元素外层到内层、内层到外层触发事件的两阶段。

    5. self:只有 event.target 是当前操作的元素时才触发事件;

      通过 self 也能实现 stop 能实现的功能,不过代码更为繁琐。

    6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

      如拖动滚动条的事件,一种是边拖动边触发事件,另一种是执行完之间回调再移动拖动。第二种事件如果不处理可能会导致页面卡死的情况。

  • 键盘事件

    1. Vue中常用的按键别名:

    ​ 回车 => enter

    ​ 删除 => delete (捕获“删除”和“退格”键)

    ​ 退出 => esc

    ​ 空格 => space

    ​ 换行 => tab (特殊,必须配合keydown去使用)

    ​ 上 => up

    ​ 下 => down

    ​ 左 => left

    ​ 右 => right

    1. Vue 未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为 kebab-case(短横线命名)

      如, caps-lock(大小写切换)

    2. 系统修饰键(用法特殊):ctrl、alt、shift、meta

    ​ (1).配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。

    ​ @ctrl.keyup.1

    ​ (2).配合 keydown 使用:正常触发事件。

    1. 也可以使用keyCode去指定具体的按键(不推荐)

      因为不同浏览器所的 keyCode 规格可能不同,并且 keyCode 已经被废除,请采用 key|code

    2. Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

v-if、v-else-if、v-else:

不满足条件直接移除 dom 元素,三者可一起使用,但是结构不能 “打断” 必须连续

注意: template 标签仅能配合 v-if 使用,不能配合 v-show

v-show:

适用于切换频率较高场景,dom 元素未移除,仅仅是将 display:none

v-for:

  1. 用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key=“yyy”
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

v-text:

  • 作用:向其所在的节点中渲染文本内容
  • 与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

v-html;

  • 作用:向指定节点中渲染包含html结构的内容。

  • 与插值语法的区别:

    1. v-html会替换掉节点中所有的内容,{{xx}}则不会。
    2. .v-html可以识别html结构。
  • 注意:v-html有安全性问题

    1. 在网站上动态渲染任意HTML是非常危险的,容易导致 XSS 攻击。
    2. 一定要在可信的内容上使用 v-html ,永不要用在用户提交的内容上!

v-cloak:

  • 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性
  • 使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题,即加载速度过慢。

v-once:

  • v-once 所在节点在初次动态渲染后,就视为静态内容了。
  • 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。

v-pre:

  • 跳过其所在节点的编译过程。
  • 可利用它跳过没有使用指令语法、没有使用插值语法的节点,会加快编译。

自定义指令

  • 定义语法:

    (1).局部指令:

    ​ new Vue({ directives:{ 指令名:配置对象 } })

​ new Vue({ directives{ 指令名:回调函数 } })

​ (2).全局指令:

​ Vue.directive ( 指令名,配置对象 ) 、Vue.directive ( 指令名,回调函数 )

  • 配置对象中常用的3个回调:

​ (1).bind:指令与元素成功绑定时调用。

​ (2).inserted:指令所在元素被插入页面时调用。

​ (3).update:指令所在模板结构被重新解析时调用。

  • 备注:
  1. 指令定义时不加v-,但使用时要加v-;
  2. 指令名如果是多个单词,要使用连字符命名方式,不要用小驼峰命名。

三、计算属性和侦听属性

(1)、 计算属性

写法:

  • computedName() { }
  • computedName:{ get(){ }, set(){ } }

**原理:**底层运用了 Object.defineProperty 方法定义 getter/settet

执行时机:

  • 初始读取计算属性时执行一次
  • 依赖数据发生改变时执行

优点: 与 methods 相比,内部有缓存机制,当模板上多次复用时,效率更高

**注意:**如果计算属性要被修改,那必须编写 setter 去响应修改,且 set 中要引起计算时依赖的数据发生改变。

(2)、侦听属性

写法:

  • watchedPropertyName(){ }
  • watchedPropertyName: { handler(){ }, immediate, deep }

**通过api 定义:**vm.$watch(watchedPropertyName, function | option)

执行时机:

被侦听的属性变化时, 回调函数自动调用;如果 immediate 属性值为 true ,则在初次解析模板时也会调用

注意: 被侦听的属性必须存在,才能进行侦听 ( 编写时注意属性名 ) ;

​ 如果侦听一个对象中的某个属性 ( 即侦听多级结构中某个属性的变化 ),采用 ‘对象名.属性名’ 的格式;

​ 计算属性能实现的,用侦听属性也一定能实现;

​ watch 能完成的功能,computed 不一定能完成。例如:watch 可以进行异步操作,而 computed 不能异步操作,

​ 因为 computed 必须有返回值,并不能实现等待 500ms 再返回这样的操作,因为返回的值并不是在最外层的函数返回。

四、生命周期

又名:生命周期回调函数、生命周期函数、生命周期钩子。

常用的生命周期钩子:

​ 1.mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。

​ 2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例

​ 1.销毁后借助 Vue 开发者工具看不到任何信息。

​ 2.销毁后自定义事件会失效,但原生DOM事件依然有效。

​ 3.一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。

在这里插入图片描述

五、非单文件组件与单文件组件

  • 组件注册三大步骤
    1. 创建组件
    2. 注册组件(注册前需引入)
    3. 使用组件

非单文件组件

​ 通常来说,在一个 html 文件中创建多个组件并引入,这就是一个非单文件组件。

创建组件:

​ 如果需要用命令式的方法创建组件可以通过 Vue 提供的一个 API 创建组件 Vue.extend(options)

​ 也可以直接编写一个配置对象,在 Vue 解析模板时会自动调用 Vue.extend 并自动 new 实例

Vue.extend

/**
* extend 会生成一个子组件的构造函数,并且每次调用时生成的构造函数都是不同的
*/
Vue.extend = function (extendOptions: Object): Function {
  	......
    const Sub = function VueComponent (options) { // 构造子组件的构造函数
      this._init(options) // 一旦构造实例,就开始进行像根实例root一样的初始化流程
    }
    // 子组件继承 Vue,因此子组件能够访问到 Vue原型 上的属性和方法
    // 即 Sub.prototype.__proto__ === Vue.prototype
    // console.log(Sub.prototype.__proto__ === Vue.prototype) // false
    Sub.prototype = Object.create(Super.prototype)
    // console.log(Sub.prototype.__proto__ === Vue.prototype) // false
    Sub.prototype.constructor = Sub	// 让 constructor 指向构造函数
    Sub.cid = cid++ // 构造函数的编号
    Sub.options = mergeOptions( // 合并父子组件的选项
      Super.options,
      extendOptions
    )
    Sub['super'] = Super  // 标记子组件的父亲
	......
    return Sub
}

​ 从上述代码可以发现一个重要的内置关系:VueComponent.prototype._proto_ === Vue.prototype

​ 这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

VueComponent:

​ 1. 通过 Vue.extend 创建的组件,本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是Vue.extend 生成的。

​ 2. 我们只需要写<school/><school></school>,Vue 解析时会帮我们创建各个组件的实例对象, 即 Vue 帮我们执行 new VueComponent(options) 这条语句 。

​ 3. 特别注意:每次调用 Vue.extend ,返回的都是一个全新的 VueComponent

​ 4.this指向:

​ (1).组件配置中:

​ data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。

​ (2).new Vue(options)配置中:

​ data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

​ 5. VueComponent 的实例对象,简称vc(也可称之为:组件实例对象)。

​ 6.如果只是在实例上注册组件,但是在模板中并未使用,则该实例所管理的组件中,并不会存在仅注册未使用的组件

new Vue(options) 和 Vue.extend(options) 的 options 区别:

​ 1、el不要写 —— 最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器。

​ 2、data必须写成函数 ——— 避免组件被复用时,数据存在引用关系。

注册组件:

​ 在注册前需要通过 import 引入组件

​ - 局部注册:在 options 中配置 components 属性,components: { 组件 }

​ - 全局注册:Vue.component(‘组件名’, 组件)

注意事项:

  • 组件名

    • 一个单词组成:

    ​ 第一种写法(首字母小写):school

    ​ 第二种写法(首字母大写):School

    • 多个单词组成:

      第一种写法(kebab-case命名):my-school

      第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

    • 注意点:

      (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。

      (2).可以在配置对象中使用 name属性指定组件在开发者工具中呈现的名字。

单文件组件

​ 每一个 .vue 文件都是一个单文件组件;

​ 在实际开发中,往往会创建一个名为 App 的组件来管理所有的其他组件,该组件仅下属于 Root 组件;并且其他子组件都有 App 组件管理

​ 一个单文件组件包括三大部分: template (模板)、script (脚本) 、 style (样式)

// Demo,一般来说单文件组件的组件名采用大驼峰是更为直观
<template>
  <div>
    // html 结构
  </div>
</template>
<script>
  // 这里使用默认导出,在导入的使用便可以直接通过 import 组件名 from 'path' 的方法,这样的方式比分别导出和统一导出都需要多加一个 {} 更为麻烦,不过也可行
  // 我们通过默认导出,导出一个配置对象, Vue 在底层会将这个 option 传入 Vue.extend(opitons) 得到一个 VueCompoment 构造函数,并在解析模板时自动调用创建组件实例
  // 当然也可以直接默认导出一个构造函数 export default Vue.extend(options)
  export default {	
    name: 'demo',	// 通过这个名字能控制 Vue 工具中显示的名字
  }
</script>
<style>
	// 样式
</style>

六、原理

1. 数据代理

定义:通过一个对象代理对另一个对象中属性的(读/写)操作

  1. Vue中的数据代理:

​ 通过 vm 对象来代理 data 对象中属性的(读/写)操作

  1. Vue中数据代理的好处:

​ 更加方便的操作 data 中的数据

  1. 基本原理:

​ 通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上。

​ 为每一个添加到 vm 上的属性,都指定一个 getter/setter , getter/setter 其实在定义响应式数据时就已创建。

​ Vue 内部代码中的数据代理只是对已创建的 getter/setter 的包装和定义。

Object.defineProterty

// Object.defineProterty(targetObj, 'protertyName', descriptor)
// 具体应用
let student = {} 
Object.defineProterty(student, 'age', {
  value: 18,
  enumerable: true,	// 属性是否可迭代(for of 遍历,keys取键),注意:该值为默认值(false)时在浏览器中会比其他属性颜色浅(chrome为浅粉色),表明不可迭代
  writable: true,	// 属性是否可修改
  configurable: true,	// 属性是否可删除
})
// Object.defineProterty(targetObj, 'protertyName', descriptor)
// 具体应用
let number = 19
let student = {}
Object.defineProterty(student, 'age', {
	get() {	// student.age 时调用(获取属性值)
      return number
	},
  	set(v) {	// student.age = 18修改属性值时调用
      console.log(`age修改${v}`)
      number = v	// 注意这里修改的是number而不是age,age并不存在,age属性只是代理了number
  	}
})
console(student.age)	// 19
student.age = 20
console(studen.age, number)	// 20,20

通过Object.defineDescriptor定义属性的getter和setter进行数据拦截,Vue2的响应式数据代理底层就是通过这个方法进行拦截。

proxy

/**
 * 实现代理的函数,包装(调用已有的 getter/setter 方法)属性的 getter/setter 方法
 * 通过 proxy 用户可以直接通过 this.属性名 的方式
 * 获取到在 data/props 定义的相应属性的属性值
 * 在目标对象上代理属性
 * @param {*} target
 * @param {*} sourceKey
 * @param {*} key
 */
export function proxy (target: Object, sourceKey: string, key: string) {
  // 添加 getter 和 setter,实质上这里的 getter 是复用了创建响应式数据时的 getter 和 setter
  sharedPropertyDefinition.get = function proxyGetter () {
    // initProps 时传入的是 _props ,则这条语句相当于target['_props'][key]
    // initData 时传入的是 _data ,则这条语句相当于target['_data'][key]
    // 通过这样的方式调用已经创建的 getter
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    // 通过这样的方式调用已经创建的 setter
    this[sourceKey][key] = val
  }
  // 定义该属性到 vue 实例上
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

2.数据监测

  • vue会监视data中所有层次的数据。
  • 如何监测对象中的数据?

​ 通过 setter 实现监视,且要在new Vue时就传入要监测的数据。

​ (1).对象中后追加的属性,Vue 默认不做响应式处理

​ 也就是用户直接通过 Object.defineProperty 创建的属性

​ (2).如需给后添加的属性做响应式,请使用如下API:

​ Vue.set(target,propertyName/index,value) 、vm.$set(target,propertyName/index,value)

defineReactive

function defineReactive (obj: Object, key: string,val: any, customSetter?: Function) {
  // 创建一个新的依赖项
  const dep = new Dep()
  // Object.getOwnPropertyDescriptor获取一个对象上某个属性的描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // configurable为true表明该属性描述符可改变,并且该属性可删除
  // 如果该属性不能配置(删除等)则退出
  if (property && property.configurable === false) {
    return
  }
  // 拿到属性上的 getter/setter
  const getter = property && property.get
  const setter = property && property.set
  
  // 创建一个属性值的观察者实例(只有在属性值为 {}、[] 时会为该属性创建 Observer)
  // 实质上这个地方就完成了嵌套'对象'类型的递归创建观察者操作
  // 例如:data:{student:{name,age}}
  // 最外层的 data 会先被传入 observe,并且构造 Observer
  // 在构造 Observer 时会调用 walk 方法,然后遍历 data 上的所有属性,即 student
  // student 又会被传入 observer ,并创建 Observer,如果 student 中还有值为对象类型的属性,便会一层一层递归包装
  // 在每次 observe 时,只要值是'对象'类型都会有为该'对象' new Observer 的操作
  // 因为引用类型的地址都不相同,且这个对象之前并未观察过,所以只要该值是'对象'就一定会创建新的观察者
  // 需要注意这里的'对象'的范围
  let childOb = observe(val)

  // 通过 Object.defineProperty 实现了数据劫持
  // 在每次获取、修改数据时都会进行依赖的收集或更新
  // 定义属性,并为该属性添加 getter 和 setter 用于数据劫持
  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 属性名描述符可改变,属性可删除
    get: function reactiveGetter () { // 响应式的 getter
      // 有 getter 方法就调用 getter 方法,不然直接赋值咯
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 有目标程序监视器,收集新的依赖
        dep.depend()
        if (childOb) {
          // 有子观察者,收集子观察者自身这个依赖
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          // 如果属性的值是一个数组,特殊处理值为数组的依赖
          // 收集该数组上所有的依赖
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) { // 响应式的 setter
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */  // 禁止下面语句 eslint 的报错
      if (newVal === value || (newVal !== newVal && value !== value)) {
        // 新旧值相同,新旧值都为NaN,直接返回不允许设置
        return
      }
      	......
      if (setter) {
        // 有 setter 方法调用
        setter.call(obj, newVal)
      } else {
        // 没有 setter 方法直接赋值
        val = newVal
      }
      // 更新该属性的观察者
      childOb = observe(newVal)
      dep.notify()  // 通知该依赖的所有申请者更新
    }
  })
}
  • 在Vue修改数组中的某个元素一定要用如下方法:

    • 使用这些 API : push()、pop()、shift()、unshift()、splice()、sort()、reverse()

    • Vue.set() 或 vm.$set()

      注意:修改数组如果通过 arr[0] = value 的方法, Vue 并不会监测数据发送改变,并重新收集依赖渲染

      ​ vue 底层对上述的几个 API 进行了重新包装

    特别注意: Vue.set() 和 vm.$set() 不能给 vm 、 vm的根数据对象 添加属性!!!

Array Rewritten

['push','pop', 'shift','unshift', 'splice', 'sort', 'reverse']
  .forEach(function (method) {
    const original = arrayProto[method] // 拿到原生的方法
    def(arrayMethods, method, function mutator () { // 包装原来的数组方法
      // 参数处理
      	......
      // 对列表的方法调用结果进行拦截
      const result = original.apply(this, args) // 传入参数调用原生的数组方法,拿到结果
      const ob = this.__ob__  // 拿到实例上的观察者
      let inserted  
      switch (method) {	// 拿到插入方法中新插入的值
        case 'push':  // 尾部添加
          inserted = args
          break
        case 'unshift': // 头部添加
          inserted = args
          break
        case 'splice':  // 内部插入
          inserted = args.slice(2)  // 拿到下标从2开始的切片,也就是 splice 中替换原数组的实参
          break
      }
      // 如果存在列表的插入类型操作,需要观察新插入的值
      // 删除、修改这些操作无需重新观察,因为这些值原本初始化时就创建了观察者
      if (inserted) ob.observeArray(inserted)
      // 通知该依赖的所有申请者更新,通过这一步各个申请者收集更新完依赖,便会重新渲染
      ob.dep.notify()
      return result
    })
  })

七、面试重点

react、vue中的key有什么作用?(key的内部原理)

​ 1. 虚拟DOM中key的作用:

​ key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,

​ 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

​ 2.对比规则:

​ (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:

​ ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!

​ ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

​ (2).旧虚拟DOM中未找到与新虚拟DOM相同的key

​ 创建新的真实DOM,随后渲染到到页面。

​ 3. 用index作为key可能会引发的问题:

​ 1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:

​ 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

​ 2. 如果结构中还包含输入类的DOM:

​ 会产生错误DOM更新 ==> 界面有问题。

​ 4. 开发中如何选择key?:

​ 1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

​ 2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,

​ 使用index作为key是没有问题的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值