我们常说,万物皆对象。在JS中常用的创建对象的方式有两种:对象字面量、构造函数(使用new运算符调用的函数就是构造函数)。
// 1. 对象字面量
const student1 = {
name: 'Lily',
age: 18,
sayName: function() {
console.log(this.name)
}
}
console.log(student1) // { name: 'Lily', age: 18, sayName: [Function: sayName] }
student1.sayName() // Lily
console.log(student1.constructor) // [Function: Object]
// 2. 构造函数
function Student(name, age) {
this.name = name
this.age = age
this.sayName = function() {
console.log(this.name)
}
}
const student2 = new Student('Lucy', 18)
console.log(student2) // Student { name: 'Lucy', age: 18, sayName: [Function] }
student2.sayName() // Lucy
console.log(student2.constructor) // [Function: Student]
根据以上代码,我们可以发现:
new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new运算符会进行如下的操作:
拿 ==new Student(‘Lucy‘, 18)== 来说,当代码 ==new Student(‘Lucy‘, 18)== 执行的时候,会发生以下事情:
都说到这个份上了,不模拟一下new运算符的实现似乎不太合适,嗯,来看看吧~
/**
* 模拟new运算符的实现:
* 第一个参数为构造函数名
* 后面的参数为传递到构造函数中的参数
*/
function create() {
// 1.创建一个新对象
var obj = new Object(),
// 2.获得构造函数,arguments去除第一个参数
Con = [].shift.call(arguments)
// 链接到原型,obj可以访问到构造函数原型中的属性
obj._proto_ = Con.prototype
// 绑定this实现继承,obj可以访问构造函数中的属性
var res = Con.apply(obj, arguments)
// 如果构造函数返回了一个对象,则优先返回这个对象;反之,则返回新创建的这个对象。
return res instanceof Object ? res : obj
}
// 测试一下
function Student(name, age) {
this.name = name
this.age = age
this.sayName = function() {
console.log(this.name)
}
}
const student = create(Student, 'Lily', 18)
student.sayName() // Lily
当我们使用构造函数的时候,每创建一个student实例,构造函数中的代码就会执行一次,如下代码段,我们发现每个student实例的sayName方法是指向不同的引用的,但实际上,sayName完成的是同一个操作,没有必要创建两个函数,消耗内存。
const student3 = new Student('Jack', 20)
console.log(student2.sayName === student3.sayName) // false
使用原型就可以解决这个问题。
当我们创建一个函数的时候,这个函数对象就会默认获得一个prototype属性,这个属性实际上是一个指针,指向一个对象,这个对象的用途是:包含由特定类型的所有实例对象共享的属性和方法。如果按照字面意思来理解,那么==prototype指向通过调用构造函数而创建的那个实例对象的原型对象==,使用原型对象的好处就是可以让所有实例对象共享原型对象所包含的属性和方法。
因此我们可以将实例对象不共享的属性或方法定义在构造函数中,而那些可以共享的属性或方法则直接定义在其原型对象上,如下:
function Student(name, age) {
this.name = name
this.age = age
}
Student.prototype.sayName = function() {
console.log(this.name)
}
const student1 = new Student('Lucy', 18)
console.log(student1) // Student { name: 'Lucy', age: 18 }
student1.sayName() // Lucy
const student2 = new Student('Lily', 20)
console.log(student2) // Student { name: 'Lily', age: 20 }
student2.sayName() // Lily
// sayName方法实现了共享
console.log(student1.sayName === student2.sayName) // true
每个函数都有一个prototype属性,它实际上是一个指针,当我们通过new运算符调用一个函数时,prototype就指向由这个函数创建的实例对象的原型对象。默认情况下,原型对象会拥有一个constructor属性,该属性指向prototype属性所在的函数。拿上面的例子来说,就是:Student.prototype.constructor 指向 Student
console.log(Student.prototype.constructor === Student) // true
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针[[Prototype]],指向该实例对象的原型对象,虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性_proto_,而在其他实现中,这个属性对脚本是完全不可见的。可以通过getPrototypeOf方法获取某个对象的原型,如下:
function Iphone(brand) {
this.brand = brand;
}
const iphone = new Iphone('apple');
const pro1 = Object.getPrototypeOf(iphone); // 原型对象为Iphone.prototype
console.log(pro1.constructor); // [Function: Iphone]
function Yphone(brand) {
this.brand = brand;
return {
brand
}
}
const yphone = new Yphone('you');
const pro2 = Object.getPrototypeOf(yphone); // 原型对象为Object.prototype
console.log(pro2.constructor); // [Function: Object]
通过以上代码,可以得出:
下图以Student为例展示了构造函数、原型以及实例之间的关系:
从上图我们可以得出:
当我们需要访问对象的属性时,都会进行一次搜索,搜索的目标是具有给定名字的属性:
注意:==我们可以通过实例访问原型上的属性和方法,但是不能直接修改它==。如果实例上添加了一个属性且该属性与原型上的某个属性同名时,并不会修改原型上的属性,而是直接在实例对象上添加了该属性,下次访问通过该实例对象访问这个属性时,会自己方法实例对象上这个属性的值,如下:
function Car(series, color) {
this.series = series;
this.color = color;
}
Car.prototype.brand = 'BMW';
let car1 = new Car('q7', 'black');
let car2 = new Car('x7', 'black');
// 在实例car1上添加属性brand,不会对原型产生影响
car1.brand = 'audi';
// 通过实例car1访问brand属性时,会屏蔽原型上的brand属性,直接返回实例上的brand属性
console.log(car1.brand); // audi
// 通过实例car2访问brand属性时,返回的是原型上的brand属性
console.log(car2.brand); // BMW
student1.toString() // Student { name: 'Lucy', age: 18 }
如上,student1本身以及它的原型上都没有定义toString方法,但是却可以直接调用,根据对象属性的查找规则,toString方法应该是student1原型链中的一个方法。
原型链: 如果一个原型对象是另一个类型的实例,那么这个原型对象将包含另一个原型的指针(proto),而另一个原型中也包含着指向另一个构造函数的指针。如果另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链。
student1调用的toString方法实际上是Object.prototype.toString,所有的引用类型默认都继承了Object。下图展示了完整的原型链:
原文:https://www.cnblogs.com/jiafifteen/p/12201319.html