Netherspite

JavaScript中的对象

对象的特征是具有唯一标识性(内存地址),并且具有状态行为(属性)。JavaScript中的对象是具有高度动态性的属性集合,它有在运行时为对象添改状态和行为的能力。

JavaScript的属性并不是简单的名称和值,而是一组特征(attribute)描述的属性(property)。属性分为数据属性和访问器属性。

  • 数据属性具有数据描述符:
    • value:属性值
    • writable:属性是否能被赋值
    • configurable:属性的特征能否被修改和删除
    • enumerable:是否能通过for in枚举
  • 访问器属性具有存取描述符:
    • setter:函数或undefined,设置属性值时被调用
    • getter:函数或undefined,取属性值时被调用
    • enumerable与configurable:同数据描述符

描述符可以用Object.getOwnPropertyDescriptor(obj, prop)来获取,用Object.defineProperty(obj, prop, descriptor)来修改。同时,在创建对象时,也可以用getset关键字来创建访问器属性。

顶层对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但Node和Web Worker没有window
  • 浏览器和Web Worker里面,self也指向顶层对象,但是Node没有self
  • Node里面,顶层对象是global,但其他环境都不支持。

类与原型

原型

每一个函数都具有prototype属性,它被默认成一个对象,即原型对象。

  • Object.create(),根据指定的原型创建新对象,原型可以是null
  • Object.getPrototypeOf(),获得一个对象的原型
  • Object.setPrototypeOf(),设置一个对象的原型

在早期版本的JS中,类的定义是一个私有属性[[class]],语言标准为内置类指定了[[class]]属性,唯一可以访问该属性的方法就是Object.prototype.toString。ES5开始,[[class]]属性被Symbol.toStringTag代替,使用该属性还可以自定义Object.prototype.toString的行为。注意,类的作用域内部默认是以严格模式来执行代码的。

类的__proto__prototype之间的关系图解:

new运算符

  • 以构造器的prototype属性为原型,创建新对象

  • 它使this指向新的对象,这样可以访问构造函数中的属性方法

  • 如果函数没有返回对象类型 Object(包含 Functoin,Array,Date,RegExg,Error),那么 new表达式中的函数调用将返回该对象引用

    function createObj() {
    let obj = {};
    let Constructor = Array.prototype.shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    let ret = Constructor.apply(obj, arguments);
    if (ret !== null && (typeof ret === 'object' || typeof ret === 'function')) {
    return ret;
    }
    return obj;
    }

原型链与继承

  • 原型链:假如我们让原型对象等于另一个类型的实例,则此时的原型对象将包含一个指向另一个原型的实例,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就是原型链。

  • 原型链继承:通过将一个类型的实例赋给另一个构造函数的原型实现。

    function Person(name){
    this.name = name;
    }
    function Child(){}
    Child.prototype = new Person();

对象实例共享所有继承的属性和方法,解决方法是借用构造函数

function Child(name){
Person.call(this, name);
}
  • 组合继承:原型链继承方法+借用构造函数继承属性,解决了函数复用问题

    function Child(name){
    Person.call(this, name);
    }
    Child.prototype = new Person(name);
    Child.prototype.constructor = Child;
  • 原型式继承:执行对给定对象的浅复制,复制得到的副本进一步改造

  • 寄生式继承:主要考虑对象而不是自定义类型和构造函数的情况下使用。通过调用函数创建一个对象然后以某种方式增强这个对象最后返回这个对象

  • 寄生组合式继承:为解决组合继承会调用两次超类型的构造函数的问题
    先用借用构造函数,然后用inheritPrototype函数,最后自定义子类型的函数

    function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
    }
    function inheritPrototype(subType, suberType) {
    var prototype = object(suberType.prototype); // 创建对象
    prototype.constructor = subType; // 增强对象
    subType.prototype = prototype; // 指定对象
    }
    function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
    alert(this.name);
    }
    function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
    }
    inheritPrototype(SubType, SuperType);
    SubType.prototype.sayAge = function() {
    alert(this.age);
    }

原生对象

JavaScript标准中提供了30多个构造器,分成以下几类:

基本类型 基础功能和数据结构 错误类型 二进制操作 带类型的数组
Boolean Array Error ArrayBuffer Float32/64Array
String Date RangeError SharedArrayBuffer Int8/16/32Array
Number RegExp EvalError DataView UInt8/16/32Array
Symbol Promise ReferenceError UInt8ClampedArray
Object Proxy SyntaxError
Map TypeError
WeakMap URIError
Set
WeakSet
Function

通过这些构造器我们可以用new运算创建新的对象,几乎所有这些构造器的能力都无法用纯JavaScript代码实现,也无法用class/extends语法继承(除Array)。原因是这些构造器多睡会或胡是哦晕了私有字段,如:

  • Error: [[ErrorData]]
  • Boolean: [[BooleanData]]
  • Number: [[NumberData]]
  • Date: [[DateValue]]
  • RegExp: [[RegExpMatcher]]
  • Symbol: [[SymbolData]]
  • Map: [[MapData]]

内置对象作为函数被调用和作为构造器被调用的行为不总是一致的,比如Date作为构造器调用产生新的对象,作为函数调用则返回字符串(typeof结果不同),而Image对象在浏览器中不允许被作为函数调用。


参考文章

  1. 重学前端

 评论