第八节:JS通过Object.defineProperty属性的精准控制和创建对象的几种方案
一. 对象的创建和属性控制
1. 最早的两种创建对象形式
(1). 通过new Object()创建
(2). 字面量的方式
代码分享:
// 1. 创建方式1:通过new Object()创建 { let obj = new Object(); obj.name = "ypf"; obj.age = 18; obj.getInfo = function () { console.log(this.name + this.age); }; } // 2. 创建方式2:字面量 { let obj = { name: "ypf2", age: 20, getInfo: function () { console.log(this.name + this.age); }, }; }
2. 对象属性的控制
(1). 获取属性
(2). 给属性赋值
(3). 删除属性 使用delete关键字
(4). 遍历属性 使用 for in 关键字
代码分享:
let obj = { name: "ypf", age: 18, msg1: "info1", msg2: "info2", }; // 获取属性 console.log(obj.name, obj.age); // 给属性赋值 obj.name = "ypf2"; console.log(obj.name, obj.age); // 删除属性 delete obj.name; console.log(obj); // 增加属性 obj.msg3 = "info3"; //遍历属性 for (const key in obj) { console.log(key); }
二. Object.defineProperty属性的精准控制
1. 说明
用途:定义新属性或者修改已有属性,可以对属性进行一些严格的限制,返回该对象。
用法: Object.defineProperty(obj,prop,descriptor), 三个参数的含义
(1). obj要定义属性的对象;
(2). prop要定义或修改的属性的名称或 Symbol;
(3). descriptor要定义或修改的属性描述符;
分类:根据最后一个参数,可以分为:数据属性描述符 和 存储属性描述符
准备一个前置对象,后面的测试均基于该对象测试。
let obj = { name: "ypf", age: 18, };
2. 数据属性描述符
有4个属性可以配置,如下图:
(1). 逐个属性详解
{ Object.defineProperty(obj, "address", { value: "青岛市", //赋值,默认值为undefined configurable: false, // 不可删除,不可重新定义,默认值false(表示不可删除) enumerable: true, //是否可以被遍历出来 默认值false writable: false, //是否可以赋值 默认值false }); //1.1 测试configurable delete obj.address; console.log(obj); //仍然有address属性,无法删除 //1.2 测试enumerable console.log(Object.keys(obj)); // [ 'name', 'age', 'address' ] //1.3 测试writeable obj.address = "北京"; console.log(obj.address); //青岛市, 说明写入失败 }
(2). 通过属性描述符defineProperty定义时的默认值
value是undefined,其它三个属性configurable、enumerable、writable三个属性均为false
{ Object.defineProperty(obj, "address", { value: "青岛市", //赋值,默认值为undefined }); //1.1 测试configurable,默认值false(表示不可删除) delete obj.address; console.log(obj); //仍然有address属性,无法删除 //1.2 测试enumerable,默认值false (表示不能被遍历) console.log(Object.keys(obj)); // [ 'name', 'age' ] //1.3 测试writeable,默认值false (表示不能被修改) obj.address = "北京"; console.log(obj.address); //青岛市, 说明写入失败 }
(3). 字面量中的属性默认值
value: 赋值的value
configurable: true,可以被重新定义或删除
enumerable: true, 可以被遍历出来
writable: true 可以被重新赋值
{ let info = { name: "ypf", age: 18, }; }
3. 存储属性描述符
有4个属性可以配置,如下图:
案例:希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符
代码分享:
{ function foo1() { console.log("获取了一次address的值"); } function foo2() { console.log("设置了addres的值"); } let address = "青岛市"; Object.defineProperty(obj, "address", { configurable: true, enumerable: true, get() { foo1(); return address; }, set(value) { foo2(); address = value; }, }); console.log(obj.address); //青岛市 obj.address = "北京市"; console.log(obj.address); //北京市 }
4. 定义多个属性
{ Object.defineProperties(obj, { msg1: { value: "haha1", configurable: true, enumerable: true, writable: true, }, msg2: { value: "haha2", }, }); console.log(Object.keys(obj)); //[ 'name', 'age', 'msg1' ] }
5. 对象方法补充
(1). 获取属性描述符:getOwnPropertyDescriptor、getOwnPropertyDescriptors
(2). 禁止对象添加新属性:Object.preventExtensions(obj);
(3). 密封对象,不允许配置和删除属性:Object.seal(obj);
(4). 冻结对象,不允许修改现有属性:即 (writable: false): Object.freeze(obj);
代码分享:
var obj = { // 私有属性(js里面是没有严格意义的私有属性) _age: 18, _eating: function () {}, }; Object.defineProperties(obj, { name: { configurable: true, enumerable: true, writable: true, value: "ypf", }, age: { configurable: true, enumerable: true, get: function () { return this._age; }, set: function (value) { this._age = value; }, }, }); // 1.获取对象属性描述符 { console.log(Object.getOwnPropertyDescriptor(obj, "name")); //{ value: 'why', writable: true, enumerable: true, configurable: true } console.log(Object.getOwnPropertyDescriptor(obj, "age")); //{ get: [Function: get], set: [Function: set],enumerable: true, configurable: true } //获取所有属性描述符 console.log(Object.getOwnPropertyDescriptors(obj)); } // 2.禁止对象添加新属性 { Object.preventExtensions(obj); obj.msg1 = "haha1"; obj.msg2 = "haha2"; console.log(obj); //msg1 和 msg2 没有添加成功 } // 3. 密封对象,不允许配置和删除属性 { Object.seal(obj); delete obj.name; console.log(obj); //name属性没有删除 } // 4. 让属性不可以修改(writable: false) { Object.freeze(obj); obj.name = "hhh"; console.log(obj.name); //ypf, 修改失败 }
三. 创建对象的几种方案
1. 工厂模式
(1).之前的两种创建对象的方案: (1). 字面量 (2). new Object方式
(这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码;)
(2).工厂模式其实是一种常见的设计模式; 通常我们会有一个工厂方法,通过该工厂方法我们可以产生想要的对象;
(3). 缺陷:工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型
代码分享:
function createPerson(name, age, height, address) { var p = {}; p.name = name; p.age = age; p.height = height; p.address = address; p.eating = function () { console.log(this.name + "在吃东西~"); }; p.running = function () { console.log(this.name + "在跑步~"); }; return p; } var p1 = createPerson("张三", 18, 1.88, "广州市"); var p2 = createPerson("李四", 20, 1.98, "上海市"); var p3 = createPerson("王五", 30, 1.78, "北京市"); // 工厂模式的缺点(获取不到对象最真实的类型,只能获取到一个字面量) console.log(p1, p2, p3);
2. 构造函数模式
(1). 几个要素:构造函数的首字母一般是大写,构造函数内部的this,会指向创建出来的新对象(p1,p2);使用new操作符。
(2). new操作符的作用:
p1. 在内存中创建一个新的对象(空对象);
p2. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
p3. 构造函数内部的this,会指向创建出来的新对象;
p4. 执行函数的内部代码(函数体代码);
p5. 如果构造函数没有返回非空对象,则返回创建出来的新对象;
(3). 好处:和工厂模式相比,这个构造函数可以确保我们的对象是有Person的类型的,实际是constructor的属性
(4). 缺点:构造函数也是有缺点的,它在于我们需要为每个对象的函数去创建一个函数对象实例;
代码分享:
function Person(name, age) { this.name = name; this.age = age; this.eating = function () { console.log(this.name + "吃东西"); }; this.running = function () { console.log(this.name + "在跑步"); }; } var p1 = new Person("ypf1", 18); var p2 = new Person("ypf2", 20); // 和工厂模式相比,构造函数这种模式能获取到具体的类型(Person) console.log(p1); console.log(p2); // 缺点:每个对象都要创建个实例 console.log(p1 === p2);
3. 对象原型剖析
(1).JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个属性可以称之为对象的原型(隐式原型)。
(2). 那么这个对象有什么用呢?
A. 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
B. 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
C. 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;
(3). 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
答案是有的,只要是对象都会有这样的一个内置属性;
(4). 获取的方式有两种:
方式一:通过对象的 __proto__ 属性可以获取到(早期浏览器自己添加的,存在一定的兼容性问题);【注意是:两个--】
方式二:ES5之后,通过 Object.getPrototypeOf 方法可以获取到;
代码分享:
let obj1 = { name: "ypf", }; let obj2 = {}; // 一. 获取原型的方式 // 方式1:早期获取原型的方法 { console.log(obj1.__proto__); //[Object: null prototype] {} console.log(obj2.__proto__); //[Object: null prototype] {} } // 方式2:Object.getPrototypeOf { console.log(Object.getPrototypeOf(obj1)); //[Object: null prototype] {} console.log(Object.getPrototypeOf(obj2)); //[Object: null prototype] {} } // 二. 原型的作用 obj1.__proto__.age = 18; console.log(obj1.name);
4. 函数原型剖析
所有的函数都有一个prototype的属性(显式原型),同时函数又是一个对象,所以函数也有__proto__属性
注意:对象是没有prototype属性的!!
代码分享:
function Test1() { console.log(1111); } console.log(Test1.prototype); // let t1 = new Test1(); let t2 = new Test1(); console.log(t1.__proto__ === Test1.prototype); //true console.log(t2.__proto__ === Test1.prototype); //true
5. 创建对象的内存表现
代码分享:
/* 创建对象的内存表现 p1.在内存中创建一个新的对象(空对象); p2.这个对象内部的__proto__属性会被赋值为该构造函数的prototype属性 内存图,详见ppt */ // 见内存图1 function Person() {} var p1 = new Person(); var p2 = new Person(); // 见内存图4 // p1.name = "ypf"; // 其它 // p1.__proto__.name = "ypf2"; // Person.prototype.name = "ypf3"; p2.__proto__.name = "ypf6"; console.log(p1.name); //ypf6
6. 函数原型上的属性
代码分享:
/* 函数原型上的属性 1. 默认情况下函数原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象; */ function foo() {} // 1. constructor属性 /* foo.prototype这个对象上有个constructor属性 */ // { // console.log(foo.prototype); //{} // console.log(Object.getOwnPropertyDescriptors(foo.prototype)); // /* { // constructor: { // value: [Function: foo], // writable: true, // enumerable: false, // configurable: true // } // } */ // console.log(foo.prototype.constructor); //[Function: foo] // console.log(foo.prototype.constructor.name); //name // } // 2. 重写原型对象 /* 我们需要在原型上添加过多的属性,通常我们会重新整个原型对象, 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性;p而我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函 数, 而不是Person构造函数了 */ { foo.prototype = { // constructor: foo, name: "why", age: 18, height: 1.88, }; var f1 = new foo(); console.log(f1.name, f1.age, f1.height); } { // 真实开发中我们可以通过Object.defineProperty方式添加constructor Object.defineProperty(foo.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: foo, }); }
7. 创建对象-原型和构造函数组合
代码分享:
/* 创建对象-原型和构造函数组合 */ function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eating = function () { console.log(this.name + "eating"); }; Person.prototype.running = function () { console.log(this.name + "running"); }; let p1 = new Person("ypf1", 18); let p2 = new Person("ypf2", 20); p1.eating(); p1.running(); p2.eating(); p2.running();
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。