Skip to content

1、对象

对象是一组属性的无序集合,通常使用对象字面量来创建对象:

javascript
let person = {
  name: "Nicholas",
  age: 29,
  job: "Software Engineer",
  sayName() {
    console.log(this.name);
  }
}

对象属性的类型

这里自己的理解数据属性和访问器属性的设计,为我们提供了使用js对象的灵活性,我们可以有两种方式定义对象的属性,当我们直接使用对象字面量的方式创建对象,对象的属性是数据属性,也可以定义访问器属性来增强js对象。

属性分为两种:数据属性和访问器属性。

数据属性:数据属性包含一个保存数据值的位置。数据属性有以下几个内部特性:

  • <font style="color:rgb(0,0,0);">[[Configurable]]</font>
  • <font style="color:rgb(0,0,0);">[[Enumerable]]</font>
  • <font style="color:rgb(0,0,0);">[[Writable]]</font>
  • <font style="color:rgb(0,0,0);">[[Value]]</font>

访问器属性

访问器属性不包含数据值,而是包含一个获取(get)函数和设置(set)函数。访问器属性有以下几个内部特性

  • <font style="color:rgb(0,0,0);">[[Configurable]]</font>
  • <font style="color:rgb(0,0,0);">[[Enumerable]]</font>
  • <font style="color:rgb(0,0,0);">[[Get]]</font>
  • <font style="color:rgb(0,0,0);">[[Set]]</font>

访问器属性通常用于这种情形:当我们有一些属性不希望直接被外部访问,或设置对象的一个属性值会导致一些其他的变化。

javascript
// 定义一个对象,包含伪私有成员year_和公共成员edition
let book = {
  year_: 2017,
  edition: 1
};
Object.defineProperty(book, "year", {
  get() {
    return this.year_;
  },
  set(newValue) {
    if (newValue > 2017) {
      this.year_ = newValue;
      this.edition += newValue - 2017;
    }
  }
});
book.year = 2018;
console.log(book.edition); // 2

不管是数据属性还是访问器属性,我们都需要使用Object.defineProperty方法。

下边这张图,当我们给name属性定义访问器属性后,person对象的name属性就变为了一个访问器属性,age还是一个数据属性。

****

2、创建对象

ES6之前js是没有class关键字的(ES6也只是原型的语法糖),当我们创建某种类型的对象时,通常是创建自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。(其实是利用js 函数的特性的一种取巧的办法)

构造函数本身也是函数,js中没有特定定义构造函数的语法,任何函数使用new 操作符就是构造函数。

javascript
function Person() {
  this.name = "Jake";
  this.sayName = function() {
    console.log(this.name);
  };
}
// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"
// 作为函数调用
Person("Greg", 27, "Doctor"); // 添加到window对象
window.sayName(); // "Greg"
// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"

3、原型

原型是js实现类、继承等最重要的因素。

3.1 基础

prototype

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型

原型对象默认会有constructor属性,指向与之关联的构造函数。

下边这张图说明了<font style="color:rgb(0,0,0);">Person</font><font style="color:rgb(0,0,0);">Person</font><font style="color:rgb(0,0,0);">Person</font>

  • <font style="color:rgb(0,0,0);">Person</font><font style="color:rgb(0,0,0);">Person.prototype</font>
  • <font style="color:rgb(0,0,0);">Person.prototype</font><font style="color:rgb(0,0,0);">Person.prototype.contructor</font><font style="color:rgb(0,0,0);">Person</font>
  • <font style="color:rgb(0,0,0);">Person</font><font style="color:rgb(0,0,0);">Person.prototype</font>**<font style="color:rgb(0,0,0);">Person</font>**

proto

既然有了prototype,那__proto__又是什么呢,为什么会出现?

__proto__属性之前不是js标准,但由于各大浏览器厂商都已经实现了,才被加入了ES6。

在JS中,任何对象都有__proto__属性,__proto__可被称为隐式原型(区别于prototype),指向构造该对象的构造函数的原型

也就是实例的__proto__属性指向实例构造函数的原型。

我们在浏览器调试时,经常会看到层层嵌套的__proto__属性,这是因为比如Person的实例person1,person1__proto__属性,

person1.__proto__也是一个对象,也有__proto__对象,这样层层嵌套,直到__proto__属性是一个object类型

我们通过下边这张图来看一下:

  • Person函数有prototype原型属性,同时Person函数也是一个funtion类型的对象,有__proto__隐式原型属性。
  • Person是一个函数,也是一个**function**类型的对象,__proto__隐式原型属性指向的function 的构造函数。
  • person1实例是一个对象,只有__proto__隐式原型属性。指向Person prototype

画板

prototype 与 __proto__的一些总结

  • 对象具有__proto__prototype,函数只有prototype
  • 对象的__proto__属性指向-->构造该对象的构造函数的原型

3.2 继承

原型可以解决对象之间共享属性和方法的问题,也就是继承,但直接使用原型实现继承的话,会带来一个问题:对于方法的继承来说是可以的,但对于属性为引用值来说,多个实例之间共享同一个引用值属性,一个实例修改引用值属性会造成其他实例也会受到影响。

因为上述的一些问题,开发者们想出了很多办法,利用原型的特性实现继承:

  • 开发者们想出来通过****

4、类

es6引入了class关键字,但本质上只是语法糖,仍是对原型的封装,

javascript
class Person {
  constructor(name) {
    this.name = name
  },
  getName(){
    return this.name
  }
}

当我们

class extend