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 方法中进行报错,比如:
- Symbol 作为属性名,该属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。
嗯,无法实现。
- 有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
这个实现类似于函数记忆,我们建立一个对象,用来储存已经创建的 Symbol 值即可。
- 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); // 报错