felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 构造器 #55

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

一、构造函数

在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。构造函数(constructor function)是类的一个成员方法,用于在类的实例化时初始化对象的

不过在 JavaScript 里并没有“类”(class)的概念(ES5之后加入了 class 关键词),虽然没有‘类’来创建对象,但是对象仍然可以通过多种方式创建,其中就有构造器方式,也可以称为构造函数,其表现形式就是函数。

因此我们也可以这么去理解 JavaScript 中的构造器:构造器就是一个函数,用于创建对象,通过 new 操作符调用。

二、书写规则及调用方式

JavaScript 构造器的书写规则和调用方式

我们来看个Case

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.getName = function () {
    console.log(this.name);
  }
}

// 当做构造函数使用时
var person1 = new Person('Felix', 100); // this--> person1
person1.getName(); 

// 当做普通函数使用时
var person2 = Person('Felix Cao', 111); // this->window
getName(); // 或window.getName();
console.log('person2: ' + person2);

上面的代码,当构造函数作为普通函数使用时,我们可以发现一些坑, 构造函数与一个普通函数并无不同,如果你故意不使用 new,或忘记用new,都会得到怪异的错误:

三、构造器的返回值

JavaScript 构造器就是一个函数,函数都是有返回值的!。

3.1、默认返回实例化对象

JavaScript 构造器与类式面向对象语言中的构造函数一样,默认是不需要显式 return 语句的,默认返回实例化的对象。这是 JavaScript 构造器的推荐写法。

function Person(){
  this.name = "Felix Cao";
  this.age = 100;
}
console.log(Person());  // undefined
console.log(new Person()); // Person {name: "Felix Cao", age: 100}

3.2、返回值为基本数据类型

如果存在显式 return 语句,则检查其返回值是否为引用类型,如果为非引用类型,如string, number, boolean, null, undefined,则与 3.1 一样,实际返回实例化的对象,显式 return 无效

function Person(){
  this.name = "Felix Cao";
  this.age = 100;
  return this.name;
}
console.log(Person());  // Felix Cao
console.log(new Person()); // Person {name: "Felix Cao", age: 100 }

3.3、返回值是引用类型

如果存在显式 return 语句,则检查其返回值是否为引用类型,如果是引用类型则返回引用类型,这种情况下,建议不要把这个函数当作构造器使用

function Person(){
  this.name = "Felix Cao";
  this.age = 100;

  return {
    name: 'up8Wang',
    age: 10
  };
}
console.log(Person());  // {name: "up8Wang", age: 10}
console.log(new Person()); // {name: "up8Wang", age: 10 }

四、new 操作符到底都干了啥?

前面提过, JavaScript 中的构造器是通过 new 操作符调用,在 3.3 中的Person构造函数,当我们执行var person1 = new Person()是,到底发生了什么? MDN 上是这么说的, 对于var o = new Foo();

//JavaScript 实际上执行的是:
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);

我们按照MDN上面的过程来理解下var person1 = new Person()的执行过程

var obj = new Object(); // 创建一个空对象 obj
obj.[[Prototype]] = Person.prototype; // obj 空对象的原型指向构造器 Person 的原型对象
Person.call(obj);  // Person 构造器中的 this 指向对象 obj
var person1 = obj;   // 把这个 obj 赋给 person1, 完成 var person1 = new Person() 的过程.

五、new 操作符的缺点

但是 new 操作符有个很严重的缺点:构造器内部的属性和方法无法被共享。 👍

function Person(name){
  this.name = name;
  this.group = 'China'; // 我要将 group 作为实例对象的共有属性
  this.getGroup = function() {
    console.log('method: ', this.group);
  }
}

// 生成两个实例对象
var p1 = new Person('p1');
var p2 = new Person('p2');

// 两个实例对象的 group 属性都是独立的,修改其中一个不会影响另外一个
p1.group = 'US';
p1.getGroup = function() {
  console.log('change the method in p1: ', 'US, But is not China');
}
console.log('change the property in p1: ', p1.group);
console.log(p1.getGroup());
console.log('property:', p2.group);
console.log(p2.getGroup());

在上面的代码中,p1p2 对象的 group 属性和 getGroup 方法都是独立的,修改其中一个不会影响另外一个,因此每一个实例对象都会拥有其构造器内部的属性和方法的副本。这无法做到数据共享,也是占用内存极大的浪费资源。

六、内置构造器

JavaScript 有九种内置的构造器:Object(), Function(), Array(), Number(), String(), Boolean(), RegExp(), Date(), Error(),当我们需要创建这些值的时候,我们可以自由选择使用字面量或者构造器。但是相同情况下,字面量对象不仅易读,而且运行速度更快,因为他们可以在解析的时候被优化。所以当你需要使用简单对象的时候就使用字面量吧。

Reference