Object.defineProperty() 在Vue双向绑定与 ES6 Symbol 基本数据类型中的应用

Object.defineProperty()

先上一个例子:

var obj= {}
Object.defineProperty(obj,"age",{
value:123
})
console.log(obj.age);//123

这里我们给obj定义一个age属性并赋值;

传入参数

第一个参数:目标对象
第二个参数:需要定义的属性或方法的名字。
第三个参数:目标属性所拥有的特性。(descriptor)

descriptor的参数:

value:属性的值(不用多说了)
writable:如果为false,属性的值就不能被重写,只能为只读了
configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable)
enumerable:是否能在for…in循环中遍历出来或在Object.keys中列举出来。

get:看如下代码
set:看如下代码

//set 和 get,他俩干啥用的的
var a= {}
Object.defineProperty`请输入代码`rty(a,"b",{
  set:function(newValue){
    console.log("你要赋值给我,我的新值是"+newValue)
    },
  get:function(){
    console.log("你取我的值")
    return 2 //注意这里,我硬编码返回2
   }
})
a.b =1 //打印 你要赋值给我,我的新值是1
console.log(a.b)    //打印 你取我的值
                    //打印 2    注意这里,和我的硬编码相同的

特性总结:
1.默认值设置的时候;其他的descrptor默认设置为false;
2.configurable当设置为false的时候,重现设置为ture的时候会报错;也就是说configurable一旦关闭就没法再打开;
3. descriptor 中不能同时设置访问器(get 和 set)和 wriable 或 value,否则会错,就是说想用 get 和 set,就不能用 writable 或 value 中的任何一个。

在Vue双向绑定中

set 和get 的应用这就是实现 observe的关键:

实现过程:

3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

1.Obsever实现

原理:循环递归给data对象设置set 和get的监听,定义Dep拥有{addsub,notify}l两个方法和一个数组存放watcher同时在get的时候将对应的watcher推进Dep.sub[],在set的时候update watcher 更新view;

2.订阅者Watcher

watch={vm://vue关联对象,exp:属性名,cb:回调函数}和run,update,get 三个方法;

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    this.value = this.get();  // 将自己添加到订阅器的操作
}
 
Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    get: function() {
        Dep.target = this;  // 缓存自己
        var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        Dep.target = null;  // 释放自己
        return value;
    }
};

3.关联Observer和watch

当解析器还没加入的时候我们单独监听某个节点的某个属性的时候Observer和watch的关联方式如下:

function SelfVue (data, el, exp) {
    this.data = data;
    observe(data);
    el.innerHTML = this.data[exp];  // 初始化模板数据的值
    new Watcher(this, exp, function (value) {
        el.innerHTML = value;
    });
    return this;
}

4.解析器Compile

1.解析模板指令,并替换模板数据,初始化视图
2.将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
为了解析模板,首先需要获取到dom元素,然后对含有dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理

function nodeToFragment (el) {
    var fragment = document.createDocumentFragment();
    var child = el.firstChild;
    while (child) {
        // 将Dom元素移入fragment中
        fragment.appendChild(child);
        child = el.firstChild
    }
    return fragment;
}

接下来需要遍历各个节点,对含有相关指定的节点进行特殊处理,这里咱们先处理最简单的情况,只对带有 ‘{{变量}}’ 这种形式的指令进行处理,先简道难嘛,后面再考虑更多指令情况:

function compileElement (el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/;
        var text = node.textContent;
 
        if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
            self.compileText(node, reg.exec(text)[1]);
        }
 
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);  // 继续递归遍历子节点
        }
    });
},
function compileText (node, exp) {
    var self = this;
    var initText = this.vm[exp];
    this.updateText(node, initText);  // 将初始化的数据初始化到视图中
    new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
        self.updateText(node, value);
    });
},
function (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}

获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,对应上面所说的步骤1,接下去需要生成一个并绑定更新函数的订阅器,对应上面所说的步骤2。这样就完成指令的解析、初始化、编译三个过程,一个解析器Compile也就可以正常的工作了

为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类SelfVue函数:

function SelfVue (options) {
    var self = this;
    this.vm = this;
    this.data = options;
 
    Object.keys(this.data).forEach(function(key) {
        self.proxyKeys(key);
    });
 
    observe(this.data);
    new Compile(options, this.vm);
    return this;
}

Symbol 基本数据类型中的应用

当调用 Symbol 的时候,会采用以下步骤:

1 如果使用 new ,就报错
2 如果 description 是 undefined,让 descString 为 undefined
3 否则 让 descString 为 ToString(description)
4 如果报错,就返回
5 返回一个新的唯一的 Symbol 值,它的内部属性 [[Description]] 值为 descString
考虑到还需要定义一个 [[Description]] 属性,如果直接返回一个基本类型的值,是无法做到这一点的,所以我们最终还是返回一个对象。

为了防止不会出现同名的属性,毕竟这是一个非常重要的特性,迫不得已,我们需要修改 toString 方法,
让它返回一个唯一值,所以第 8 点就无法实现了,而且我们还需要再写一个用来生成 唯一值的方法,就命名为 generateName,我们将该唯一值添加到返回对象的 Name 属性中保存下来

7.Symbol 值不能与其他类型的值进行运算,会报错。

以 + 操作符为例,当进行隐式类型转换的时候,会先调用对象的 valueOf 方法,如果没有返回基本值,就会再调用 toString 方法,所以我们考虑在 valueOf 方法中进行报错,比如:

  1. Symbol 作为属性名,该属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

嗯,无法实现。

  1. 有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

这个实现类似于函数记忆,我们建立一个对象,用来储存已经创建的 Symbol 值即可。

  1. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。

实现原理如下代码:

 (function() {
        var root = this;
        var generateName = (function() {
            var postfix = 0;
            return function(descString) {
                postfix++;
                return `@@${descString}_${postfix}`;
            }
        })();

        var forMap = {};
        var SymbolPolyfill = function Symbol(description) {
            if (this instanceof SymbolPolyfill) {
                throw new TypeError("Symbol is not a constructor");
            }

            var descString = description === undefined ? undefined : description;

            var symbol = Object.create({
                toString: function() {
                    return this.__Name__;
                },
                valueOf: function() {
                    throw new Error("Cannot convert a Symbol value");
                }
            });

            Object.defineProperties(symbol, {
                "__Description__": {
                    value: descString,
                    writable: false,
                    enumerable: false,
                    configurable: false
                },

                "__Name__": {
                    value: generateName(descString),
                    writable: false,
                    enumerable: false,
                    configurable: false
                }
            })

            return symbol;
        }

        Object.defineProperties(SymbolPolyfill, {
            'for': {
                value: function(description) {
                    var descString = description === undefined ? undefined : String(description)
                    return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
                },
                writable: true,
                enumerable: false,
                configurable: true
            },
            'keyFor': {
                value: function(symbol) {
                    for (var key in forMap) {
                        if (forMap[key] === symbol) return key;
                    }
                },
                writable: true,
                enumerable: false,
                configurable: true
            }
        })

        root.SymbolPolyfill = SymbolPolyfill;
    })();
    console.log(SymbolPolyfill(1))
    console.log(SymbolPolyfill(1).toString());
    console.log(String(SymbolPolyfill(1)));

    var a = SymbolPolyfill('foo');
    var b = SymbolPolyfill('foo');

    console.log(a === b); // false

    var o = {};
    o[a] = 'hello';
    o[b] = 'hi';

    console.log(o); // Object { "@@foo_1": "hello", "@@foo_2": "hi" }

    console.log('1' + symbol); // 报错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值