JS(10)理解对象and对象创建过程【涉及原型】

149 阅读6分钟

理解对象

对象JavaScript 中的对象是一种复合值,可以包含属性和方法。是一组属性的无序集合!

属性的特征

ECMA-262使用了一些内部特性来描述属性的特征,开发者不可以在 JS 中直接访问这些特性。但可以通过Object.defineProperty()方法来修改属性的特征

  • 该方法接收三个参数

    ( 属性所在对象 , 属性名 , 特性对象)
    
  • 有四个特性来描述数据属性: 默认都为 true

    • [[Configurable]] :属性是否可以通过 delete 删除并重新定义
    • [[Enumerable]] : 属性是否可以通过 for-in循环枚举
    • [[Writerable]] : 属性是否可以被修改
    • [[Value]] : 属性存的值

定义多个属性

Object.defineProperties()同时定义多个属性,参数为描各个属性的对象

读取对象的属性

  • Objet.getOwnPropertyDescriptor() 读对象中指定属性的特性

    - 该方法接收两个参数 :
           - 一个是要读取的属性所在的对象
           - 一个是要读取的属性名
    
  • Objet.getOwnPropertyDescriptors()读对象中所有属性的特性

     - 该方法只接收一个参数 :
             - 要读取的对象
     - 该方法返回对象,对象中包括各个属性的特性对象
    

对象属性名的遍历

  • for - in 会遍历对象的所有可枚举的属性,包括实例属性和原型属性

  • Object.keys() 【可枚举

       - Object.keys(原型对象) 返回原型属性的属性名的字符串形式的数组
       - Object.keys(实例对象) 返回实例属性的属性名的字符串形式的数组
    
  • Object.getOwnPropertyName() 【可枚举不可枚举都可以得到】【实例属性

       - 获得所有实例属性,不管是否可以枚举 (constructor属性不可枚举,但也可被遍历到)
    
  • Object.getOwnPropertySymbols()

       - 获得所有以符号 Symbol 为键的属性名
    

对象的迭代

  • Object.entries()

    • 返回对象键值对的数组,每对键值对都对应一个数组,且属性名以字符串的形式返回
  • Object.values()

    • 返回对象属性值的数组

合并对象

Object.assign() 该方法接收一个目标对象和多个源对象作为参数

-   把多个源对象复制到目标对象中

注意:

  • 该方法对每个源对象执行的是浅复制 ,即复制的是每个源对象中属性的引用,所以如果多个源对象中存在同名属性,那么会产生覆盖效果,即后面出现的同名属性的值会覆盖之前出现的同名属性!!!
  • 如果在赋值期间出错,则操作会终止并退出,所以该方法可能只会完成部分的赋值!!!

创建对象

<1> 工厂模式【封装为普通函数】

它通过使用函数来封装和返回新对象的方法。工厂函数可以根据传入的参数和逻辑创建不同的对象实例。

function createPerson(name, age) {
  var person = {};

  person.name = name;
  person.age = age;

  person.sayHello = function() {
    console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
  };

  return person;
}

var person1 = createPerson('Alice', 25);
var person2 = createPerson('Bob', 30);

优点:

  • 是可以通过 封装创建对象 的过程来简化对象的创建,避免了重复的代码。它适用于创建相似但不完全相同的对象实例。

缺点:

  • 是无法识别对象的具体类型 ,所有的对象都是通过相同的工厂函数创建的,可能会导致难以追踪和维护。

<2> 构造函数模式

构造函数 其实就是一般的函数(为了区别,取名时开头大写)。JS把使用 new 操作符调用的函数就叫做构造函数!!!构造函数也可以进行普通调用,此时其内部的属性会被添加到全局 windows对象身上(如果是在全局作用中调用时),即此时(构造)函数内部的this执行 window!!

function Person(name, age) {
  this.name = name;
  this.age = age;

  this.sayHello = function() {
    console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
  };
}

var person1 = new Person('Alice', 25);
var person2 = new Person('Bob', 30);

问:使用 new 操作符后隐式进行了哪些步骤呢?

  • 创建一个新对象 newObj

  • 设置该实例对象的原型 _ _ proto _ _ 指向该构造函数的原型对象(prototype属性)

  • 让该构造函数内部的this指向该创建的新对象,并执行该构造函数,初始化实例对象

  • 判断该构造函数的返回类型,如果不返回或者返回的是原始值类型,就返回该新创建的对象;如果返回的是引用类型,就照着返回!!

模拟实现:

因为 new 操作符不是一个函数,不好直接模拟,所以我们写一个函数,命名为 objectFactory(构造函数,参数),来模拟 new 的效果。

javascript
复制代码
function objectFactory(){
     var newObj = new Object();
     // 从传入的参数中,截取获得第一个,得到构造函数
     var Constructor = [].shift.call(arguments);
     // 设置新创建的对象实例的原型属性
     newObj.__proto__ = Constructor.prototype;
     // 执行构造函数(绑定构造函数内部的this指向新创建的对象)
     var result = Constructor.call(newObj,...arguments)
     // 判断构造函数的返回类型
     return typeof ret === 'object' ? ret : obj;
}

注意: 自定义类型的构造函数也可以用 instanceOf 确定类型

优点:

  • 构造函数模式的优点是可以识别对象的具体类型,便于管理和维护。

缺点:

  • 每个对象实例都会包含相同的方法,这可能会导致浪费内存。【即方法不可复用】

     - 每次实例化对象都会创建方法,创建方法的过程其实也是实例化对象的过程(Function

<3> 原型模式【构造函数 + 方法放到原型对象上】

3.1 原型对象

每个函数都会有一个 prototype属性 (函数本质上也是一个对象) ,该属性是一个对象 ,它就是 原型对象。在它上面定义的属性和方法可以被由该构造函数创建的对象实例们所共享!!!Why?分析如下:

先看几个容易混淆的概念:

  • 构造函数有一个 prototype属性指向它的原型对象
  • 原型对象也有一个constructor属性指回它的构造函数
  • 实例对象是由构造函数 new 出来的 ,对象实例上有属性[[Prototype]] or _ _proto_ _指向它的原型对象!!!

所以说放到原型对象上的方法,实例对象可以通过自己的 _ _proto_ _属性去访问!!!

3.2 一些个判断实例对象与原型对象对应关系的方法
  • isPrototypeOf()  判断实例对象是不是某个原型对象的实例?

原型对象A . isPrototypeOf( 实例1 ) == > 判断实例 1 是不是原型对象 A 的实例

  • getPrototypeOf()  获得实例的原型对象

实例 . getPrototypeOf()

3.3 原型层级 (原型查找机制)

通过对象访问属性时,搜索会开始于实例对象本身;如果没找到,会去它的原型对象上找......

注意:

  • 可以通过实例对象访问原型对象上的属性和方法,但不能通过实例对象改写原型对象!!

      - 如果不想用原型对象上的某个属性,可以在实例对象上添加一个与该属性同名的属性,则
      该属性会**遮蔽掉**原型对象上的属性!!!
    
  • hasOwnProperty( 属性名 ) 在实例对象调用 ,可以确定属性是在实例身上还是在原型对象上!!!

    • 区别于 in操作符

       - 属性名的字符串形式 in 对象 ,用于检验该对象上是否存在该属性
       - 属性不论在实例身上还是在原型对象上都行,都返回 true
      

优点:

  • 通过共享对象的属性和方法来减少内存占用

缺点:

  • 共享属性和方法可能会导致它们被意外修改【尤其是引用类型的属性】