JavaScript 中 this 与 prototype 的本质区别
前面的几篇博文对 JavaScript 中原型与原型链,基本类型与引用类型,以及 Object.create 和 new 操作符的本质做了整理,通过完全手写一个 newProto 函数来实现 new 操作符的功能来理解上述概念,但是也遗留了一个小问题,即定义在 this 下的属性和方法,与定义在 prototype 下的属性和方法的本质区别是什么?本篇博文将从数据类型的角度来解释这个问题,以便加深对 JavaScript 的基本理解。
this 与 prototype
从语义上理解这两个概念,this 表示当前的,prototype 表示原型、雏形。
使用 this 的情况,是定义了一个 function 以后,在 function 的内部来通过 this 明确指向问题,而 prototype 是定义在 function 外部的。
function Student(name = "student") {
this.name = name;
}
Student.prototype.name = "student";
this 和 prototype 都定义了 name 属性,矛盾吗?
不矛盾!
function Student(name = "student") {
this.name = name;
}
Student.prototype.name = "student";
let std = new Student();
console.log(std.name);
// student
delete std.name;
console.log(std.name);
// student
delete Student.prototype.name;
console.log(std.name);
// undefined
看上述示例代码,输出了三次 std.name ,删除了两次 name 属性,分别是 std.name 和 Student.prototype.name ,仔细查看每次的输出,应该就理解:
- this 定义的属性是实例对象的属性,对实例来说是私有的,删除了就没有了;
- prototype 定义的属性是原型属性,对原型的示例来说是共有的,原型的属性删除了,那么所有对应示例的属性就没有了;
理解了这两点以后,那么下面的代码就容易看懂:
function Student(name = "student") {
this.name = name;
}
Student.prototype.name = "student";
let std1 = new Student();
let std2 = new Student();
console.log(std1.name, std2.name);
// student student
delete std1.name;
console.log(std1.name, std2.name);
// student student
delete Student.prototype.name;
console.log(std1.name, std2.name);
// undefined student
delete std2.name;
console.log(std1.name, std2.name);
// undefined undefined
之所以删除了实例对象的属性后依然可以正常输出,是因为实例对象的原型也具有同名属性,按照原型链的查找规则,一个属性会一直向上查找,直到在 Object.prototype 中也找不到为止。
将上面代码修改一下,做一下比对更容易理解。
function Student(name = "student") {
this.name = name;
this.desc = {
name: name
}
}
Student.prototype.info = {
name: "student"
};
let std1 = new Student("Alice");
let std2 = new Student("Sean");
console.log(std1.name === std2.name);
// false
console.log(std1.info === std2.info);
// true
// 修改任意一个对象的info属性值
std1.info.name = "std";
console.log(std1.info.name, std2.info.name);
// std std
// 修改任意一个对象的desc属性值
std1.desc.name = "std";
console.log(std1.info.name, std2.info.name);
// std student
可以看到,对于定义在 prototype 下的属性,在 new 实例化后实行的是 浅拷贝
,即实例近保存原型的引用,定义在 this 下的属性,new 实例化以后进行的是 深拷贝
。
总结
如果从 Java 或者 C++ 的角度来看,this 下定义的属性,在实例化以后,是每一个实例都各自拥有的属性,在实例化的时候会对 this 下的属性进行 深拷贝 以便每个实例均可各自拥有自己的独立属性,而 prototype 下定义的属性,类似于静态属性,通过 new 操作符实例化时,对 prototype 下的属性进行 浅拷贝