MVVM的设计模式,相信很多人早就从别的文章中看到过了,这里就不过多叙述,直接上干货。
需要的基础知识:
- Object.defineProperty (接下来我会先介绍这个属性的用法)
我们的最终目标:
- 实现数据双向数据绑定功能: v-model
- 点击事件的监听: v-on="clickd" (即@click)
首先来介绍一下Object.defineProperty的属性:
Object.defineProperty(obj, prop, descriptor)
// 参数介绍
// obj:要定义属性的对象
// prop: 要定义或修改属性的名称
// descriptor:对参数2(prop)的描述,也叫属性描述符
接下来重点说一下参数3(descriptor)属性描述符的用法
Object.defineProperty(对象, 属性名,{
configurable:false, // 可配置
enumerable:false, // 可枚举
writable:false, // 可写入
value:undefined, // 初始值
get:function(){}, // 重点
set:function(){} // 重点
})
-
configurable: 为 true 时,属性才能重新被定义(再写一次Object.defineProperty)。默认为 false。 -
enumerable:为true时,该属性才能够出现在对象的枚举属性中,即可以使用for in循环访问。默认为 false。 -
value:该属性对应的初值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。 -
writable:为true时,value属性值才能被修改。默认为 false,相当是只读的。 -
get:一个给属性提供 get的方法,如果没有 getter 则为undefined。当访问该属性时,该方法会被执行。默认为undefined。 -
set:一个给属性提供 set 的方法,如果没有 setter 则为undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为undefined。
学习属性描述符(有基础的同学可以直接跳过)
enumerable :可枚举的
// 对象
var obj = {
b:1
}
// 添加新属性
Object.defineProperty(obj,"a",{
value:100, // 值
enumerable:false // 不可枚举。不能被for in循环出来
})
Object.defineProperty(obj,"c",{
value:200, // 值
enumerable:true // 可枚举。能被for in循环出来
})
console.dir(obj)
// 循环取出属性名。它只能取出 可枚举的属性名
// for(var 属性名 in 对象)
// 功能是:循环取出对象中的,可枚举的,所有属性名
for(var key in obj) {
console.log(key)
}
结论:
enumerable只有为true,它才能被枚举(被for in循环)。
for of是循环属性值,for in 是循环属性名
configurable
可配置的。如果是false,则不能再次使用defineProperty去修改,修改就会报错。
也可以理解这个属性是只读的它不能被修改了。
var obj = {}
// 定义属性
Object.defineProperty(obj,"c",{
configurable: false,
// 如果这个值是false,说明这属性将不能再次使用defineProperty来修改
// 如果再通过:obj.c = 200,则也不会生效。
value:100
})
// 对现有属性的修改
// Uncaught TypeError: Cannot redefine property: c
// Object.defineProperty(obj,"c",{
// value:200
// })
console.log(obj)
value和writable
value:是属性初值
writeable: 可写入的
定义只读的属性:
const obj = {
B:1
}
Object.defineProperty(obj,"A",{
value: 1,
writable: false
})
console.dir(obj)
console.log(obj.A)
obj.A =1001
console.log(obj.A)
const定义的对象,它的属性还是可以修改的。我们可以通过writable设置为false来设置只读的属性,真正实现常量的效果。
在上面的代码中,如果给对象的属性赋值,并不会修改属性的值。
进阶:对已有对象进行封装,以得到一个常量对象
目标:写一个函数,传入对象1,返回对象2。要求对象2的属性都不能被修改。
const obj = {
a:1,
b:2
}
function getConst(obj){
var _obj = {}
for(var key in obj){
Object.defineProperty(_obj,key,{
writable:false,
value: obj[key]
})
}
return _obj
}
var obj1 = getConst(obj)
console.log("设置之后的值是:",obj1)
get和set
get是一个方法,当访问对象的属性时,它会执行;
set是一个方法;当设置对象的属性时,它会执行;
- 它们与value和writable是互斥的(一个属性的属性描述符中, 不能同时有
value和writableg与get和set)。 - 一旦使用它们,则这个属性就没有保存属性值的能力,如果希望它能保存属性性,则需要引入另一个额外的变量。
- 应用:它们来做拦截器
var obj = {
a:1
}
//
// Uncaught TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute
// set get 不能与value和writeable同时存在 。
// get,set 无法保存属性的值,只能借助另一个额外的变量
// 需求,在你设置age属性时,如果属性>30岁,则统一改成28。
var _age = 18
Object.defineProperty(obj,'age',{
get:function(){
// obj.age ,则会执行get函数;
// get()的返回值,就是obj.age的值
// console.log('获取age属性')
return _age
},
set(val){
// obj.age = XX ,则会执行set函数,并且会传入值给val
if(val > 30 ){
console.log('程序员,30岁是一个劫')
val = 28
}
_age = val
}
})
console.log(obj);
obj.age = 31;
console.log(obj.age); // 28
下面我们做几道题巩固一下吧:
第1题
题目
下面代码会输出什么?
var obj = {a:1};
Object.defineProperty(obj,"a",{
get(){
return this.a
}
})
console.log(obj.a)
友情请示: 如果在浏览器中运行上面的代码,可能会导致浏览器卡死。慎重。
答案
会陷入死循环。因为是上面的Object.definedProperty()给obj对象的a属性添加一个拦截器get(),当访问obj.a时,会执行get()中的代码,而在get()中又再次访问obj.a(this.a也就是obj.a),所以这里会形成死循环。
第2题
题目
Object.defineProperty()是否能在ie8中使用?
答案
不能。在这里可以查
第3题
题目
属性描述符一共有几个?
答案
6个。分别是configurable,enumable, value,writable,get,set;
第4题
题目
如何让一个对象属性处于只读状态。例如:
var obj = {a:1}
// 你的代码。
obj.a = 20;
console.log(obj.a) // ==> 1
答案
var obj = {a:1}
// 你的代码
Object.defineProperty(obj,'a',{
writable:false
})
obj.a = 20;
console.log(obj.a) // ==> 1
第5题
题目
get,set能和value一起使用吗?
答案
不能。
第6题
题目
如何理解get,set是拦截器?
答案
当给一个属性定义了get,set之后,就说明它不能直接用来保存具体的数据了,数据只能保存在另一个变量中。此时我们就说这个属性是那个变量的拦截器。
如下的代码中:
var obj = {}
var _a = 1
Object.defineProperty(obj,"a",{
set(val){ _a = val},
get(){ return _a;}
})
obj.a就是_a的拦截器,获取和设置_a的两个操作都必须要经过obj.a的set,get函数。
第6题
题目
Object.defineProperty与vue框架有什么关系?
答案
vue.js 2.X版本中使用它来实现数据响应式效果。
第6题
题目
如何使用proxy来实现一个允许下标为负的数组。
答案
var arr = [1,2,3];
var proxyArr = new Proxy(arr,{
get: (target,prop)=>{
let index = Number(prop);
if(index < 0){
prop = target.length + index;
}
return target[prop];
}
})
console.info(arr[-1]); // undefined
console.info(proxyArr[-1]); // 3
第9题
题目
如何用proxy来实现访问不存在的属性名时给出更加优雅的提示。
const con = {
COMPANYNAME:"jd",
}
// 你的代码
//let proxyConst = ...;//
console.log(proxyConst.abc); // 提示abc属性不存在。
答案
const con = {
COMPANYNAME:"jd",
}
let proxyConst = new Proxy(con, {
get: function (target, key, receiver) {
if(key in target)
return target[key];
else{
throw new Error("error:常量名"+key+"不存在!")
}
}
});
第10题
题目
如下代码会输出什么?
var obj = {
a : 1
}
Object.defineProperty(obj,"b",{
value: 2
})
obj.a = 100;
obj.b = 100;
console.log(obj.a, obj.b) // a,b的值分别是什么?
答案
obj.a是100,obj.b是2。因为writable属性默认为false,所以obj.b是不能修改了,或者说改了也无效。
第10题
题目
Object.defineProperty和Proxy的相同和不同?
答案
Object.defineProperty是es5中给出的,用来精细设置对象的属性的工具:例如可以通过writeable,enumable,configurable,set,get来设置只读属性,或者对某个属性进行拦截。 在vue2.x中就是使用它实现的数据响应式效果。
Proxy是es6提出新对象。它用来在一个已有对象的基础上设置代理对象。这个代理对象的作用是对原对象上的所有操作进行拦截,也可以充当拦截器的功能。
不同的之处在于,proxy的代理功能更加强大:
它可以很方便地拦截所有的属性操作;
除了get,set之外还有其它的代理功能。
在vue3中将会使用它来实现数据响应式效果。