创建新对象的方法

一、创建新对象:字面量、构造器、Object.create()、class关键字

对象初始化器 / 字面值 / 字面量 / 语法结构

一、语法:通过使用带有可选属性列表的花括号{…}来创建对象

  1. var obj = {
  2. property_1: value_1, // property_# 可以是一个标识符
  3. 2 value_2, // 数字
  4. ['property' + 3]: value_3, // 可计算的Key名
  5. // ...
  6. 'property n': value_n, // 字符串
  7. }

1、obj 是新对象的名称,每一个 property_i 是一个标识符(可以是一个名称、数字或字符串字面量),并且每个 value_i 是一个其值将被赋予 property_i 的表达式。
2、obj 与赋值是可选的;
3、如果你不需要在其他地方引用对象,你就不需要将它赋给一个变量。(注意在接受一条语句的地方,你可能需要将对象字面量括在括号里,从而避免将字面量与块语句相混淆)

| 【示例】只有当cond表达式的值为true时创建对象并将其赋值给变量x```javascript if (cond) var x = { hi: ‘there’ }

  1. |
  2. | --- |
  3. 4、尾随(trailing)或悬挂(hanging)逗号:列表中的最后一个属性应以逗号结尾(不强制要求有逗号)
  4. | 【示例】```javascript
  5. let user = {
  6. name: "John",
  7. age: 30, // 尾随逗号 / 悬挂逗号。这样便于我们添加、删除和移动属性,因为所有的行都是相似的。
  8. }

| | —- |

二、如果一个对象是通过在顶级脚本的对象初始化器创建的,则JavaScript在每次遇到包含该对象字面量的表达式时都会创建对象。在函数中的初始化器在每次函数调用时也会被创建。
三、使用语法结构创建的对象、生成原型链

| 【示例】```javascript 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

 |
| --- |

四、使用 const 声明的对象是可以被修改的

| 【示例】```javascript
const user = {
  name: "John"
};

user.name = "Pete"; // 似乎会触发一个错误,但实际并没有
                                        // const声明仅固定了user的值,而不是值(该对象)里面的内容

alert(user.name); // Pete

1、仅当我们尝试将user=…作为一个整体进行赋值时,const会抛出错误。
2、有另一种将对象属性变为常量的方式,见属性标志和属性描述符:https://www.yuque.com/tqpuuk/yrrefz/xg43a2 | | —- |

【示例】以下哪一条Javascript代码会产生运行错误?
A. var obj=( );
B. var obj=[ ];
C. var obj={ };
D. var obj=/ /;
答案:A
解析:考察点:创建新对象的方法-字面量
A. 语法错误
B,声明obj为数组对象
C,声明obj为对象
D,声明obj为正则对象。若为var obj = //;即赋值被注释掉,及运行被结束

计算属性

一、当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做计算属性。

| 【示例】```javascript let fruit = prompt(“Which fruit to buy?”, “apple”);

let bag = { [fruit]: 5, // 属性名是从 fruit 变量中得到的 };

alert( bag.apple ); // 5 如果 fruit=”apple”

1、计算属性的含义很简单:[fruit]含义是属性名应该从fruit变量中获取。<br />2、所以,如果一个用户输入"apple",bag将变为{apple: 5}。<br />3、本质上,这跟下面的语法效果相同:```javascript
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};

// 从 fruit 变量中获取值
bag[fruit] = 5;

| | —- |

| 【示例】可以在方括号中使用更复杂的表达式:```javascript let fruit = ‘apple’; let bag = { [fruit + ‘Computers’]: 5 // bag.appleComputers = 5 };

 |
| --- |


<a name="0ff9e1962cd266d295481c760c7e7ac6"></a>
## 使用构造器 / 构造函数
一、在JavaScript中,构造器其实就是一个普通的函数。当使用new操作符来作用这个函数时,就可以被称为构造方法(构造函数)<br />二、构造函数在技术上是常规函数。不过有两个约定:<br />1、它们的命名以大写字母开头。<br />2、它们只能由"new"操作符来执行。<br />三、可以通过两步来创建对象<br />1、通过创建一个构造函数来定义对象的类型。首字母大写是非常普遍而且很恰当的惯用法。<br />2、通过new创建对象实例。<br />四、当一个函数被使用new操作符执行时,它按照以下步骤:<br />1、一个新的空对象被创建

- 将这个空对象的原型,指向构造函数的prototype属性
- 将这个空对象分配给this:将空对象赋值给函数内部的this关键字

2、函数体执行。通常它会修改this,为其添加新的属性。<br />3、返回this的值。

| 【示例】换句话说,new User(name)做的就是类似的事情```javascript
function User(name) {
  // this = {};(隐式创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐式返回)
}

1、所以new User(‘Jack’)的结果是相同的对象```javascript let user = { name: “Jack”, isAdmin: false };

2、如果我们想创建其他用户,我们可以调用new User("Ann"),new User("Alice")等。比每次都使用字面量创建要短得多,而且更易于阅读。 |
| --- |

五、如果没有参数,我们可以省略new后的括号:

| 【示例】这里省略括号不被认为是一种“好风格”,但是规范允许使用该语法。```javascript
let user = new User; // <-- 没有参数
// 等同于
let user = new User();

| | —- |

六、为了定义对象类型,为对象类型创建一个函数以声明类型的名称、属性和方法。

| 【示例】想为汽车创建一个类型,并且将这类对象称为car,并且拥有属性 make, model 和 year,可以创建如下函数```javascript function Car(make, model, year) { this.make = make // 通过this将传入函数的值赋给对象的属性 this.model = model this.year = year }

// 创建一个mycar对象,将指定的值赋给它的属性 var mycar = new Car(‘Eagle’, ‘Talon TSi’, 1993)

 |
| --- |

<a name="P909G"></a>
### 使用构造函数构建对象的方式
一、使用构造函数构建对象的方式:用function来模拟无参的构造函数、用function来模拟有参构造函数来实现、工厂方式、原型方式、混合方式<br />二、构造器的主要目的:实现可重用的对象创建代码。
<a name="hMUJX"></a>
#### function(函数)来模拟class
<a name="hXY8g"></a>
##### 用function来模拟无参的构造函数
| 【示例】```javascript
function Person(){}
var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Class
person.name="Mark";
person.age="25";
person.work=function(){
  alert(person.name+" hello...");
}
person.work();

| | —- |

用function来模拟有参构造函数来实现(用this关键字定义构造的上下文属性)

| 【示例】```javascript function User(name) { this.name = name; this.isAdmin = false; }

let user = new User(“Jack”);

alert(user.name); // Jack alert(user.isAdmin); // false

 |
| --- |

| 【示例】```javascript
// 定义了person对象
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

// 创建了两个person实例
var rand = new Person("Rand McKinnon", 33, "M");
var ken = new Person("Ken Jones", 39, "M");

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

var car1 = new Car("Eagle", "Talon TSi", 1993, rand); // 将rand作为owner的参数值,而不是传入字符串字面量或整数值
var car2 = new Car("Nissan", "300ZX", 1992, ken);

console.log(car2.owner.name) // car2的拥有者的姓名

car1.color = 'black' // 可以为之前定义的对象增加新的属性,这并不影响其他的对象
                                         // 想要为某个类型的所有对象添加属性,必须将属性加入到car对象类型的定义中
console.log(car1)
/*
    {
      color: "black"
    make: "Eagle"
    model: "Talon TSi"
    owner: {
      age: 33
      name: "Rand McKinnon"
      sex: "M"
    }
    year: 1993
  }
*/

| | —- |

object构造函数

| 【示例】```javascript var wcDog =new Object(); wcDog.name=”旺财”; wcDog.age=3; wcDog.work=function(){ alert(“我是”+wcDog.name+”,汪汪汪……”); } wcDog.work();

 |
| --- |

<a name="yqJdx"></a>
#### 工厂模式
| 【示例】```javascript
function createObj(name, age) {
  let obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.sayHi = function() {
    console.log(obj.name);
  }
  return obj;
}

let dog = createObj('汪汪', 20);
console.log(dog.age); // 20
dog.sayHi(); // '汪汪'

| | —- |

原型方式

| 【示例】```javascript function Dog(){

} Dog.prototype.name=”旺财”; Dog.prototype.eat=function(){ alert(this.name+”是个吃货”); } var wangcai =new Dog(); wangcai.eat();

 |
| --- |

<a name="TOH2Z"></a>
#### 混合方式(原型+构造函数)
| 【示例】```javascript
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。

| | —- |

动态原型(混合模式的一种特例)

| 【示例】```javascript function Animal(name, age) { this.name = name; this.age = age; if (typeof Animal.sell === ‘undefined’) { Animal.prototype.sell = function() { alert(‘我是’ + this.name + ‘今年’ + this.age + ‘岁了,汪汪~’); } Animal.sell = true; } } var dog = new Animal(‘狗狗’, age) dog.sell()

 |
| --- |

<a name="233bd7f1219ea2ad84e218e29cbc301b"></a>
### 对象属性索引
一、通过构造函数创建一个对象,如果最初使用名称定义了一个属性,则必须通过名称来访问它;如果最初使用序号来定义一个属性,则必须通过索引来访问它。

| 【示例】```javascript
myCar.color = 'red'
console.log(myCar.color) // 'red'

myCar[5] = '25'
console.log(myCar[5]) // '25'
console.log(myCar)

| | —- |

1、这条规则的例外是从与HTML对应的对象,如forms数组。这些数组的元素,可以通过其序号(依据其在文档中出现的顺序),也可以按照其名称访问它。

| 【示例】文档中的第二个

标签有一个NAME属性且值为’myForm’,访问该form的方法有以下3种```javascript document.forms[1]

document.forms[‘myForm’]

document.myForm

 |
| --- |

二、为对象类型定义属性<br />可以通过prototype属性为之前定义的对象类型增加属性。这为该类型的所有对象,而不仅仅是一个对象增加了属性。

| 【示例】为所有类型为Car的对象增加color属性,然后为对象car1的color属性赋值。```javascript
Car.prototype.color = null
color1.color = 'black'

| | —- |

构造器的 return

一、通常,构造器没有return语句。它们的任务是将所有必要的东西写入this,并自动转换为结果。
二、但是,如果这有一个return语句,那么规则就简单了:

  • 如果return返回的是一个对象,则返回这个对象,而不是this。
  • 如果return返回的是一个原始类型,则忽略。

1、换句话说,带有对象的return返回该对象,在所有其他情况下返回this。

| 【示例】这里return通过返回一个对象覆盖this:```javascript function BigUser() {

this.name = “John”;

return { name: “Godzilla” }; // <— 返回这个对象 }

alert( new BigUser().name ); // Godzilla,得到了那个对象

 |
| --- |

| 【示例】return为空的例子(或者我们可以在它之后放置一个原始类型,没有什么影响):```javascript
function SmallUser() {

  this.name = "John";

  return; // <-- 返回 this
}

alert( new SmallUser().name );  // John

| | —- |

Object.create()

一、ECMAScript5引入了一个新方法:Object.create()。对象也可以使用Object.create()方法创建。
二、它允许你为创建的对象选择一个原型对象,而不用定义构造函数

| 【示例】```javascript var Animal = { type: ‘Invertebrates’, // 属性默认值 displayType: function () { // 用于显示type属性的方法 console.log(this.type) } }

// 创建一种新的动物-animal var animal1 = Object.create(Animal) animal1.displayType() // Invertebrates

// 创建一种新订单动物-Finhes var fish = Object.create(Animal) fish.type = ‘Fishes’ fish.displayType() // Fished

 |
| --- |

三、新对象的原型就是调用create方法时传入的第一个参数

| 【示例】```javascript
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

| | —- |

class关键字 / 类

【见】class / 类:https://www.yuque.com/tqpuuk/yrrefz/cbmcfx

对象创建过程

一、JS中只有函数对象具备类的概念,因此要创建一个对象,必须使用函数对象。函数对象内部有[[Construct]]方法和[[Call]]方法,[[Construct]]用于构造对象,[[Call]]用于函数调用,只有使用new操作符时才触发[[Construct]]逻辑。
二、var obj=new Object(); 是使用内置的Object这个函数对象创建实例化对象obj。
var obj={};var obj=[];这种代码将由JS引擎触发Object和Array的构造过程。
function fn(){}; var myObj=new fn();是使用用户定义的类型创建实例化对象。
三、new Fn(args)的创建过程如下(即函数对象的[[Construct]]方法处理逻辑,对象的创建过程)。另外函数对象本身的创建过程(指定义函数或者用Function创建一个函数对象等方式)虽然也使用了下面的处理逻辑,但有特殊的地方,后面再描述。
1. 创建一个build-in object对象obj并初始化
2. 如果Fn.prototype是Object类型,则将obj的内部[[Prototype]]设置为Fn.prototype,否则obj的[[Prototype]]将为其初始化值(即Object.prototype)
3. 将obj作为this,使用args参数调用Fn的内部[[Call]]方法
3.1 内部[[Call]]方法创建当前执行上下文
3.2 调用F的函数体
3.3 销毁当前的执行上下文
3.4 返回F函数体的返回值,如果F的函数体没有返回值则返回undefined
4. 如果[[Call]]的返回值是Object类型,则返回这个值,否则返回obj
注意步骤2中, prototype指对象显示的prototype属性,而[[Prototype]]则代表对象内部Prototype属性(隐式的)。
四、构成对象Prototype链的是内部隐式的[[Prototype]],而并非对象显示的prototype属性。显示的prototype只有在函数对象上才有意义,从上面的创建过程可以看到,函数的prototype被赋给派生对象隐式[[Prototype]]属性,这样根据Prototype规则,派生对象和函数的prototype对象之间才存在属性、方法的继承/共享关系。

| 【示例】用代码来做一些验证:```javascript //Passed in FF2.0, IE7, Opera9.25, Safari3.0.4 function fn(){} //the value of implicit [[Prototype]] property of those objects derived from fn will be assigned to fn.prototype fn.prototype={ attr1:”aaa”, attr2:”bbb”}; var obj=new fn(); document.write(obj.attr1 + “
“); //result: aaa document.write(obj.attr2 + “
“); //result: bbb document.write(obj instanceof fn); //result: true document.write(“
“); //I change the prototype of fn here, so by the algorithm of Prototype the obj is no longer the instance of fn, //but this won’t affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties fn.prototype={}; document.write(obj.attr1 + “
“); //result: aaa document.write(obj.attr2 + “
“); //result: bbb document.write(obj instanceof fn); //result: false

 |
| --- |

| 【示例】关于创建过程返回值的验证:```javascript
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){
 //according to step 4 described above, 
    //the new fn() operation will return the object { attr1: 111, attr2: 222 }, it's not an instance of fn!
    return { attr1: 111, attr2: 222 };
}
fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new fn();
document.write(obj.attr1 + "<br />"); //result: 111
document.write(obj.attr2 + "<br />"); //result: 222
document.write(obj instanceof fn); //result: false

| | —- |

| 【示例】经过上面的理解应,请写出下面这幅图的实现代码。图中CF是一个函数,Cfp是CF的prototype对象,cf1, cf2, cf3, cf4, cf5都是CF的实例对象。虚线箭头表示隐式Prototype关系,实线箭头表示显示prototype关系。
【见】创建新对象 - 图1
供参考的实现方案:javascript //Passed in FF2.0, IE7, Opera9.25, Safari3.0.4 function CF(q1, q2){ this.q1=q1; this.q2=q2; } CF.P1="P1 in CF"; CF.P2="P2 in CF"; function Cfp(){ this.CFP1="CFP1 in Cfp"; } CF.prototype=new Cfp(); var cf1=new CF("aaa", "bbb"); document.write(cf1.CFP1 + "<br />"); //result: CFP1 in Cfp document.write(cf1.q1 + "<br />"); //result: aaa document.write(cf1.q2 + "<br />"); //result: bbb | | —- |