第八节: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);
    },
  };
}
View Code

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); //北京市
} 
View Code

4. 定义多个属性 

  使用Object.defineProperties() 定义多个属性
{
  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, 修改失败
} 
View Code

 

三. 创建对象的几种方案

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);
View Code

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);
View Code

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);
View Code

 

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
View Code

 

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,
  });
}
View Code

 

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();
View Code

 

 

 

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-02-28 09:00  Yaopengfei  阅读(261)  评论(1编辑  收藏  举报