> 文档中心 > JS之对象精讲(一)

JS之对象精讲(一)

文章目录

    • 对象属性和访问方式
      • 对象的属性
        • 数据属性
        • 访问器属性
      • 属性的访问方式
    • 创建对象
      • 1.基于Object()构造函数
      • 2.基于对象字面量
      • 3.基于工厂方法模式
      • 4.基于构造函数模式
      • 5.基于原型对象的模式
      • 6.构造函数和原型混合模式
      • 7.基于动态原型模式
    • 对象克隆
      • 浅克隆
      • 深克隆
        • JSON序列化和反序列化
    • 原型对象
      • 原型对象、构造函数、实例之间的关系
        • 实例属性的读取顺序
        • 重写原型对象
      • 原型链
        • 原型链的特点
        • 属性区分
        • _proto_属性

JS虽然是一门弱类型语言,但它同样是一门面向对象的语言。

对象的属性和访问方式

对象的属性

对象就是一组键值对的集合【属性名称:属性的值】
对象的属性可以分为数据属性和访问器属性。

数据属性

[[Configurable]]:表示属性能否删除而重新定义,默认值为true
[[Enumerable]]:表示属性是否可以枚举,可枚举的属性可以通过for…in循环返回,默认是true
[[Writable]]:表示属性值能否被修改,默认值为true
[[Value]]:表示属性的真实值,属性的读取和写入均通过此属性完成,默认值为undefined
定义一个包含name属性的对象person

  var person={ name:'nick'    };

如果要修改数据属性默认的特性,则必须使用Object.defineProperty()函数

 Object.defineProperty(person,'name',{ configurable:true, enumerable:false, writable:false, value:'nick'    })

第一个参数表示目标对象,第二个参数表示要修改的属性,第三个参数是一个描述符对象,描述符对象的属性必须为configurable、enumerable、writable、value。

访问器属性

访问器属性也包含四个特性,分别是
[[Configurable]]
[[Enumerable]]
[[Get]]:在读取属性时调用的函数(一般称为getter()函数),负责返回有效的值,默认值为undefined
[[Set]]:在写入属性值时调用的函数(一般称为setter()函数),负责处理数据,默认值为undefined
getter()函数和setter()函数可以实现对象的私有属性。

   var person={ _age:10    };    Object.defineProperty(person,"age",{ get:function(){     return this._age; }, set:function(newValue){     if(newValue>10){  this._age=newValue;  console.log('设置成功');     } }    });    console.log(person.age); //10    person.age=19;    console.log(person.age); //19

一般以下划线开头的属性就是私有属性,通过Object.defineProperty()函数为person对象定义为一个age属性,用于对_age属性的读取和写入。
读取age属性时,直接返回对象的_age属性。

属性的访问方式

对象属性有两种访问方式:点操作符中括号操作符
对象名称.对象属性
对象名称[对象属性]
不同之处:
1.点操作符时静态的,而且无法修改,中括号操作符是动态的,可以传递字符串或者变量,在运行时可以修改。

    var obj={};    obj.name='张三';    var myName='name';    console.log(obj.myName);  //undefined    console.log(obj[myName]); //张三

2.点操作符不能以数字作为属性名,中括号操作符是可以的。

    var obj={};    // obj.1=1;    obj[2]=2;    console.log(obj[2]);

3.属性名中包含会导致语法错误的字符,或者是关键字、保留字,可以使用方括号操作符,而不能使用点操作符。

    var person={};    person['first name']='nick';    console.log(person['first name']);

其实访问对象的属性用点操作符已经可以满足大部分场景了。

创建对象

1.基于Object()构造函数

通过Object对象的构造函数生成一个实例,然后给它增加需要的各种属性

    var person=new Object();    person.name='nick';    person.age=11;    person.getName=function(){ return this.name;    }    person.address={ name:'北京市', code:'10000'    }

2.基于对象字面量

基于对象字面量,一系列键值对组合,每个属性之间通过逗号分隔。

    var person={ name:'Bob', age:18, getName:function(){     return this.name; }, address:{     name:'北京',     code:'10000' }    }

3.基于工厂方法模式

抽象出创建对象和属性赋值的过程,只对外暴露出需要设置的属性值。但是创建的实例都是Object类型。

//对外暴露接受的name,agefunction createPerson(name,age){    //通过objetc函数生成一个对象,并添加各种属性    var o=new Object();    o.name=name;    o.age=age;    return o;}var person=createPerson('zhang',11);

4.基于构造函数模式

通过this为对象添加属性,new操作符创建实例,但是相同实例的函数是不一样的,都要占据内存,其实没必要。

function Person(name,age){    this.name=name;    this.age=age;    this.getName=function(){ return this.name;    }}var person1=new Person('zhang',12);var person2=new Person('zhang',12);console.log(person1.getName===person2.getName); //false

5.基于原型对象的模式

所有函数和属性都封装在对象的prototype属性上

function Person(){}Person.prototype.name='zhang';Person.prototype.age=18;Person.prototype.sayName=function(){    return this.name;}var person1=new Person();var person2=new Person();console.log(person1.getName===person2.getName); //true

解决了方法4的问题,但是所有属性都被实例共享,一个实例的属性被改变,其他也会受影响。

6.构造函数和原型混合模式

这是目前最常见的方法。构造函数定义实例属性,原型对象定义实例共享的函数,通过构造函数传递参数,每个实例都可以拥有自己的属性值,还能共享函数的引用,最大程度节省额内存空间

   //构造函数定义实例的属性function Person(name,age){    this.name=name;    this.age=age;}//原型添加实例共享的函数Person.prototype.sayName=function(){    return this.name;}

7.基于动态原型模式

将原型对象放在构造函数内部,通过变量进行控制,只在第一次生成实例时进行原型的设置,相当于懒汉模式。

function Person(name,age){    this.name=name;    this.age=age;//表明还没有为Person的原型对象添加函数if(typeof Person._initialized==="undefined"){ Person.prototype.sayName=function(){return this.name;     };    Person._initialized=true;    }}

对象克隆

克隆是指通过一定的程序将某个变量的值复制到另一个变量的过程。根据复制后的变量与原始变量值的影响情况,又分为浅克隆和深克隆。

浅克隆

只克隆对象最外层的属性,如果对象存在更深层的属性,则不处理。这就会导致克隆对象和原始对象的深层属性仍然指向同一块区域,下面介绍两种方法。
1.简单的引用复制

    function shallowClone(origin){ var result={}; //遍历最外层属性 for(var key in origin){     //判断是否是对象自身的属性     if(origin.hasOwnProperty(key)){  result[key]=origin[key];     } } return result;    }    var origin={a:1,b:[2,3,4],c:{d:'name'}};    var result=shallowClone(origin);    console.log(result);

克隆后对象的值与原始对象的值是一样的。

2.ES6的Object.assign()函数

    var origin={a:1,b:[2,3,4],c:{d:'name'}};    var result2=Object.assign({},origin);    console.log(result2);

但是浅克隆会存在问题:原始对象如果是引用数据类型的值,对克隆对象的值修改会影响原始对象的值。

    //修改克隆属性的内部属性    result.c.d='address';    //发现原始对象也被修改    console.log(origin);    console.log(result);

深克隆

JSON序列化和反序列化

如果对象的全部属性可以序列化,先序列化为字符串,再反序列化为对象。

    var origin={a:1,b:[2,3,4],c:{d:'name'}};    var result=JSON.parse(JSON.stringify(origin));
  • 无法对函数进行克隆
  • 对象的constructor会被抛弃,所有构造函数指向Object,原型链关系断裂。
  • 对象中存在循环引用会报错。
    var origin={a:1};    //添加属性指向自身    origin.b=origin;    var result=JSON.parse(JSON.stringify(origin));

JS之对象精讲(一)

原型对象

单纯通过构造函数创建实例会导致函数在不同的实例中重复创建,所以需要用原型对象来解决。
每个函数在创建时都会被赋予一个prototype属性,它指向函数的原型对象,这个对象可以包含所有实例共享的函数和属性。所以我们可以将实例共享的属性和函数抽离出来放在prototype属性中。

    function Person(name,age){ this.name=name; this.age=age;     }    Person.prototype.sayName=function(){ console.log(this.name);    }    var person1=new Person();    var person2=new Person();    console.log(person1.sayName===person2.sayName); //true

不同实例中的sayName属性是相等的,函数在不同实例中重复创建的问题就解决啦。

原型对象、构造函数、实例之间的关系

function Person(){}//为原型对象添加三个属性Person.prototype.name='zhang';Person.prototype.age=18;Person.prototype.sayName=function(){    console.log(this.name);}var person1=new Person();var person2=new Person();

以上代码一个构造函数Perosn,有个prototype属性,指向Person的原型对象。在原型对象中会有constructor属性和3个原型对象上的属性,constructor属性指向构造函数本身。
通过new创建实例都会有一个[[prototype]],指向Person的原型对象。
JS之对象精讲(一)

实例属性的读取顺序

通过对象的实例读取某个属性,先在实例本身找,找到直接返回,没找到继续沿着原型对象找,找到返回该值。
比如以上案例找person1.name属性,本实例其实没有该属性,会继续沿着原型对象找,会找到name属性值zhang。

重写原型对象

每次为原型对象加属性或函数时,都要写prototype,很麻烦。

function Person(){}Person.prototype={    constructor:Person, //非常重要    name:'zhang',    age:'18',    sayName:function(){ console.log(this.name);    }}

将一个对象字面量赋给prototype属性就是重写了原型对象,所以需要在对象字面量中加一个constructor属性,指向构造函数本身,否则原型的constructor属性会指向object类型的构造函数,而非原构造函数。

function Person(){}Person.prototype={  //没有显示指明constructor    name:'zhang',    age:'18',    sayName:function(){ console.log(this.name);    }}console.log(Person.prototype.constructor===Object);//trueconsole.log(Person.prototype.constructor===Person);//false

JS之对象精讲(一)
还有一个注意点,不要在重写原型对象之前生成对象实例,新的原型对象的属性是访问不到的,如下代码,会报错。

function Person(){}var person1=new Person();Person.prototype={    name:'zhang',    age:'18',    sayName:function(){ console.log(this.name);    }}person1.sayName();

JS之对象精讲(一)
因为person1实例指向的是最初的原型,手动重写原型对象后,已经脱离与最初原型的关系,这点要注意哦。

原型链

对象的每个实例都有_proto_属性,指向构造函数的原型对象,而原型对象又存在_proto_属性指向上一级构造函数的原型对象,到某个原型对象为null结束,这构成的链路就叫原型链,顶端是Object.prototype。

原型链的特点

属性查找会一直沿着原型链向上查,找到便返回,否则返回undefined。
原型链继承的函数也可以调用。
查找的链路越长,性能越低。

属性区分

区分属性是实例本身还是从原型链继承。
hasOwnPropertype()函数可以判断属性是否为自身拥有,前面用过,不再赘述。

_proto_属性

var str=new String('zhang');console.log(str);

String()
str本身只有length属性,在调用str.substring(2,4)却不会报错,这是因为_proto_属性【就是图中的[[prototype]]]】以沿着原型链拿到String.prototype中的函数。

央视天气网