第五篇 引用类型 - 对象 - Object

发布时间 2023-03-28 14:16:26作者: caix-1987

概念

什么是对象: 对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

  let obj = {
       name: "caixin",
       age: 35
     }

创建对象的方式

new Object()
 let obj = new Object();
 
     obj.name = "caixin"
字面量方式
 let obj = {
       name: "caixin",
       age: 35
     }
使用 Object.create(原型对象,自定义属性对象)
 Object.create(原型对象,自定义属性对象)方法创建一个新对象,使用现有的对象来提供新创建的对象的 _ proto _
 
 let tempObj = {
   x: 1,
   y: 2
 }
 
 let newAttrObj = {
   a:{
     writable:true,
     configurable:true,
     value:100
   }
 }
 
 let obj = Object.create(tempObj,newAttrObj);
 
 console.log(obj) 
 
 => {
       a:100,
       _proto_:{
         x:1,
         y:2
       }
    }
    
 创建一个普通对象
 
   let obj_1 = Object.create(Object.prototype)
   obj_1.toString(); => [object,object]
   
 创建一个空对象,但该对象不继承任何属性和方法
 
   let obj_2 = Object.create(null)
   obj_2.toString() => TypeError: obj2.toString is not a function
工厂方式
 function createPerson(name,age){
 
     let person = new Object();
     
     person.name = name;
     person.age = age;
     
     return person;
 }
 
 let person_1 = createPerson("caixin",35)
构造函数
function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayHi = function (){
        console.log("hello")
    }
}

let person_1 = new Person("caixin",35)

扩展:  实例对象是通过构造函数来创建的,创建的过程叫实例化。
_ proto _
实例对象 person_1 有个属性 _ proto _ ,称之为原型,这既是一个属性也是一个对象,这个属性是给浏览器使用的,不是标准的属性,它的下面还有一个属性 constructor ,称之为构造器,constructor 的值指向生成该对象实例的构造函数 Person 。
prototype
构造函数 Person 下面有一个属性 prototype,也称之为原型,这个属性是给程序员使用的,是标准属性,它的下面也有一个属性 constructor ,也叫构造器,constructor 的值指向自己 。
_ proto _ 与 prototype 的关系
 js对象都有 _ proto _ 属性,也就是构造函数 Person 与实例对象 person_1 都有
 
 只有函数/方法才有 prototype 属性
 
 实例对象的 _ proto _ 指向了构造函数的原型对象 prototype
 
 实例对象的构造器指向创建自己的构造函数
 
 console.log(person_1._proto_.constructor == Person)  => true
  
 console.log(person_1._proto_.constructor == Person.prototype.constructor)  => true
如何判断实例对象是不是这种数据类型
console.log(d instanceof Person) => false

说明对象实例d不是由构造函数Person来创建的
对象原型实现数据共享
 构造函数的原型对象( prototype )中的方法是可以被实例对象直接访问的
 
 通过原型来添加方法,解决数据共享,节省内存空间。
 
 原型中的方法是可以互相访问的
new 操作符具体干了什么 ?
  1、在内存中创建一个空的对象

  2、让 this 指向这个空的对象,并继承改对象原型

  3、调用构造函数,目的是给对象属性和方法

  4、返回一个对象
键名 ( 属性 )
  对象的所有键名都是字符串,ES6 引入了 Symbol 值也可以作为键名,所以加不加引号都可以
  
  如果键名是数值,会被自动转为字符串
  
  如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错
  
  let obj = {1p:'caixin'} => 报错
  
  对象的每一个键名又称之为‘属性’,它的‘键值’可以是任何类型。包括 function。如果属性的值还是一个对象,就行成了链式引用。
  
  属性可以动态创建,不必在对象声明时就指定。
对象的引用
  如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
  
  let obj1 = {}
  let obj2 = {}
  
  obj1.a = 1
  obj2.a => 1
  
  obj2.b = 2 
  obj1.b => 2
  
注意: 如果取消某一个变量对于原对象的引用,不会影响另一个变量

  let obj1 = {}
  let obj2 = o1
  
  obj1 = 1
  obj2 => {}
  
注意: 如果两个变量指向同一个原始类型的值,那么,变量这时都是值的拷贝

  let x = 1
  let y = x
  
  x = 2
  
  y => 1
表达式还是语句

对象采用大括号表示,这导致一个问题: 如果行首是一个大括号,它到底是表达式还是语句 ?

   为了避免这个歧义,javascript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
  
  { console.log(1) } => 1
  
  ( {foo:123} ) => 123
  
  ( console.log(123) ) // 报错

   如果要解释为对象,最好在大括号前加上圆括号,因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。 

对象属性的操作

属性的读取
  读取对象有两种方法: 点运算符 和 方括号运算符
  
  let obj = {
    name:'caixin'
  }
  
  obj.name  => caixin
  obj['name']  => caixin
  
  注意:
  
     1、如果是方括号运算符,键名必须是在引号里面,否则会当作变量处理
     
       let foo = "bar"
       
       let obj = {
         foo:1,
         bar:2,
       }
       
       obj.foo => 1
       obj[foo]  => 2 没有引号,当作变量处理
       
     2、方括号内可以使用表达式
     
       obj['f' + 'oo'] => 1
       
     3、数值键可以不加引号,但会自动转成字符串;数值键不能使用点运算符,会被当成小数点,只能使用方括号。
属性的查看
  Object.keys()  和  for in 语句
  
  let obj = {
    name:"caixin"
    age:"35"
  }
  
  console.log(Object.keys(obj)) => ["name","age"]
  
  for (let i in obj){
    console.log(i)  =>  name age
  }
  
  注意 不能使用 for of 语句
  
  for(let i of obj){
    console.log('for of',i)  => obj is not iterable
  }
属性的删除
  delete 命令用于删除对象的属性,删除成功后返回 true
  
  let obj = { p: 1 }
  
  Object.keys(obj)  => ["p"]
  
  delete obj.p   => true
  
  obj.p  => undefined
  
  Object.keys(obj)  => []
  
  注意:
  
     1、删除一个不存在的属性,delete 不报错,而且返回 true , 因此不能根据 delete 命令的结果认定某个属性是否存在。
     
     2、只有一种情况 delete 命令会返回 false , 那就是该属性存在,且不得删除。delete 只能删除对象本身的属性,无法删除继承的属性。
     
     3、删除数组元素,数组的长度不受影响
     
     let arr = [1,2,3]
     
     delete arr[1];
     console.log(arr) => [1,empty,3]
属性的检测
  1、in 运算符用于检查某个对象是否包含某个属性,如果包含返回 true 否则返回 false ( 可检测自有属性和继承属性,如果包含则返回 true)
  
  let obj = { name:'caixn' }
  
  console.log( 'name' in obj ) => true
  console.log( 'toString' in obj ) => true
  console.log('age' in obj ) => false
  
  注意:
  
    1、in 运算符有一个问题,不能识别哪些属性是自身的,哪些属性是继承的。
    
    2、可以使用对象的 hasOwnProperty 方法判断一下,是否为对象自身的属性。
    
    let obj = {}
    
    if('toString' in obj){
      console.log(obj.hasOwnProperty('toString'))  => false
    }
    
 2、hasOwnProperty()
 
    可检测自有属性,包含则返回true。如果是继承属性将返回false。
   
    var o = {x: 1,  k: undefined};
   
       o.hasOwnProperty("x"); // true - 自有属性
       o.hasOwnProperty("k"); // true - 自有属性
       o.hasOwnProperty("y"); // false - 非 o 对象属性
       o.hasOwnProperty("toString"); // false - 继承属性 
       
 3、propertyIsEnumerable()
 
    hasOwnpreperty()的增强版, 只有检测到是自有属性且该属性的可枚举性为true时,才返回true。
   
 4、使用 !==
 
   通过 !== 判断一个属性是否是 undefined,如果属性值为 undefined 则无法检测

对象属性的类型

对象属性是由名字、值和一组特性(attribute)组成。在 es5 中 ,属性值可以用一个或者两个方法替代,就是 getter 和 setter 。由 getter 和 setter 定义的属性称为 “存储器属性” 也叫做 访问器属性。

对象的每个属性都有一个描述对象 (Descriptor),用来控制属性的行为。

ECMAScript 中有两种属性: 数据属性 和 访问器属性

数据属性

数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有 4 个 描述其行为的特性,又叫属性特性。

属性名 说明 默认值
configurable 能否通过 delete 删除属性从而重新定义属性;能否修改属性的特性;能否把属性修改为访问器属性 true
enumerable 能否通过 for - in 循环返回属性 true
writable 能否修改属性的值 true
value 属性的数据值,属性值存储和读取都是这个位置 undefined
可通过 Object.getOwnPropertyDescriptor() 查看属性的描述对象
  使用: Object.getOwnPropertyDescriptor(属性所在对象,属性名)
     
     let obj = { name: "caixin" };
     
     let attributeObj = Object.getOwnPropertyDescriptor(obj,name)
     
     console.log(attributeObj) 
     
          => {
               configurable:true,
               enumerable:true,
               writable:true,
               value:"caixin"
             }
可通过 Object.defineProperty() 修改属性默认特性
  使用: Object.defineProperty(属性所在对象,属性名,描述符对象) 
  
      let obj = {};
      
      Object.defineProperty(obj,"name",{
          configurable: false,
          enumerable: false,
          writable: false,   => 不可修改
          value: "caixin"
      })
      
      console.log(obj.name)  => "caixin"
      
      obj.name = "caiqiran"
      
      console.log(obj.name)  => "caixin"  修改无效
      
  注意:
  
      1、使用 Object.definedProperty() 定义新的属性,描述对象的属性默认值为false
      
      let obj = { age: 35}
      
      Object.definedProperty(obj,"name",{
          value: "caixin"
      })
      
      console.log(Object.getOwnPropertyDescriptor(obj,"name"))
      
        新的属性,描述对象属性默认是 false
      
      => {
           configurable: false
           enumerable: false
           writable: false
           value:"caixin"
         }
         
      Object.definedProperty(obj,"age",{
         value: 32
         configurable: false
      })   
         
      console.log(Object.getOwnPropertyDescriptor(obj,"age")) 
      
         如果是已有属性则只影响设置的属性
         
      =>   {
             configurable: false
             enumerable: true
             writable: true
             value:32
           }  
           
      2、一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时,再调用 Object.defineProperty()方法修改除 writable 之外的特性,都会导致错误。
      
       var person = {}; 
       
       Object.defineProperty(person, "name", { 
          configurable: false, 
          value: "Nicholas" 
       }); 
       
       => 抛出错误
       Object.defineProperty(person, "name", { 
          configurable: true, 
          value: "Nicholas" 
       });
访问器属性

访问器属性不包含数据值;它们包含一对 getter 和 setter 函数(不过,这两个函数都不是必需的)。

读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值。

写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据。

属性名 说明 默认值
get 读取属性时调用的函数,只有 get 则是一个只读属性 undefined
set 写入属性时调用的函数,只有 set 则是一个只写属性 undefined

注意:访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。

使用 Object.defineProperty() 定义单个属性
 let book = {
    _year: 2022,
    edition: 3
 }
 
 Object.defineProperty(book,"year",{
    get:function(){
       return this._year;
    },
    
    set:function(newValue){
       if(newValue > 2005){
          this._year = newValue;
          this.edition += newValue - 2018;
       }
    }
 })
 
 book.year = 2020;
 
 console.log(book.year) => 2020
 console.log(book.edition) => 3
使用Object.defineProperties()定义多个属性
let book = {};

Object.defineProperties(book,{
   _year:{
     value:2022  => 数据属性
   },
   edition:{
     value:1  => 数据属性
   },
   year:{  => 访问器属性
     get:function(){
        return this._year;
     },
     set:function(newValue){
        if (newValue > 2018) { 
            this._year = newValue; 
            this.edition += newValue - 2018; 
        } 
     }
   }
})
对象属性类型总结

1、数据属性是属性自带的,数据属性可以被继承。

2、访问器属性是需要创建的,访问器属性可以被继承。

3、相关方法

方法 作用 说明
Object.getOwnPropertyDescriptor() 属性的描述对象 -
Object.defineProperty() 修改属性默认特性/定义单个属性 数据属性和访问器属性都可用,使用后数据属性默认值为 false
Object.defineProperties() 定义多个属性 数据属性和访问器属性可用,但注意数据属性设置 false 后不能修改为 true,所以慎用

对象的固有属性

每个对象都有: 原型属性(prototype)、类 (class)、可拓展性(extensible)这 三个 属性

原型属性 prototype
对象的 原型属性 是用来 继承属性 的,原型属性 也简称为 原型。
原型链
谈到继承时,javaScript 只有一种结构: 对象。

每个实例对象 Object 都有一个私有属性 (称之为 _proto_ )指向它的构造函数的原型对象 protoptype 。该原型对象也有一个自己的原型对象 ( proto ),层层向上直到一个对象的原型对象为 null 。这种链式结构叫做: 原型链。

null 没有原型,并作为这个原型链中的最后一个环节。也可表示空对象指针。
__ proto __ 和 prototype 的关系和区别
在所有实现中,对象实例都没办法通过 [[prototype]] 来访问原型,所以定义了 __proto__ 指向对象构造函数的 prototype 。[[prototype]] 可以使用 __proto__ 访问。

   1、prototype 是对象的原型属性,可被继承
   
   2、__proto__ 指向对象构造函数的 prototype
   
   3、constructor 属性将原型对象指向关联的构造函数
isPrototypeOf()
 isPrototypeOf(): 测试一个对象是否存在于另一个对象的原型链上
 
    function Foo() {}
    function Bar() {}
    function Baz() {}

    Bar.prototype = Object.create(Foo.prototype);
    Baz.prototype = Object.create(Bar.prototype);

    var baz = new Baz();

    console.log(Baz.prototype.isPrototypeOf(baz)); => true
    console.log(Bar.prototype.isPrototypeOf(baz)); => true
    console.log(Foo.prototype.isPrototypeOf(baz)); => true
    console.log(Object.prototype.isPrototypeOf(baz)); => true
类属性 class

对象的类属性(class attribute)是一个字符串,用来表示对象的类型信息。可以通过 toString() 方法来查询它。 默认的 toString() 方法会返回下面格式的字符串:

[object class]

toString()方法可以被重写,所以为了能得到正确的数据,需要间接调用 Function.call() 方法。

function classof(o) {
   return Object.prototype.toString.call(o);
  // return Object.prototype.toString.call(o).slice(8, -1);
}

classof(null);         => [object Null]
classof(undefined);    => [object Undefined]
classof(1);            => [object Number]
classof("");           => [object String]
classof(false);        => [object Boolean]
classof({});           => [object Object]
classof([]);           => [object Array]
classof(/./);          => [object RegExp]
classof(new Date());   => [object Date]
classof(window);       => [object Window]
可拓展性

对象的可拓展性表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显示可拓展的。

检测可拓展性
 Object.isExtensible()
 
 Object.isSealed()
 
 Object.isFrozen()
影响可拓展性的方法
 Object.preventExtensions()
 
 Object.seal()
 
 Object.freeze()
注意
 1、一旦转为不可拓展后,就无法再转回可拓展
 
 2、preventExtensions 只影响到对象本身的可拓展性
 
 var a = {};
 
 Object.isExtensible(a); => true
 Object.preventExtensions(a); => Object.seal()、Object.freeze() 同理
 
 a.name = 'a';
 a;  => {}
 
 Object.isExtensible(a); => false

对象的方法

对象原型上 继承 的方法
方法名 功能
hasOwnProperty() 检测自身属性
propertyIsEnumerable() 检测自身属性且该属性的可枚举性为 true 时
isPrototypeOf() 测试一个对象是否存在于另一个对象的原型链上
toString() 返回表示调用这个方法的对象值的字符串
toLocaleString() 返回本地化的字符串
toJSON() 返回序列化的结果
valueOf() 将对象转换为原始值
const a = {name: 123};

a.toString(); => [object Object]
a.toLocaleString(); => [object Object]
a.valueOf(); => {name: 123}

const b = [1, 2, 3]  

b.toString(); => 1,2,3
b.toLocaleString();
b.valueOf(); => [1, 2, 3]

const d = new Date();

d.toString(); => Fri Aug 28 2020 11:29:42 GMT+0800 (中国标准时间)
d.toLocaleString(); => 2020/8/28 上午11:29:42
d.toJSON(); => 2020-08-28T03:29:42.460Z
d.valueof(); => 1598585382460
Object 构造函数 上 的方法
Object 属性

1、Object.length: 值为 1
2、Object.prototype: 可以为所有 Object 类型的对象添加属性

ES5 方法
方法名 功能
Object.create() 使用指定的原型对象和属性创建一个新对象
Object.defineProperty() 给对象添加一个属性并指定该属性的配置
Object.defineProperties() 给对象添加多个属性并分别指定它们的配置
Object.getOwnPropertyNames() 返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性
Object.getOwnPropertySymbols() 返回一个数组,它包含了指定对象自身所有的符号属性
Object.getOwnPropertyDescriptor() 返回对象指定的属性配置
Object.isExtensible() 判断对象是否可扩展
Object.preventExtensions() 防止对象的任何扩展
Object.seal() 创建一个“密封”对象,实际上是调用Object.preventExtensions()把所有属性标记为configurable:false,仅可修改属性的值
Object.isSealed() 判断对象是否已经密封
Object.freeze() 创建一个“冻结”对象,实际上调用Object.seal(),并把所有属性标记为writable:false
Object.isFrozen() 判断对象是否已经冻结
ES6 方法
方法名 功能
Object.is() 比较两个值是否相同。所有 NaN 值都相等(这与=不同)
Object.assign() 通过复制一个或多个对象来创建一个新的对象
Object.getOwnPropertyDescriptors() 返回对象所有属性的描述对象
Object.setPrototypeOf() 设置对象的原型(即内部 [[Prototype]] 属性)
Object.getPrototypeOf() 返回指定对象的原型对象
Object.keys() 返回一个包含所有给定对象自身可枚举属性名称的数组
Object.values() 返回给定对象自身可枚举值的数组
Object.entries() 返回给定对象自身可枚举属性的 [key, value] 数
Object.fromEntries() Object.entries()的逆操作,用于将一个键值对数组转为对象

javascript对象类别划分

原生对象 ( native object )

原生对象也可以叫做本地对象或者内部对象

ECMA-262 把原生对象(native object)定义为: "独立于宿主环境的 ECMAScript 实现提供的对象"

所以每一种宿主环境都可以使用原生对象

JavaScript中的原生对象有
 
Object、Function、Array、String、Boolean、Math、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError和Global
内置对象 ( built-in object )

ECMA-262 把内置对象(built-in object)定义为: "由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现"

前半部分和原生对象很像,实际上内置对象也都是原生对象,区别在于后半句 "在 ECMAScript程序开始执行时出现"

这意味着开发者无需使用 new + 构造函数 创建,而是JavaScript引擎初始化的时候就被创建,可以直接使用

  目前定义的内置对象只有两个
  
  Global 和 Math
  
  内置对象是原生对象的一种,主要区别在于是否需要实例化
  
  在 ECMAScript 中,不存在独立的函数,所有函数都必须是某个对象的方法
  
  在使用原生对象时,我们一般都需要var obj = new Object() 或字面量 var obj = {} 明确实例化生成一个实例再去调用 Object 的某个方法 obj.toSting()
  
  但如果是内置对象 Global 和 Math,我们只需Math.floor(2.4) 直接调用,不需要再进行实例化
宿主对象 ( host object )

什么是宿主,ECMA 仅是一套规范,也就是指定的一套编程规则

规则毕竟是规则,如果要发挥作用,必须要有平台或者说环境,这就是ECMA的宿主

ECMAScript 中的 "宿主" 当然就是我们网页的运行环境,即 "操作系统" 和 "浏览器"

所有非原生对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象

   所有的 BOM 和 DOM 对象都是宿主对象,它们都属于window对象的子对象
   
   不同的宿主环境所展示的内容不同
   
   它不是 ECMAScript 官方提供的,而是浏览器这个宿主为了方便开发者而加上去的
   
   实际上所有非原生对象都是宿主对象

深浅拷贝

变量的存储方式 栈(stack)和 堆(heap)
 栈
 
    自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
   
 堆
 
    动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值  
    
    
在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。

基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值  

引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量
深拷贝及实现方法
 基本类型不存在浅拷贝还是深拷贝的问题,主要是针对于引用类型
 
 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据
 
 也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的
 
 深拷贝的实现方式
 
    1、JSON.parse(JSON.stringify())
    
      利用JSON.stringify将对象转成JSON字符串
      
      再用JSON.parse把字符串解析成对象
      
      一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝
      
      缺点
      
        不能处理 函数 和 正则 undefined symbol
         
        JSON.stringify 和 JSON.parse 处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了
        
     2、递归实现