【vue学习】生命周期

马老师的VUE生命周期教程

下面的图片来自上面链接

Vue实例

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

一个 Vue 应用由一个通过new Vue() 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所有的 Vue 组件也都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

面试中的问题

  1. 请描述下vue的生命周期是什么?
  2. vue生命周期总共有几个阶段?
  3. vue生命周期的作用是什么?
  4. 组件进来请求接口时你是放在哪个生命周期?为什么?
  5. DOM渲染在哪个周期中就已经完成了?
  6. 第一次加载页面时会触发哪几个钩子?
  7. vue在created和mounted这两个生命周期中请求数据有什么区别呢?
  8. watch的属性用箭头函数定义结果会怎么样?
  9. 在vue项目中如果methods的方法用箭头函数定义结果会怎么样?

API中的钩子函数

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

大部分钩子在服务器端渲染期间不被调用。

  • beforeCreate:
    在实例初始化(new)之后,数据观测data observer 和 事件配置 event/watcher之前被调用。
  • created:
    实例创建完成后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。
    然而,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount:
    在挂载开始前被调用:相关的 render 函数首次被调用。
  • mounted:
    el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。注意: mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted。。
  • beforeUpdate:
    数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated:
    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
    当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。。
  • beforeDestroy:
    实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:
    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • activated: keep-alive 组件激活时调用。被调用。
  • deactivated: keep-alive 组件停用时调用。。
  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

5种处理Vue异常的方法

既然学到了钩子函数errorCaptured,那么我们就来学习下vue的异常处理

错误大全

为了测试各种异常处理技巧,来触发三种类型的错误。

  1. 引用一个不存在的变量:
<div id="app" v-cloak>
    Hello, {{name}}
</div>

上述代码运行后不会抛出错误,但是在控制台会有[Vue warn]消息。

你可以在 Codepen 查看例子,页面可以正常显示

2. 将变量绑定到一个被计算出来的属性,计算的时候会抛出异常

<div id="app" v-cloak>
    Hello, {{name2}}
</div>

<script>
    const app = new Vue({
        el: "#app",
        computed: {
            name2() {
                return x;
            }
        }
    });
</script>

运行上述代码会在控制台抛出一个[Vue warn]和一个常规的错误,网页白屏。

3. 执行一个会抛出异常的方法

<div id="app" v-cloak>
    <button @click="doIt">Do It</button>
</div>

<script>
    const app = new Vue({
        el: "#app",
        methods: {
            doIt() {
                return x;
            }
        }
    });
</script>

这个错误在控制台也[Vue warn]和常规报错。和上一个错误的区别在于,只有你点击了按钮才会触发函数调用,才会报错。

上面 3 个例子并不代表所有类型的错误。这 3 种是比较常见的错误。

异常处理技巧
  1. errorHandler[2.2+]
/**
err指代 error 对象,
info是一个 Vue 特有的字符串,
vm指代 Vue 应用本身。
记住在一个页面你可以有多个 Vue 应用。
这个 error handler 作用到所有的应用。
*/
Vue.config.errorHandler = function(err, vm, info) {
    console.log(`Error: ${err.toString()}\nInfo: ${info}`);
};
/**
上面第一种错误不会触发 errorHandler,它只是一个 warning。

第二种错误会抛出错误被 errorHandler 捕获:
Error: ReferenceError: x is not defined
Info: render

第三种错误也会被捕获:
Error: ReferenceError: x is not defined
Info: v-on handler

记住info里面的信息也是非常有用的。
*/
  1. warnHandler[2.4+]
/**
msg和vm都容易理解,trace代表了组件树。
*/
Vue.config.warnHandler = function(msg, vm, trace) {
    console.log(`Warn: ${msg}\nTrace: ${trace}`);
};
/**
第一个错误被warnHandler捕获:
Warn: Property or method 'name' is not defined on the instance but referenced during render. 
Make sure that this property is reactive, either in the data option, 
or for class-based components, by initializing the property. 
See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Trace:(found in <Root>)
*/
  1. renderError
/**
和前面两个不同,这个技巧不适用于全局,和组件相关。
并且只适用于非生产环境。
*/
const app = new Vue({
    el: "#app",
    renderError(h, err) {
        return h("pre", { style: { color: "red" } }, err.stack);
    }
});

第一个例子是没有效果的,因为只是一个 warning。第二个例子就会在网页上显示具体的错误信息。

老实说,我没觉得这个比直接看控制台好多少。但是,如果你们的 QA 团队或则测试对浏览器控制台不熟悉的话,这个还是蛮有用的。

  1. errorCaptured
Vue.component("cat", {
    template: `
<div><h1>Cat: </h1>
  <slot></slot>
</div>`,
    props: {
        name: {
            required: true,
            type: String
        }
    },
    errorCaptured(err, vm, info) {
        console.log(`cat EC: ${err.toString()}\ninfo: ${info}`);
        return false;
    }
});

Vue.component("kitten", {
    template: "<div><h1>Kitten: {{ dontexist() }}</h1></div>",
    props: {
        name: {
            required: true,
            type: String
        }
    }
});
<!--注意 kitten 组件的代码是有 BUG 的。-->
<div id="app" v-cloak>
    <cat name="my cat">
        <kitten></kitten>
    </cat>
</div>

捕获的信息如下:

  1. window.onerror (不仅仅针对 Vue)
    最后也是最重要的一个候选项 window.onerror。它是一个全局的异常处理函数,可以抓取所有的 JavaScript 异常。
window.onerror = function(message, source, line, column, error) {
  console.log('ONE ERROR HANDLER TO RULE THEM ALL:', message);
}

Vue.config.productionTip = false;
Vue.config.devtools = false;

Vue.config.errorHandler = function(err, vm, info) {
  //oopsIDidItAgain();
  console.log(`Error: ${err.toString()}\nInfo: ${info}`);
}

const app = new Vue({
  el:'#app',
  methods:{
    doIt() {
      return x;
    }
  }
})

Vue选项/数据

学习vue的生命周期,这里也顺便看下vue选项中的数据:

  1. propsData

错误用法:


正确用法:

2. props 可以是数组或对象,用于接收来自父组件的数据。

// 简单语法
Vue.component('props-demo-simple', {
  props: ['size', 'myMessage']
})

// 对象语法,提供验证
Vue.component('props-demo-advanced', {
  props: {
    // 检测类型
    height: Number,
    // 检测类型 + 其他验证
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: function (value) {
        return value >= 0
      }
    }
  }
})
  1. data
  • 组件的定义data只能是function类型。
  • Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化。
  • 实例创建之后,可以通过 vm.$data 访问原始数据对象
  • _$ 开头的属性 不会被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突。你可以使用例如 vm.$data._property 的方式访问这些属性
  • 如果需要,可以通过将 vm.$data 传入 JSON.parse(JSON.stringify(...)) 得到深拷贝的原始数据对象。
  • 深入响应式原理
  1. computed
computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
  1. methods
methods: {
    plus: function () {
      this.a++
    }
}
  1. watch
    一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch对象的每一个属性。
var vm = new Vue({
  data: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // 方法名
    b: 'someMethod',
    // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
      handler: 'someMethod',
      immediate: true
    },
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }
})

data中为啥不能用-$开头的属性

<template>
 <div class="hello">
    {{test}}
    {{_tttttttttt}}
    {{$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this)
  }
}
</script>

这么一个简单例子,但是vue却会提示你报错了,错误如下:

  • 从错误中我们可以发现报错的原因竟然是$tttttt_tttttttttt的变量是没有定义。这是为什么呢?我们明明在data中定义了。
  • 通过查看官网API说明,data中不要使用$_开头,因为在Vue内部也使用$_作为方法或属性,这是为了防止冲突。那么我们就从源码的角度来分析,我们定义的变量为什么没了呢??
  • 我们都知道vue采用了ES的defineProperty来实现数据驱动视图,如下:
Object.defineProperty(target, key) {
    enumerable: true,
    configurable: true,
    get: function() {
        // 这里获取数据
    },
    set: function() {
        // 这里设置参数,通知更新视图
    }
}
/**
可如果仅仅是这样的话,我们在vue中是没法通过this.xxx来获取变量的,
而必须是通过this.$data.xxx。因此vue的变量都挂在在$data或_data上。
所以vue内部还做了一层代理,如下
*/
/**
target是vue实例,key为_data,
这样就能通过访问this.xxx = this._data.xxx了
*/
function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
/**
所以那么我们的变量为什么还是不存在呢,
那是因为vue做了一个检测,
检测你的变量的开头是否为_或$,
如果使用了那么就不会使用代理了,
变量只会存在$data上或_data上。我们来看下源码:
*/
function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // proxy data on instance
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
        // 这边处理代理,所以isReserved处理了是否要进行代理
      proxy(vm, "_data", key);
    }
  }
  // observe data
  observe(data, true /* asRootData */);

  function isReserved (str) {
      var c = (str + '').charCodeAt(0);
      return c === 0x24 || c === 0x5F // 这边判断chartCode是否为_和$
   }
}
  • 到这里我们就完成的解释为什么无法访问了,所以一般不要使用_和$命名,如果真的要使用的话,那也行。使用如下就不会报错了。
<template>
 <div class="hello">
    {{test}}
    {{_data._tttttttttt}}
    {{_data.$tttttt}}
    {{$data._tttttttttt}}
    {{$data.$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this.$data._tttttttttt)
    console.log(this.$data.$tttttt)
    console.log(this._data._tttttttttt)
    console.log(this._data.$tttttt)
  }
}
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值