|

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 下的属性进行 浅拷贝

类似文章

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注