使用 new 操作符内部到底在做什么

从对象声明开始一步步介绍

1.普通对象声明

首先创建自定义对象的最简单方式就是创建一个Object的实例,然后在为他们添加属性和方法,如下所示:

var person = new Object();        //创建对象
person.name = "Nicholas";        //添加属性
person.age = 29;
person.job = "teacher";
person.sayName = function(){    //添加方法
    return this.name
};

this的含义:

  1. this表示当前作用域下的对象;
  2. this表示new Object()实例化出来的那个对象;
  3. this要放在一个作用域下,比如person.sayName()person下的方法,可用this表示方法本身。

缺点:要创建一个类似的对象会产生大量的代码。

为了解决多个类似声明的问题,用一种工厂模式,这种方法是为了解决实例化对象产生大量重复的代码。

2.工厂模式

用函数来封装以特定接口创建对象的细节。

function createPerson(name,age,job){    //创建对象
    var obj = new Object();                //添加属性
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.sayName = function(){            //添加方法
        return this.name
    };
    return obj;                            //返回对象引用
}

var person1 = createPerson("Zhangsan",29,"Teacher");    //实例化第一个对象
var person2 = createPerson("Lisi",34,"Doctor");        //实例化第二个对象
console.log(person2 instanceof Object)        //true

this的含义:
1.thisnew Object(),实例化出来的那个对象;
2.this要放在一个作用域下,比如obj.sayName(){},这是obj作用域下的的方法,可以用this来表示obj本身。

缺点:集中实例化函数,解决了大量重复的代码;从上面例子我们可以看出sayName是共有属性,而我们每实例化一个函数都会创建sayName,这也造成了重复。

3.构造函数模式

构造函数可用来创建特定类型的对象,类似Object类型。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        return this.name
    };
}

function Person2(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        return this.name
    };
}

var person1 = new Person("Zhangsan",29,"Teacher");
var person2 = new Person("Lisi",34,"Doctor");    
var person3 = new Person2("Wangwu",34,"Police");

alert(person1 instanceof Person);        //true,person1从属于Person
alert(person2 instanceof Person);        //true,person2从属于Person
alert(person3 instanceof Person2);        //true,person3从属于Person2
alert(person1 instanceof Person2);        //false,因为这里person1是从属于Person

alert(person1.sayName() == person2.sayName());    //true,构造函数的方法的值是想等的
alert(person1.sayName == person2.sayName);    //false,比较的是引用地址

我们使用new操作符,到底是在做什么

  1. 不用创建临时对象,因为 new 会帮你做(你使用「this」就可以访问到临时对象);
  2. 不用绑定原型,因为 new 会帮你做(new为了知道原型在哪,所以指定原型的名字为 prototype);
  3. 不用 return 临时对象,因为 new 会帮你做;
  4. 不要给原型想名字了,因为 new 指定名字为 prototype。
  5. persen1 和 person2 的 constructor 属性都指向 Person

缺点:每次实例化 Person,共有属性 sayName 都会重复创建,和工厂模式问题一样。

3.1把方法移动到构造函数外部

把 sayName 方法移至 Person 外面,这样每次实例化 Person 内部的 sayName 只是全局 sayName 的引用,这样避免了重复。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName(){            //把构造函数内部的方法通过全局来实现,引用地址一致
    return this.name
}

var person1 = new Person("Zhangsan",29,"Teacher");
var person2 = new Person("Lisi",34,"Doctor");    

缺点:

  1. 全局 sayName 函数和 Person 之间联系不紧密,如果它们中间有很多代码,sayName 就不知道是干嘛用的了;
  2. 如果方法很多,每个都是全局函数,就没封装可言了;
  3. 用全局函数很容易覆盖全局变量。

4.原型模式

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

  1. 如果是实例方法,不同的实例化,它们的方法和地址是不一样的,是唯一的;
  2. 如果是原型方法,那它们的地址是共享的,大家都一样。
function Person(){}        //构造函数什么体内什么都没有,如果有叫做实例方法,实力属性

Person.prototype.name = "Zhangsan";
Person.prototype.age = 29;
Person.prototype.job = "Teacher";
Person.prototype.sayName = function(){
    return this.name
};

var person1 = new Person();
person.sayName()     //Zhangsan

var person2 = new Person();
person.sayName()     //Zhangsan

alert(person1.sayName === person2.sayName);    //true

缺点:重复敲Person.prototype,造成大量的重复输入。

4.1字面量方式创建原型

function Person(){}//使用字面量的方式创建原型对象,这里的`{}`就是对象,是`Object`,`new Object`相当于`{}`

Person.prototype = {
    constructor:Person,    //强行指向实例
    name: "Zhangsan",
    age: 29,
    job: "Teacher",
    sayName: function(){
        return this.name
    }
};

var person = new Person();

注意:

  1. 实例化后重写原型对象,会切断现有实例和新原型之间的联系
  2. 不能重写原型中的属性,如 person.name = 'Lisi',它会变成 person 的实例属性。

缺点:constructor不在指向实例,而会指向Object。新对象的constructor重写Person原来的constructor,因此会指向新对象。
解决方法:在原型内部,可以设置constructor强行执行实例。

4.2组合构造函数模式和原型模式

构造函数模式用于定义实力属性,原型模式用于定义方法和共享属性

function Person(name,age,job){        //保持独立的使用构造函数
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Xiaoming","Fangfang"];
}

Person.prototype = {        //保存共享的使用原型
    constructor: Person,
    sayName: function(){
        return this.name
    }
}

var person1 = new Person("Zhangsan",29,"Teacher");
var person2 = new Person("Wangwu",34,"Doctor");

person1.friends.push("Xiaohong");
alert(person1.friends);    //"Xiaoming,Fangfang,Xiaohong"
alert(person2.friends);    //"Xiaoming,Fangfang",引用类型没有使用原型,所以没有共享
alert(person1.friends == person2.friends);    //false
alert(person1.sayName == person2.sayName);    //true

注意:实例化的私有属性是自有的

5.动态原型模式

动态原型模式,把所有信息都封装在了构造函数中。

function Person(name,age,job){        //保持独立的使用构造函数
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Xiaoming","Fangfang"];
    
    if(typeof this.sayName != "function"){    //仅在第一次时初始化
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}

原型的初始化,只要第1次初始化,就可以了,没必要每次构造函数实例化的时候都初始化,可以将原型封装在函数里。
注意:使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

proto 和 prototype

__proto__:是实例化后的原型属性
prototype:是 JS 内部提供的原型属性

上面例子中
person1.__proto__ === Person.prototype
person1.__proto__.__proto__ === Object.prototype

之前写过一篇文章阐述它们之间的不同:前端学习笔记之原型——一张图说明prototype__proto__的区别

你可能感兴趣的:(javascript,对象,原型)