js对象类型

对象简介

  1. 对象的概念:一组"键值对"(key-value)的集合,无序的数据集合.(键值对是指"键名:键值"这种一对对的形式,其中键名(又称属性,成员)要遵守一定的命名和书写规则;键值可以是js的任何一种数据类型,这包括原始值primitive如number,string,boolean等,也包括一般对象object,函数Function,数组Array)
    [说明:键名/键值,属性名/属性值,成员/成员值都是指的同一个东西].

    • 键名/键值:形式结构上的描述
    • 属性名/属性值:css中的概念
    • 成员/成员值:java中的概念
  2. 键名/属性名的规则:

    • 对象的属性名都是字符串(ES6引入Symbol值也可作为键名)
    • 不是字符串的,自动转为字符串
    • 如果属性名不符合标识符条件(例如:包含空格),必须加上引号,否则会报错
      注:js标识符命名规则
      • js中标识符可以由 字母、数字、下划线、$ 组成,但不能是保留字;
      • js中标识符只能由 字母、下划线、$ 开始,而不能由数字开始;
      • js中标识符区分大小写
  3. 对象的属性(property):(详细内容,参照后面的章节)

    • 各个属性(键值对)之间用逗号","隔开,最后一项的逗号可加也可不加
    • 属性分类:数据属性(data property),访问器属性(accessor property),内部属性(internal property)
    • 各个属性又有各自的特性(attribute)与属性描述符对象(property descriptor)
  4. 函数的参数的传递方式:原始类型按值传递,复合类型按引用传递(对象的引用:该对象所在的内存地址,即"指向对象的指针")

  5. 属性的操作:

    • 属性的读取与赋值:

      • 点号".":obj.属性名(注意:数字键名不能用此种方法,点号会被当成小数点)
      • 方括号"[]":obj[‘属性名’],方括号内的两个单引号不能省略.并且方括号之内还可以进行字符串处理.例:obj[‘on’+‘click’]
    • 属性(名)查看:Object.keys(obj)方法(不能查看属性值,只能查看含有哪些属性)

    • 删除对象本身的属性:delete命令

      • 格式:delete obj.属性名;
      • 删除成功返回true;该属性不得删除,返回false.(见后续章节)
      • delete命令不能删除继承属性/原型属性
    • 验证属性存在性:in 运算符

      • 格式:‘字符串’ in obj;
      • 检查对象的键名而不是键值
      • 不区分本身属性与继承属性
    • 判断属性是否为本身属性:obj.hasOwnProperty(‘字符串’);

    • 遍历可enumerable的属性:for…in循环
      格式:for (var i in obj) {…}

    • 操作同一对象的多个属性:with语句
      with(obj) {a=1;b=2;}等同于obj.a=1;obj.b=2;
      [注意:with语句操作的必须是对象已经拥有的属性.否则就会创建一个仅在with语句的作用域内有效的新属性.with语句结束后,该属性又变成undefined.正因为如此,我们不推荐使用with语句]


对象属性详解

  1. 属性分类:

    • 数据属性(data property):一般的键值对
    • 访问器属性(accessor property):不保存值,但可对访问和赋值操作的实际过程进行自定义
    • 内部属性(internal property):一般无法直接访问,需要借助相应的函数
  2. 数据属性(data property):

    • 就是我们说的一般的属性,它保存一个值,要么是数据本身(原始类型),要么是数据的引用(对象).
    • 数据属性的访问与赋值操作的实际过程是默认的,无法修改
    • 数据属性的特性(property attributes):
      • 属性(property)与特性(attribute)是两个不同的概念

      • 属性的特性(attribute)是对属性本身能够进行的行为的约束

      • 数据属性的特性有如下几种:

        js对象类型

      • 属性特性不能直接进行修改,必须使用专门的方法Object.defineProperty(obj,‘属性名’,属性描述符对象)

        注:属性描述符,或者说属性描述符对象是指将要修改的特性与修改值,以键值对的形式书写,并用对象封装.例如{configurable:false,writable:false;}
      • 与属性描述符相关的函数:

        • Object.defineProperty(obj, ‘属性名’, 属性描述符)
        • Object.defineProperties(obj, 属性-描述符键值对对象)
          这里的 属性-描述符键值对对象 是指如下形式的封装对象:
          {属性名1:描述符对象1 , 属性名2:描述符对象2,…}
        • Object.create(proto, 属性-描述符键值对对象)
        • Object.getOwnPropertyDescriptor(obj, 属性名):返回特定属性的属性描述符对象;如果不存在该属性,返回undefined.
  3. 访问器属性(accessor property):

    • 访问器属性不包含数据值,他们有一对儿getter和setter函数(这两个函数不是必须的,保存在其属性特性Set和Get中)。允许用户在赋值或取值都经过预先设定的函数,从而实现内部属性的那一种特殊效果.

      读取访问器属性时,会调用getter函数,这个函数负责返回有效的值。 在写入访问器属性时,会调用setter函数,这个函数负责决定如何处理数据。
    • 属性特性:(前两个与数据属性相同,后两个不同)
      js对象类型
      小结:因为不保存值,所以没有Value和Writable特性,Set和Get取而代之.

    • 访问器属性的修改:不能直接修改,需要用到特定函数Object.defineProperty(obj,‘属性名’,属性描述符对象)或者Object.defineProperties(obj, 属性-描述符键值对对象)

  4. 内部属性(internal property):

    • 一些属性仅仅在规范之中用到,它们被称为"内部属性"是因为它们不能通过js直接访问,但也确确实实影响着代码.内部属性的书写都是包含在双方括号([[]])之中的.
    • 所有对象共有的内部属性:
      js对象类型
      js对象类型
      更多相关资料,请参考:ECMA5.1

重要的内部属性详解:

  • [[prototype]]:存放当前对象的原型对象的引用
    1.函数与函数的原型对象(prototype object):

    • 在JavaScript中,创建一个函数A(就是声明一个函数), 浏览器就会在内存中创建一个对象B,而且该函数默认会有一属性 prototype 指向这个对象(即:prototype属性的值)
    • 这个对象B就是函数A的原型对象,简称函数的原型。这个原型对象B也默认会有一个属性 constructor 指向了这个函数A (即:constructor属性的值是函数A)
    • 凡是以函数A为构造函数而创建的对象C,D,E等等,也都有一个内部的[[prototype]]属性,也指向这个对象B.

    2.要点一:构造函数的prototype的属性与其实例对象的[[prototype]]属性的区别.
    前面的内容提到"内部属性"是不能直接访问到的,需要借助某些函数.而构造函数的prototype属性不是内部属性,而是普通属性;但是其实例对象的[[prototype]]确是内部属性,直接obj.prototype总是返回undefined.
    代码:
    function Test() {}
    var t1 = new Test();
    console.log(Test.prototype);
    console.log(t1.prototype);
    运行结果:
    js对象类型
    3.要点二:任何对象都有构造函数,都有原型对象
    对象可以分为字面量对象,new出来的对象和函数对象.显然,new出来的对象都是有构造函数和原型对象的.

    • 字面量对象
      代码:

      var square = {length: 10};
      var pro=Object.getPrototypeOf(square);
      console.log(pro);
      console.log(pro==Object.prototype);

      运行结果:
      js对象类型
      因此,字面量对象的原型是Object.prototype,其构造函数为Object().

    • 函数对象:
      代码:

      function Test(){}
      var proF=Object.getPrototypeOf(Test);
      console.log(proF);
      console.log(proF==Function.prototype);

      运行结果:
      js对象类型
      因此,函数对象的原型是Function.prototype,构造函数为Function().
      至此,我们可以得出开头的结论.

    4.要点三:任何函数都可以作为构造函数,但不一定都能对其实例对象初始化.
    例子1:
    代码:

    function Test2(x,y){
    length=x;
    name=y;
    }
    var T2 = new Test2(1,‘peter’);
    console.log(Object.getPrototypeOf(T2)==Test2.prototype);
    console.log(T2.length);

    运行结果:
    js对象类型

    例子2:
    代码:

    function Test3(x,y){
    this.length=x;
    this.name=y;
    }
    var T3 = new Test3(1,‘peter’);
    console.log(Object.getPrototypeOf(T3)==Test3.prototype);
    console.log(T3.length);

    运行结果:
    js对象类型
    5.我们约定,凡是构造函数,其函数名首字母必须大写.

  • 进一步探讨内部属性
    由上面的要点一,我们可以看出,prototype属性像是构造函数的私有属性(类似java的私有成员).构造函数自身可以直接访问,但其实例对象不能直接访问,需要借助在构造函数里定义的"特权"函数才能间接访问.
    我们再把我们的视野移到整个内部属性集合,像[[Class]],[[Extensible]]等.

    内部属性 访问方法
    [[Prototype]] Object.getPrototypeOf(obj) , obj.__proto__(访问器属性,类似于方法)
    [[class]] Object.prototype.toString.call(obj)
    [[Extensible]] Object.isExtensible , Object.preventExtensions

那么,我们如何自定义一个函数的私有属性呢?
在js中,我们通常通过闭包来实现私有属性.
代码:

function Student(num,str){
var grade = num;
var name = str;
this.getGrade = function (){
return grade;
}
this.getName = function(){
return name;
}
}
var s = new Student(98,‘peter’);
console.log(s);
console.log(s.grade);//undefined,不能直接访问私有属性
s.name = ‘selena’;//对象新建了一个自身属性name,不是私有属性[[name]]
console.log(s.name);//selena,访问自身属性
console.log(s.getName());//peter,通过特权方法getName()访问私有属性

运行结果:
js对象类型
这种实现的优缺点总结:
1.私有属性和特权函数只能在构造函数中创建
2.闭包的使用增加了资源占用,可能会导致一些问题


对象的保护

  1. 阻止对象的扩展(Preventing Extension)
    效果:禁止向对象添加新属性,仍可以删除已有属性
    实现途径:Object.preventExtensions(obj)
    检查是否已阻止:Object.isExtensible(obj)

  2. 封闭对象(Sealing)
    效果:禁止添加新属性,全部已有属性"unconfigurable"(不可更改属性的特性值,不可删除自身属性),但基于历史原因,仍可以将writable改为read-only.
    实现途径:Object.seal(obj)
    检查对象是否已封闭:Object.isSealed(obj)

  3. 冻结对象(Freezing)
    效果:使所有属性变为只读,同时不可扩展(添加新属性),不可修改属性特性,不可删除属性
    实现途径:Object.freeze(obj)
    检查对象是否已冻结:Object.isFrozen(obj)


严格模式

  1. 严格模式是ECMAScript的一部分.可以灵活切换,可以是整个文件,也可以仅在一个函数内

  2. 启用方式:
    整个文件----将’use strict’放在在文件开始处
    某个函数----将’use strict’放在在函数开始处

  3. 对安全性的需求是严格模式使用的主要动机

  4. 严格模式特征:

    • 安全性:极严格地限制eval()的使用,with语句禁止使用
    • 全局变量使用之前必须被明确地声明(不会再自动生成),这有助于预防输入上的错误
    • 未使用new调用构造函数时:
      在严格模式出现之前,this是绑定到全局变量的,这导致会有属性被添加到这个对象.
      严格模式中,如果构造函数不是经new调用的,那么this是绑定到undefined的,而且通常会有一个exception被抛出.
  5. 烦人的报错:尝试修改一个只读的属性的值或者删除一个不可配置的属性,会丢出exception.

  6. 不再支持八进制数:严格模式中,以0开头的数不会再被理解成八进制,0100就是十进制的100,而不是八进制的64.

  7. 参数对象:arguments.callee和arguments.caller属性被排除掉(基于安全原因:这样使得他们不被外部代码发现)

  8. 函数参数:禁止出现与形参同名的参数或变量名.