JavaScript对象和原型链

148 阅读8分钟

使用对象

一个对象就是一系列属性的集合,一个属性包含一个名和一个值。一个属性的值可以是函数,这种情况下属性也被称为方法

对象和属性

一个对象的属性可以被解释成一个附加到对象上的变量。可以通过点符号来访问一个对象的属性。

objectName.propertyName
var myCar = new Object();
myCar.make = "Ford";

JavaScript 对象的属性也可以通过方括号访问或者设置,对象有时也被叫作关联数组,因为每个属性都有一个用于访问它的字符串值

myCar["make"] = "Ford";

person['name']['last'] = 'Cratchit'

在 for...in 语句中使用方括号标记以枚举一个对象的所有属性。

for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
  }

枚举一个对象中的所有属性的方法:

  • for...in 循环 该方法依次访问一个对象及其原型链中所有可枚举的属性。
  • Object.keys(o) 该方法返回对象 o 自身包含(不包括原型中)的所有可枚举属性的名称的数组。
  • Object.getOwnPropertyNames(o) 该方法返回对象 o 自身包含(不包括原型中)的所有属性 (无论是否可枚举) 的名称的数组。

创建新对象

可以通过对象初始化器(Object Initializer)创建对象。或者可以创建一个构造函数并使用该函数和 new 操作符初始化对象。

  • 对象初始化器
var obj = { property_1:   value_1,   // property_# 可以是一个标识符...
            2:            value_2,   // 或一个数字...
          }
  • 构造函数
  1. 通过创建一个构造函数来定义对象的类型。首字母大写是非常普遍而且很恰当的惯用法。
  2. 通过 new 创建对象实例。
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

通过使用 this 将传入函数的值赋给对象的属性。

var mycar = new Car("Eagle", "Talon TSi", 1993);
  • Object.create方法

允许为创建的对象选择一个原型对象,而不用定义构造函数。

// Animal properties and method encapsulation
var Animal = {
  type: "Invertebrates", // 属性默认值
  displayType : function() {  // 用于显示 type 属性的方法
    console.log(this.type);
  }
}

// 创建一种新的动物——animal1
var animal1 = Object.create(Animal);
animal1.displayType(); // Output:Invertebrates

// 创建一种新的动物——Fishes
var fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Output:Fishes

继承

所有的 JavaScript 对象至少继承于一个对象。被继承的对象被称作原型,并且继承的属性可通过构造函数的 prototype 对象找到。

为对象类型定义属性: 可通过prototype为之前定义的对象类型增加属性。

Car.prototype.color = null;
car1.color = 'red';

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

基于原型链的继承

someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 proto。Object.prototype表示Object的原型对象 this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。函数(function)是允许拥有属性的。所有的函数会有一个特别的属性 —— prototype

创建对象

  1. 使用语法结构
var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind 等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

  1. 使用构造器
function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
  1. 使用Object.create 新对象的原型就是调用 create 方法时传入的第一个参数:
var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined,因为 d 没有继承 Object.prototype
  1. 使用class
class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

hasOwnProperty 是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。

console.log(g.hasOwnProperty('nope'));

constructor属性

每个实例对象都从原型中继承了一个 constructor 属性,该属性指向了用于构造此实例对象的构造函数。

var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

修改构造器的 prototype 属性

function Ball(x, y, velX, velY, exists, color, size){
  Shape.call(this, x, y, velX, velY, exists);//构造器 `Ball()` 应该从构造器 `Shape()` 继承 `x`, `y`, `velX`, `velY`,和 `exists` 属性
  this.color = color;
  this.size = size;
}

Ball.prototype = Object.create(Shape.prototype);
Ball.prototype.constructor = Ball;//给构造器 `Ball()` 的`prototype` 和 `constructor` 属性设置适当的值。
function EviCircle(x, y, exists){
  Shape.call(this, x, y, 20, 20, exists);
  this.color = 'white';
  this.size = 10;
}
EviCircle.prototype = Object.create(Shape.prototype);
EviCircle.prototype.constructor = EviCircle;

定义方法

通过this引用对象

定义getters与setters

一个getter是一个获取某个特定属性的值的方法。一个 setter 是一个设定某个属性的值的方法。你可以为预定义的或用户定义的对象定义 getter 和 setter 以支持新增的属性。定义 getter 和 setter 的语法采用对象字面量语法。

var o = {
  a: 7,
  get b() { return this.a + 1; },
  set c(x) { this.a = x / 2; }
};

删除属性

可以用delete操作符删除一个不是继承而来的属性。 如果一个全局变量不是用 var 关键字声明的话,也可以用 delete 删除它

在 JavaScript 中 objects 是一种引用类型。两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回 true.

class Person {

  name;

  constructor(name) {
    this.name = name;
  }

  introduceSelf() {
    console.log(`Hi! I'm ${this.name}`);
  }

}

构造函数使用 constructor 关键字来声明。就像在类声明外的构造函数一样,不需要任何特殊的初始化内容,你可以省略构造函数,默认的构造函数会被自动生成。会:

  • 创建一个新的对象
  • 将 this 绑定到这个新的对象,你可以在构造函数代码中使用 this 来引用它
  • 执行构造函数中的代码
  • 返回这个新的对象
const giles = new Person('Giles');

giles.introduceSelf(); // Hi! I'm Giles

继承

声明子类:使用 extends 关键字来声明这个类继承自另一个类。

class Professor extends Person {

  teaches;

  constructor(name, teaches) {
    super(name);
    this.teaches = teaches;
  }
}

构造函数中需要做的第一件事是使用 super() 调用父类的构造函数,并传递 name 参数。父类的构造函数会设置 name 属性。

封装

类私有域

类属性在默认情况下是公有的,但可以使用增加哈希前缀 # 的方法来定义私有类字段

class ClassWithPrivateField {
  #privateField;
}

class ClassWithPrivateMethod {
  #privateMethod() {
    return 'hello world';
  }
}

class ClassWithPrivateStaticField {
  static #PRIVATE_STATIC_FIELD;
}

从作用域之外引用 # 名称、内部在未声明的情况下引用私有字段、或尝试使用 delete 移除声明的字段都会抛出语法错误。

私有方法

class Example {

  somePublicMethod() {
    this.#somePrivateMethod();
  }

  #somePrivateMethod() {
    console.log('You called me?');
  }

}

JSON

JavaScript 对象表示法(JSON)是用于将结构化数据表示为 JavaScript 对象的标准格式,通常用于在网站上表示和传输数据。JSON 可以作为一个对象或者字符串存在,前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。一个 JSON 对象可以被储存在它自己的文件中,这基本上就是一个文本文件,扩展名为 .json

JSON结构

JSON 对象就是基于 JavaScript 对象,可以把 JavaScript 对象原原本本的写入 JSON 数据——字符串,数字,数组,布尔还有其它的字面值对象

{
  "squadName" : "Super hero squad",
  "homeTown" : "Metro City",
  "formed" : 2016,
  "secretBase" : "Super tower",
  "active" : true,
  "members" : [
    {
      "name" : "Molecule Man",
      "age" : 29,
      "secretIdentity" : "Dan Jukes",
      "powers" : [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    }
}

为了访问对象中的对象,只需简单地链式访问

JSON数组

[  {    "name" : "Molecule Man",    "age" : 29,    "secretIdentity" : "Dan Jukes",    "powers" : [      "Radiation resistance",      "Turning tiny",      "Radiation blast"    ]
  },
  {
    "name" : "Madame Uppercut",
    "age" : 39,
    "secretIdentity" : "Jane Wilson",
    "powers" : [
      "Million tonne punch",
      "Damage resistance",
      "Superhuman reflexes"
    ]
  }
]
  • JSON 是一种纯数据格式,它只包含属性,没有方法。
  • JSON 要求在字符串和属性名称周围使用双引号。单引号无效。
  • 甚至一个错位的逗号或分号就可以导致 JSON 文件出错。

加载JSON

为了载入 JSON 到页面中,使用一个名为XMLHTTPRequest的 API(常称为 XHR)。是一个JavaScript 对象,能够通过代码来向服务器请求资源文件 (如:图片,文本,JSON,甚至 HTML 片段),意味着可以更新小段内容而不用重新加载整个页面

 <body>

      <header>

      </header>

      <section>

      </section>

      <script>
        var header = document.querySelector('header');
        var section = document.querySelector('section');

        var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';//保存一个即将访问的 URL 作为变量

        var request = new XMLHttpRequest();//创建一个 HTTP 请求对象,通过 new 构造函数的形式

        request.open('GET', requestURL);//使用 open() 函数打开一个新的请求

        request.responseType = 'json';//设定 responseType 为 JSON,服务器将知道我们想要返回一个 JSON 对象,然后发送请求
        request.send();

        request.onload = function() {
            var superHeroes = request.response;//保存了相应请求的数据 (访问 response 属性) 于变量 superHeroes ;这个变量现在含有 JSON
            populateHeader(superHeroes);//将会用正确的数据填充<header>
            showHeroes(superHeroes);//创建一个信息卡片,然后把它插入<section>中。
        }
//定位 header
        function populateHeader(jsonObj) {
            var myH1 = document.createElement('h1');
            myH1.textContent = jsonObj['squadName'];//参数为 jsonObj
            header.appendChild(myH1);

            var myPara = document.createElement('p');
            myPara.textContent = 'Hometown: ' + jsonObj['homeTown'] + ' // Formed: ' + jsonObj['formed'];
            header.appendChild(myPara);
        }

        function showHeroes(jsonObj) {
            var heroes = jsonObj['members'];

            for(i = 0; i < heroes.length; i++) {
                var myArticle = document.createElement('article');
                var myH2 = document.createElement('h2');
                var myPara1 = document.createElement('p');
                var myPara2 = document.createElement('p');
                var myPara3 = document.createElement('p');
                var myList = document.createElement('ul');

                myH2.textContent = heroes[i].name;
                myPara1.textContent = 'Secret identity: ' + heroes[i].secretIdentity;
                myPara2.textContent = 'Age: ' + heroes[i].age;
                myPara3.textContent = 'Superpowers:';

                var superPowers = heroes[i].powers;
                for(j = 0; j < superPowers.length; j++) {
                    var listItem = document.createElement('li');
                    listItem.textContent = superPowers[j];
                    myList.appendChild(listItem);
                }

                myArticle.appendChild(myH2);
                myArticle.appendChild(myPara1);
                myArticle.appendChild(myPara2);
                myArticle.appendChild(myPara3);
                myArticle.appendChild(myList);

                section.appendChild(myArticle);
            }
        }
      </script>
  </body>

对象和文本间的转换

接收到一些字符串作为 JSON 数据,然后想要将它转换为对象。当想要发送 JSON 数据作为信息,将需要转换它为字符串,经常需要正确的转换数据

  • parse(): 以文本字符串形式接受 JSON 对象作为参数,并返回相应的对象。
  • stringify(): 接收一个对象作为参数,返回一个对应的 JSON 字符串。
request.open('GET', requestURL);
request.responseType = 'text'; // now we're getting a string!
request.send();

request.onload = function() {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
  showHeroes(superHeroes);
}
var myJSON = { "name" : "Chris", "age" : "38" };
myJSON
var myString = JSON.stringify(myJSON);
myString