function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job;
    this.sayName = function() { 
    console.log(this.name);
    }; 
}
let person1 = new Person("Nicholas", 29, "Software Engineer"); 
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas 
person2.sayName(); // Greg

要创建person实例,就要使用new操作符调用Person构造函数,这样会产生如下操作:
1、在内存中创建一个新对象;
2、这个新对象内部的[[Prototype]]特性被赋值为构造函数的Prototype属性;
3、构造函数内部的this被赋值为这个新对象;
4、执行构造函数内部代码;
5、如果构造函数返回非空对象,则返回该对象;否则返回刚创建的新对象。

该例子中,person1和person2分别保存着Person的不同实例,这两个对象都有一个constructor属性指向Person:

console.log(person1.constructor == Person); // true 
console.log(person2.constructor == Person); // true

补充:
1、构造函数也是函数
构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。并没有把某个函数转化为构造函数的语法。任何函数只要使用new运算符调用就是构造函数,不用new操作符调用就是普通函数,比如上面的Person:

// 作为构造函数
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"

2、构造函数的问题
构造函数的主要问题是其定义的方***在每个实例上都创建一遍。对前面的例子而言,person1和person2都有sayName方法,但却是不同的Function实例。因为都是做一件事,没有必要定义两个实例。想要解决这个问题,可以把函数定义转移到构造函数外部进行:

function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job;
    this.sayName = sayName; 
}
function sayName() { 
console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer"); 
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas 
person2.sayName(); // Greg

这样就解决了函数重复定义的问题,但又出现了新的问题。函数全部定义在外部扰乱了全局作用域,如果对象需要多个方法,那就要在全局作用域定义多个函数,这会导致自定义函数代码无法结合在一起,该问题可以通过原型模式解决。