最近忙得狗一样, 有一段时间没有更新了, 虽然是僵尸博主, 但是有点小收获还是要来唠叨一下的.
相信很多的用 vue 的人都知道 vue 双向绑定的原理建立在, 给属性绑定了 getter 和 setter, 在属性被改变的同时出触发视图的再渲染. 而本期也是借助这两个内置方法实现 vue 内的 watch
getter 和 setter
getter 是一种获得属性值的方法, setter 是一种设置属性值的方法.
属性被赋值 a = 1 的时候, a 的原型内的 setter 就会被触发;
而 console.log(a) 的时候, a 的原型内的 getter 就会被触发.
实现 getter 和 setter
我们不能直接给变量的 setter 和 getter 绑定事件函数, 为了实现绑定我们要借助 Object 对象来构造带有 setter 和 getter 的属性.
这里有前辈总结的 几种实现 getter 和 setter 的方法 https://segmentfault.com/a/1190000003882976 , 而且他还总结了一些 Object.prototype 内控制属性枚举的特性的隐式属性.
我这里选用了比较好构造的一种
Object.defineProperty
概要
Object.defineProperty() 方法直接在一个对象上定义一个新属性, 或者修改一个已经存在的属性, 并返回这个对象.
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
需要定义属性的对象.
prop
需被定义或修改的属性名.
descriptor
需被定义或修改的属性的描述符.
第一个参数, 被构造的属性的 this 指向的对象
第二个参数, 被构造的属性名
第三个参数, 构造的规则 (上面的文字链接最后面有介绍)
- (function () {
- var o = { a : 1}// 声明一个对象, 包含一个 a 属性, 值为 1
- Object.defineProperty(o,"b",{
- get: function () {
- return this.a;
- },
- set : function (val) {
- this.a = val;
- },
- configurable : true
- });
- console.log(o.b);//==> 1
- o.b = 2;
- console.log(o.b);//==> 2
- })();
configurable 是指 "b" 是否内被在配置, 默认是 false.false 的话
Object.defineProperty(o,"a",{set : function(val){}} );
再修改时会不起作用或者报错, 一般默认 false.
构造我们的 vue.watch
目标实现, 一下是我们想要的达到的效果
- import watcher from './watcher.js';
- let wm = new watcher({
- data:{
- a: 0
- },
- watch:{
- a(newVal,oldVal){
- console.log('newVal:'+newVal);
- console.log('oldVal:'+oldVal);
- }
- }
- })
- vm.a = 1
- // newVal:1
- // oldVal:0
创建构造对象
- class watcher{
- constructor(opts){
- this.$data = opts.data;
- for(let key in opts.data){
- this.setData(key,opts.data[key])
- }
- }
- setData(_key,_val){
- Object.defineProperty(this,_key,{
- get: function () {
- return this.$data[_key];
- },
- set : function (val) {
- const oldVal = this.$data[_key];
- if(oldVal === val)return val;
- this.$data[_key] = val;
- return val;
- },
- });
- }
- }
- export default watcher;
添加 watch 事件触发
- /**
- * @desc 属性改变监听, 属性被 set 时出发 watch 的方法, 类似 vue 的 watch
- * @author Jason
- * @data 2018-04-27
- * @constructor
- * @param {object} opts - 构造参数. @default {data:{},watch:{}};
- * @argument {object} data - 要绑定的属性
- * @argument {object} watch - 要监听的属性的回调
- * watch @callback (newVal,oldVal) - 新值与旧值
- */
- class watcher{
- constructor(opts){
- this.$data = this.getBaseType(opts.data) === 'Object' ? opts.data : {};
- this.$watch = this.getBaseType(opts.watch) === 'Object' ? opts.watch : {};
- for(let key in opts.data){
- this.setData(key)
- }
- }
- getBaseType(target) {
- const typeStr = Object.prototype.toString.apply(target);
- return typeStr.slice(8, -1);
- }
- setData(_key){
- Object.defineProperty(this,_key,{
- get: function () {
- return this.$data[_key];
- },
- set : function (val) {
- const oldVal = this.$data[_key];
- if(oldVal === val)return val;
- this.$data[_key] = val;
- this.$watch[_key] && typeof this.$watch[_key] === 'function' && (
- this.$watch[_key].call(this,val,oldVal)
- );
- return val;
- },
- });
- }
- }
- export default watcher;
为了函数内部的健壮性, getBaseType 是用来做类型校验的.
Object.defineProperty(this)
,this 把上下文指向当前对象.
this.$watch[_key].call(this,val,oldVal)
, 把监听事件的上下文页绑定到当前对象, 方便在 watch 内通 this 获取对象内的值, 如下
- let wm = new watcher({
- data:{
- a: 0,
- b: 'hello'
- },
- watch:{
- a(newVal,oldVal){
- console.log(this.b);
- }
- }
- })
总结
有人可能会问为什么不直接用 vue 呢. 你也知道 vue 是一个工程级别的框架, 做比较大的项目当然是用 vue,react; 但是单单做一个展示性的官网或者做个移动端的 H5 宣传页也用上 vue 吗? 那当然是不可能的.
用上这一个 watcher 类, 可以让你页面的状态控制有条理, 有迹可循.
比如几个按钮联动一个或几个视图的改变和动效的时候, 你就不用在每个按钮的 click 是见中都触发一下修改
- btn1.onclick=function(){
- var a = 'haha';
- document.getElementById('id').html(a)
- }
- btn2.onclick=function(){
- var a = 'xixi';
- document.getElementById('id').html(a)
- }
- let wm = new watcher({
- data:{
- a: "",
- },
- watch:{
- a(newVal,oldVal){
- document.getElementById('id').html(newVal)
- }
- }
- })
- btn1.onclick=function(){
- var a = 'haha';
- }
- btn2.onclick=function(){
- var a = 'xixi';
- }
但是如果你的视图不被 2 个以上动作联动的话, 也未必会用上.
来源: https://juejin.im/post/5b0408f2f265da0ba17cda08