1 对象属性特性
-
Object.defineProperty((obj,prop,descriptor) 方法可以精确地添加或修改对象上的属性。该方法用于设置单一属性特性 参数
- obj 要在其上定义属性的对象
- prop 要定义或修改的属性的名称
- descriptor 定义或修改的属性的描述
返回值 传递给函数的对象
-
Object.defineProperties(obj, props) 方法直接在对象上定义新属性或修改现有属性,并返回该对象。 参数
- obj 在其上定义或修改属性的对象。
- props 一个对象,其键表示要定义或修改的属性的名称,其值是描述这些属性的对象。中的每个值props必须是数据描述符或访问器描述符
返回值 传递给函数的对象
注:Object.defineProperties本质上,定义props了与对象obj对象的可枚举自己的属性相对应的所有属性
// 在ES3.1中,只要对象能够访问到,就可以任意的去操作该对象,访问对象、添加属性、修改属性和删除属性,如下:
console.group("ES3对象操作");
var stu = {
name:"张三",
sex:"男",
age:23
}
console.log("原对象stu:",stu);
// 添加分数属性
stu.score = 100;
console.log("添加分数属性后stu:",stu);
// 删除年龄属性
delete stu.age;
console.log("删除年龄属性后stu:",stu);
// 修改性别属性
stu.sex = "女";
console.log("修改性别属性后stu:",stu);
console.groupEnd();
//在ES5中,为对象拓展了一个叫做“特性”的东西,为对象中属性的可访问行进行了限制 如设置属性是否可以被枚举的特性enumerable [ɪ'njʊmərəbl],属性值为布尔值
console.group("ES5限制枚举特性");
// 使用for...in 枚举(一一列举)对象的属性
for(var i in stu){
console.log(i);//没有限制枚举的情况下,正常获取对象的每一个属性
}
// console.log("====== 开始限制某个独立属性的枚举 ======")
// // 使用ES5中对象的特性 enumerable限制枚举 属性值设置为false则对象属性不可枚举 默认true可枚举
// Object.defineProperty(stu,"score",{
// // 设置score属性不可枚举
// enumerable:false
// })
// for(var i in stu){
// console.log(i);//限制枚举的情况下,不能正常获取对象限制枚举的属性
// }
console.log("====== 限制多个属性的枚举 ======")
Object.defineProperties(stu,{
"name":{
enumerable:false
},
"sex":{
enumerable:false
}
})
for(var i in stu){
console.log(i);//限制枚举的情况下,不能正常获取对象限制枚举的属性
}
console.groupEnd();
总结:对象中的属性默认可以被枚举;但使用ES5方法限制枚举后,被限制枚举后的属性就不可以再次枚举了
JS中的三类对象:
- 内置对象(native object)是由ECMAScript规范定义的对象或者类。例如:函数对象、数组对象、日期对象、正则表达式对象等等
- 宿主对象(host object) 是由js编译器所嵌入的宿主环境(web浏览器)所定义的。比如客户端js中表示网页结构的HTMLElement对象就是宿主环境创建的对象。宿主环境定义的对象可以直接使用的话,我们也可以把它们当做内置对象。
- 自定义对象(user-defined object) 由运行中的js创建的对象。
JS中的两类属性:
- 自有属性(own property) 直接在对象当中定义的属性,区别于继承属性。
- 继承属性(inherited property) 在对象原型中定义的属性。
JS中属性描述符对象(属性的特性描述): ES5中定义了一个属性描述符对象(property descriptor)。这个对象属性和他们所描述的属性特性是同名的。
- value:属性的值。
- writable:可写性,是否可以设置值。
- enumerable:可枚举性,遍历对象时该属性会不会出现。
- configurable:可配置性。
通过value特性配置值:
// 在ES3.1中,只要对象能够访问到,就可以任意的去操作该对象,访问对象、添加属性、修改属性和删除属性,如下:
// console.group("ES3对象操作");
// var stu = {
// name:"张三",
// sex:"男",
// age:23
// }
// console.log("原对象stu:",stu);
// // 添加分数属性
// stu.score = 100;
// console.log("添加分数属性后stu:",stu);
// // 删除年龄属性
// delete stu.age;
// console.log("删除年龄属性后stu:",stu);
// // 修改性别属性
// stu.sex = "女";
// console.log("修改性别属性后stu:",stu);
// console.groupEnd();
//在ES5中,为对象的属性配置值
console.group("ES5添加属性的值");
// 创建一个空对象
var stu = {}
// ES5方法为空对象添加属性和属性值
Object.defineProperty(stu,'name',{
// 配置值 添加属性值
value:"张翠花"
})
// 访问对象 通过这种方式添加的属性
console.log("stu:",stu);//stu: {name: "张翠花"}
// 删除属性 不能使用 delete 删除配置的属性
delete stu.name;
console.log("stu:",stu);//stu: {name: "张翠花"}
// 访问属性 可以访问配置的属性
console.log("stu.name:",stu.name);//stu.name: 张翠花
// 修改属性 不可以使用 obj.att 或 obj[att]方 式修改属性
stu.name = "张无忌";
// 访问属性 可以访问配置的属性
console.log("stu.name:",stu.name);//stu.name: 张翠花
//枚举属性 不可以被枚举
for(var i in stu){
console.log(i);
}
console.groupEnd();
// ES5,修改对象属性
var obj = {type:"未知"};
console.log(obj.type);//未知
Object.defineProperty(obj,"type",{
value:"狗"
});
console.log(obj.type);//狗
obj.type = "猫";
console.log(obj.type);//猫
delete obj.type;
console.log(obj.type);//undefined
/*
* 总结:1. 当一个对象中没有任何原有属性,通过特性的方式 Object.definePeoperty方式添加的属性,里面属性的所有特性都是false:
* 即:通过这种方式添加的属性不能够删除、修改和枚举。
* 2. 如果一个对象中天生自带某个属性,此时对象属性的所有特性属性值为true:
* 即:只要对象能访问到,不管什么方式,都可以进行任意的操作
*/
通过writable特性配置可读写性
// 创建一个对象
var obj = {type:"未知"}
// 将对象的属性type设置为不可写特性(即:不可修改)
Object.defineProperty(obj,"type",{
// 设置属性是否可写 true默认可写 false不可写
writable:false
})
console.log("修改前属性type:",obj.type);//修改前属性type: 未知
// 尝试使用普通的方式进行修改 结果不可修改
obj.type = "猫";
console.log("修改后属性type:",obj.type);//修改后属性type: 未知
通过enumerable配置可枚举性
// 创建一个对象
var obj = {
type:"未知",
color:"白色"
}
// 默认情况下,对象的属性可以被枚举
for(var i in obj){
console.log(i);
}
// 设置属性color特性 不可枚举
Object.defineProperty(obj,"color",{
// 默认true可被枚举 设置false不可被枚举
enumerable:false
})
// 设置color不可枚举后,尝试枚举,color不再出现
for(var i in obj){
console.log(i);
}
通过 configurable配置相关配置
// 创建一个对象
var obj = {
type:"未知",
color:"白色"
}
console.log(obj.color)
// 设置属性特性 配置 value值 writeable可写性
Object.defineProperty(obj,"color",{
// 配置属性的value值
value:"黄色",
// 不可修改
writable:false,
// 设置是否重复配置相关特性 默认false可重复配置,设置true不可以重复配置
configurable:false
})
console.log(obj.color);//白色
// 重复修改 可写特性 的值
Object.defineProperty(obj,"color",{
// 配置属性的value值
value:"黄色",
// 可修改
writable:true
})
console.log(obj.color);//黄色
// 修改obj中的属性
obj.color = "黑色";
console.log(obj.color);//如果 writeable为true,则可以通过普通方式修改属性值;否则不可以
// 总结:设置属性特性configurable为false的情况下,不能重复配置相关特性,否则报错 TypeError: Cannot redefine property: color
存值器set和取值器get
<body>
<h2>单项数据绑定:通过修改JS中的数据,页面呈现最新修改的数据信息。这是一种映射关系,把JS数据的变化映射到DOM结构上</h2>
<div id="box">数据单项绑定呈现位置</div>
<input type="text" id="ipt">
</body>
<script src="./js/jquery-1.12.3.min.js"></script>
<script>
// 创建对象
// var obj = {
// des:"把Model的变化映射到DOM结构上"
// }
// 获取元素
// var box = document.getElementById("box");
// // 设置元素的内部文本
// box.innerHTML = obj.des;
// 普通的方式,修改对象的值,查看页面DOM数据变化
// obj.des = "新的数据";//页面不会发生改变,除非通过innerHTML重新写入
// 通过set方法设置单项绑定
// Object.defineProperty(obj,'des',{
// //赋值器
// set:function(value){
// // 当设置或修改新的值时,将最新的值输出到页面
// box.innerHTML = value;
// // 备用属性赋值
// this.oDes = value;
// },
// // 取值器
// get:function(){
// return this.oDes;
// }
// });
// 再次修改以下值试试
// obj.des = "新的值";//发现页面内容更新了
// 现在我们做这样一个修改,页面添加一个输入框,随着输入框内容的输入,上面呈现输入框中的内容,修改如下:
// 创建对象
var obj = {
des:"把Model的变化映射到DOM结构上"
}
// 获取元素
var box = document.getElementById("box");
var ipt = document.getElementById("ipt");
// 设置元素的内部文本
box.innerHTML = obj.des;
Object.defineProperty(obj,'des',{
//赋值器
set:function(value){
// 当设置或修改新的值时,将最新的值输出到页面
box.innerHTML = value;
// 备用属性赋值
this.oDes = value;
},
// 取值器
get:function(){
return this.oDes;
}
});
// 新的值来自于页面输入框 触发正在输入事件
// ipt.oninput = function(){
// if(this.value === ""){
// box.innerHTML = "把Model的变化映射到DOM结构上";
// }else{
// obj.des = this.value;
// }
// }
// 含有输入法时,不能检测中文,改造并使用jQuery(使用js也可)如下:
/**
* 拓展:实现中文输入法下,仅在选词后触发input事件
* 描述:在使用oninput监控输入框内容变化时,我们期望仅在value值变化时,才触发oninput事件,而在中文输入下,未选词时的按键也会触发oninput事件。
* 两个事件:
* compositionstart事件
* compositionend事件
* 实现方式:使用一个变量表示拼写状态,在oninput事件中判断是否在拼写状态,当拼写状态结束,继续执行下一步操作。
* 代码示例如下:
**/
// 定义是否开启输入法的开关 默认关闭
var flag = false;
//oninput在oncompositionend之前执行,需加定时器
ipt.oninput = function(){
this.addEventListener('compositionstart',function(){//中文开始书写
flag = true;//检测到中文输入法开启,设为true
})
this.addEventListener('compositionend',function(){//中文书写完毕,选取文字完毕
flag = false;//输入完毕,设置false
})
setTimeout(function() {
if(!flag) {//保证输入法没有开启也能正常获取数据
if(ipt.value === ""){
box.innerHTML = "把Model的变化映射到DOM结构上";
}else{
obj.des = ipt.value;
}
}
},0);
}
</script>
对象所有特性相关应用:
// 创建一个对象
var obj = {
name:"张三",
age:25
}
// 设置多属性特性value
Object.defineProperties(obj,{
// 设置属性name的特性
name:{
// 配置值 设置或修改值
value:"张三丰",
// 是否可写配置 设置值为false后不可以再通过普通方式修改值
writable:false,
// 是否可枚举配置 设置值为false后 不可以枚举属性
enumerable:false,
// 是否可配置设置,设置为false后不能使用任何方式重复修改设置
configurable:false
},
// 设置属性age的特性
age:{
// 存值器
set:function(value){
this.oAge = value;
},
// 取值器
get:function(){
return this.oAge;
},
// 设置是否可枚举 设置false后,age属性不再被获取,只显示备用属性
enumerable:false,
// 注意:一旦设置set/get方法就是设置了设置值或修改值的配置,
// 一旦设置了set特性方法,就不能再设置相同功能的特性 writeable 与 value,否则配置冲突报错
// 设置是否可写
// writable:false,
// 配置新的值
// value:"张君宝"
}
})
// 尝试修改obj中的属性
obj.name = "张翠华";
obj.age = 23;
console.log(obj)
// 尝试枚举
for(var i in obj){
console.log(i,obj[i])
}
2 原型拓展
ES5为原型拓展了几个方法:
- isPrototypeOf(obj) 方法用于判断原型对象是否是参数实例对象的原型 参数 实例化对象 注 在查找的过程中,会查找整个原型链;即 原型的原型 也是 实例化对象的原型
- Object.setPrototypeOf(obj,prototype) 方法用于设置某个实例对象的原型 参数 obj 实例化对象 prototype 要给前面实例化对象设置的新原型对象(可以是null,也可以是一个对象)
- Object.getPrototypeOf(obj) 参数 实例化对象
ES5之前,实例化对象通过 proto 属性来获取原型对象; ES5中,不推荐以 __ 开头的语句,所以提供了 getPropertyOf() 方法用于获取对象的原型对象
<body>
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
</body>
<script>
// 获取类数组对象
var lis = document.getElementsByTagName("li");
// 定义数组
var arr = [10,20,30,40];
// 自定义对象
var obj = {name:"张三"}
// 查看Array.prototype这个原型是哪个实例化对象的原型
console.log(Array.prototype.isPrototypeOf(lis));//false
console.log(Array.prototype.isPrototypeOf(obj));//false
console.log(Array.prototype.isPrototypeOf(arr));//true
// 查找过程中,会查找整个原型链
console.log(Object.prototype.isPrototypeOf(lis));//true
console.log(Object.prototype.isPrototypeOf(obj));//true
console.log(Object.prototype.isPrototypeOf(arr));//true
// 获取实例化对象 arr 的原型对象
// 原始方式
console.log(arr.__proto__);//[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
// ES5方式
console.log(Object.getPrototypeOf(arr));//[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
// 对比
console.log(arr.__proto__ === Object.getPrototypeOf(arr));//true
// 设置实例化对象 arr 的原型对象 可以为null 也可以是一个对象 会修改整个原型链
Object.setPrototypeOf(arr,null);
console.log(Object.getPrototypeOf(arr));//null
Object.setPrototypeOf(arr,{a:10});
console.log(Object.getPrototypeOf(arr));//{a:10}
// 构造函数的原型不变 只是实例化对象原型指向改变
console.log(Array.prototype);//[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
</script>
3 取消对象可拓展性
Object.preventExtensions(obj) 方法用于取消对象的可拓展性 Object.isExtensible(obj) 判断对象是否取消了可拓展性 返回值 一个布尔值,返回true对象可以拓展属性 返回false对象不可拓展属性
注:当一个对象被取消了可拓展性之后,对象不能再拓展属性,但是可以修改和删除属性
var obj = {
a:1,
b:2
}
console.log("冻结前对象:",obj);
// 取消对象拓展
Object.preventExtensions(obj);
// 拓展属性
obj.c = 3;
// 删除属性
delete obj.a;
// 修改属性值
obj.b = 22;
// 判断对象是否可拓展
console.log(Object.isExtensible(obj));//true
console.log("冻结后并操作属性后的对象:",obj);
4 封闭对象
- Object.seal(obj) 封闭对象的属性
- Object.isSealed(obj) 判断对象属性是否被封闭 返回值布尔值,返回true 对象被封闭 返回false对象没有被封闭
注:当一个对象被封闭后,不能拓展和删除属性,但是可以修改属性。
var obj = {
a:1,
b:2
}
console.log("封闭前对象:",obj);
// 封闭对象
Object.seal(obj);
// 拓展属性
obj.c = 3;
// 删除属性
delete obj.a;
// 修改属性值
obj.b = 22;
// 判断对象是否被封闭
console.log(Object.isSealed(obj));//true
console.log("封闭后并操作属性后的对象:",obj);
5 冻结对象
Object.freeze(obj) 方法用于冻结对象的属性 Object.isFrozen(obj) 方法用于判断对象属性是否被冻结 返回值 一个布尔值 返回true对象被冻结,返回false对象没有被冻结
注:当一个对象被冻结后,不能拓展、修改和删除对象的属性;
var obj = {
a:1,
b:2
}
console.log("冻结前对象:",obj);
// 冻结对象
Object.freeze(obj);
// 拓展属性
obj.c = 3;
// 删除属性
delete obj.a;
// 修改属性值
obj.b = 22;
// 判断对象是否被冻结
console.log(Object.isFrozen(obj));//true
console.log("冻结后并操作属性后的对象:",obj);
6 Object.create() 创建对象的新方式
Object.create(proto,[ propertiesObject ]) 方法使用现有对象作为新创建的对象的原型来创建新对象。 参数
- proto 该对象应该是新创建对象的原型。可以是null
- propertiesObject 可选的。指定要添加到新创建的对象的属性描述符,以及相应的属性名称。这些属性对应于的第二个参数Object.defineProperties()对象属性的特性。
返回值 具有指定原型对象和属性的新对象。
// 学过的创建对象的方式
// var obj = {};
// var obj1 = new Object();
// var obj2 = Object();
// ES5新增创建一个空对象 第一个参数新创建对象的原型设置为null
// var obj3 = Object.create(null);
// console.log(obj3)
//使用Object.create() 来创建一个对象,第一个参数原型对象为一个常量对象
// var obj = Object.create({
// sayHi:function(){
// console.log("Hello");
// }
// })
// 第一个参数为空,第二个参数为 要创建对象的空对象的属性特性描述(类似于Object.defineProperty()设置的对象特性)
// var obj = Object.create(null,{
// name:{
// // 配置值
// value:"张三",
// // 配置是否可写
// writable:false,
// // 配置是否可枚举
// enumerable:false
// },
// age:{
// // 配置值
// value:10,
// // 配置是否可写
// writable:false,
// }
// })
// console.log(obj);
// console.log(obj.name);
// // 通过这种对象特性的方式创建的对象,默认属性不能被删除 修改
// obj.name = "haha";
// delete obj.age;
// console.log(obj);
// console.log(obj.name);
// 创建一个对象,并能继承另外一个对象的方法;将一个对象作为另外一个对象的原型
// 创建需要的原型对象
var prototype = {
sayHi:function(){
console.log("Hello");
}
}
// 创建需要的特性属性对象
var options = {
name:{
// 配置值
value:"张三",
// 配置是否可写
writable:false,
// 配置是否可枚举
enumerable:false
},
age:{
// 配置值
value:10,
// 配置是否可写
writable:false,
}
}
// 两者组合创建对象
var obj = Object.create(prototype,options);
console.log(obj)//查看原型
obj.sayHi();
利用Object.create()完善继承
// 定义父类
function People(name,sex,age){
this.name = name;
this.sex = sex;
this.age = age;
}
// 原型中定义方法
People.prototype.sayHi = function(){
return "姓名:" + this.name + ",性别:" + this.sex + ",年龄:" + this.age;
}
People.prototype.sayHello = function(){
return "Hello";
}
// 定义子类
function Student(name,sex,age,score){
// applay实现继承(改变调用对象
People.apply(this,arguments);
// 定义子类拓展的属性
this.score = score;
}
// var p = new People();
// delete p.name;
// delete p.sex;
// delete p.age;
// 子类继承父类中的方法 必须要使用原型继承 将子类的原型指向父类的实例
// Student.prototype = new Student();
// 使用Object.create()优化继承
Student.prototype = Object.create(People.prototype);
// 原型继承会造成结构的紊乱,将原型对象的构造函数手动改回到Student
Student.prototype.constructor = Student;
// 实例化对象
var s = new Student("张三","男",23,100);
// 调用父类原型中的方法
console.log(s.sayHi());
/*
* 原型继承,子类的原型就是父类的实例,这种方式会在子类的原型中多出几个无用的属性
* 此时,会在子类的原型中多出几个属性:name:undefined,age:undifined,sex:undefined
* 如果不考虑寄生组合继承这种方式进行优化,ES5还提供了Object.create()方法来优化
**/
自己封装实现Object.create()方法
// 定义父类
function People(name,sex,age){
this.name = name;
this.sex = sex;
this.age = age;
}
// 原型中定义方法
People.prototype.sayHi = function(){
return "姓名:" + this.name + ",性别:" + this.sex + ",年龄:" + this.age;
}
People.prototype.sayHello = function(){
return "Hello";
}
// 定义子类
function Student(name,sex,age,score){
// applay实现继承(改变调用对象
People.apply(this,arguments);
// 定义子类拓展的属性
this.score = score;
}
// 取消Object.create方法
Object.create = null;
// 重新自定义 create 方法,实现相同的功能
Object.create = function(prototype){
// 定义一个构造函数
var F = function(){
}
// 将F的原型指向传入的原型
F.prototype = prototype;
// 返回F的实例
return new F();
}
// 使用Object.create方法实现继承
Student.prototype = Object.create(People.prototype);
// 实例化对象
var s = new Student("张三","男",23,100);
console.log(s.sayHi());