原型 prototype

function对象(函数是一种特殊的对象)都有一个属性prototype

  1. function Handphone() {}
  2. console.log(Handphone.prototype); // {constructor: ƒ}

prototype属性又名为「原型」,prototype也是一个对象。

既然prototype是个对象,那我们能不能保存点什么?

  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = "18:9";
  5. this.system = "Android";
  6. }
  7. // 在 Handphone 函数的 prototype 上保存些属性
  8. Handphone.prototype.rom = "64G";
  9. Handphone.prototype.ram = "6G";
  10. console.log(Handphone.prototype)
  11. // {rom: '64G', ram: '6G', constructor: ƒ}

既然已经保存了那我们看看「实例对象」能不能访问得到?

  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = "18:9";
  5. this.system = "Android";
  6. }
  7. // 在 Handphone 函数的 prototype 上保存些属性
  8. Handphone.prototype.rom = "64G";
  9. Handphone.prototype.ram = "6G";
  10. // 实例化对象拥有的属性不会去原型上寻找
  11. Handphone.prototype.screen = "16:9";
  12. Handphone.prototype.call = function () {
  13. console.log("i am calling somebody");
  14. };
  15. var hp1 = new Handphone("red", "xiaomi");
  16. var hp2 = new Handphone("black", "huawei");
  17. // 能不能访问 prototype 对象呢?
  18. console.log(hp1.rom, hp1.screen); // 64G, 18:9
  19. console.log(hp2.ram, hp2.screen); // 6G, 18:9
  20. hp2.call(); // i am calling somebody

:::info 💡 实例化对象拥有的属性不会去原型上寻找。 ::: 以上代码我们通过「实例化对象」直接访问prototype上面得属性或方法,发现竟然可以正常的打印,因此证明:「实例化对象」是可以访问「构造函数」的prototype属性。
(后面讲解为什么可以直接访问原型上的属性,而不需要 hp1.prototype.rom

以上我们认识了prototype这个对象,那么prototype到底是什么东西? :::success 其实prototype就是定义「构造函数」构造出每个「实例化对象」的公共祖先。
结合以上代码片段也就是hp1hp2的公共祖先。
所有被「构造函数」构造出的「实例化对象」都可以继承**prototype**(原型)上的属性和方法。 :::

原型的真正作用:「实例化对象」往往有一些写死的值,这些写死的值每次new的时候都会走一遍「构造函数」内部的流程,这样的写法代码其实是冗余的,也是一种耦合,所以这种情况下能不能让「实例化对象」直接继承谁,在这种时候就可以使用原型挂载一些属性和方法。

还有我们在看到别人开发库的时候,方法都会写到原型上面,只有部分属性写在构造函数内部,这是因为属性往往都是配置项,而方法都是固定一样的。

实例化对象操作 prototype

  1. // 实例化对象查询原型上面的属性
  2. function Test() {
  3. this.name = "proto";
  4. }
  5. Test.prototype.name = "prototype";
  6. var test = new Test();
  7. console.log(test); // {name: 'proto'}
  8. console.log(Test.prototype); // {name: 'prototype', constructor: ƒ}
  9. // 实例化对象无法新增原型上的属性
  10. // 只会新增到自己的实例上
  11. function Test() {
  12. this.name = "proto";
  13. }
  14. Test.prototype.name = "prototype";
  15. var test = new Test();
  16. test.num = 1;
  17. console.log(test); // {name: 'proto', num: 1}
  18. console.log(Test.prototype); // {name: 'prototype', constructor: ƒ}
  19. // 实例化对象无法删除原型的属性
  20. function Test() {
  21. this.name = "proto";
  22. this.num = 1;
  23. }
  24. Test.prototype.name = "prototype";
  25. var test = new Test();
  26. deletee test.name
  27. console.log(test); // {num: 1}
  28. console.log(Test.prototype); //{name: 'prototype', constructor: ƒ}
  29. // 实例化对象无法更改原型上的属性
  30. // 只会更改实例化对象自身的属性
  31. function Test() {
  32. this.name = "proto";
  33. }
  34. Test.prototype.name = "prototype";
  35. var test = new Test();
  36. test.name = "proto2";
  37. console.log(test); // {name: 'proto2', num: 1}
  38. console.log(Test.prototype); //{name: 'prototype', constructor: ƒ}

简化 prototype 属性

既然prototype是个对象,那我们就没必要一行一行的去赋值,可以简化成字面量的形式。

  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = "18:9";
  5. this.system = "Android";
  6. }
  7. Handphone.prototype = {
  8. rom: "64G",
  9. ram: "6G",
  10. screen: "16:9",
  11. call: function () {
  12. console.log("i am calling somebody");
  13. },
  14. };
  15. var hp1 = new Handphone("red", "xiaomi");
  16. var hp2 = new Handphone("black", "huawei");
  17. // 能不能访问 prototype 对象呢?
  18. console.log(hp1.rom, hp1.screen); // 64G, 18:9
  19. console.log(hp2.ram, hp2.screen); // 6G, 18:9
  20. hp2.call(); // i am calling somebody

构造器 constructor

我们再打印「构造函数」的prototype属性的时候能看到prototype对象有个constructor属性。

  1. function Handphone(color, brand, system) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.system = system;
  5. }
  6. console.log(Handphone.prototype)

image.png

更改 constructor

其实**constructor**属性指向的就是「构造函数」本身!!!
结合以上代码,Handphone构造函数对象有一个prototype属性,prototype是个对象,该对象有一个constructor属性,该属性指向构造函数本身。
原型、原型链、闭包立即执行函数、插件开发 - 图2

原型的constructor还可以强制指定为另外的构造函数。

  1. function Telephone() {}
  2. function Handphone(color, brand, system) {
  3. this.color = color;
  4. this.brand = brand;
  5. this.system = system;
  6. }
  7. // 强制指定另外的构造函数 constructor 为 Telephone
  8. Handphone.prototype = {
  9. constructor: Telephone
  10. };
  11. var hp1 = new Handphone("black", "iPhone", "IOS");
  12. // constructor 指向构造函数本身 Handphone
  13. console.log(Handphone.prototype);

image.png

实例化对象 proto

先看代码:

  1. function Car() {}
  2. Car.prototype.name = "Benz";
  3. var car = new Car();
  4. console.log(car);

image.png
以上代码我们能看到car实例化对象有一个__proto__属性(也就是[[Prototype]]),
image.png
__proto__属性也是一个对象,该对象保存了Car构造函数的prototype属性。
原型、原型链、闭包立即执行函数、插件开发 - 图6

:::info __proto__是实例化之后的结果,既然是实例化之后的结果,那么__proto__就是new过程中this对象的属性。
展开__proto__对象发现里面存储了构造函数的prototype
__proto__是系统内置的属性,作用就是保存构造函数的prototype属性对象。 :::

  1. function Car() {}
  2. Car.prototype.name = "Benz";
  3. var car = new Car();
  4. console.log(car);
  5. // ========== 分解 ==========
  6. /**
  7. * 当构造函数 new 的时候产生 this 对象,this对象 默认新增属性 __proto__
  8. * __proto__ 保存了构造函数的 prototype
  9. *
  10. * function Car() {
  11. * var this = {
  12. * __proto__: Car.prototype
  13. * }
  14. * }
  15. *
  16. * 所以 __proto__ 是属于对象实例
  17. *
  18. */

所以这就是为什么实例化对象能直接访问构造函数原型上属性的原型,因为它是通过**__proto__**属性去访问的。

更改实例化对象的 __proto__

  1. function Person() {};
  2. Person.prototype.name = "张三";
  3. var p1 = {
  4. name: "李四",
  5. };
  6. var person = new Person();
  7. console.log(person, person.__proto__); // Person {}, {name: "张三"}
  8. person.__proto__ = p1;
  9. console.log(person, person.__proto__); // Person {}, {name: "李四"}

接下来看几个例子 🌰 :
还有一种情况就是更改构造函数的prototype对象和重写构造函数prototype对象的区别

  1. function Car() {}
  2. Car.prototype.name = "Mazda";
  3. var car = new Car();
  4. Car.prototype.name = "Benz";
  5. console.log(car.name) // Benz
  1. function Car() {}
  2. Car.prototype.name = "Mazda";
  3. var car = new Car();
  4. Car.prototype = {
  5. name: "Benz",
  6. };
  7. console.log(car.name); // Mazda
  8. /**
  9. * 因为 Car 在运行 new 的时候 this.__proto__ 已经指向了 Car.prorotype 对象
  10. * 实例化之后重写了 Car.prototype 就和 实例化对象.__proto__ 没关系了
  11. * 和上段代码的主要区别在于「重写了构造函数的 prototype 对象」
  12. */

image.png

闭包立即执行函数

我们在之前的学习过程认识了闭包

  1. function test() {
  2. var a = 1;
  3. function plus1() {
  4. a++;
  5. console.log(a);
  6. }
  7. return plus1;
  8. }
  9. var plus = test();
  10. plus(); // 2
  11. plus(); // 3
  12. plus(); // 4

而闭包的原理就是test函数作用域内部的plus1函数被返回到全局执行执行,plus1函数牵制这test函数中的a变量。

既然知道了闭包的原理,那我们能不能用window的方式来实现一个闭包。

  1. function test() {
  2. var a = 1;
  3. function add() {
  4. a++;
  5. console.log(a);
  6. }
  7. window.add = add;
  8. }
  9. test();
  10. add(); // 2
  11. add(); // 3
  12. add(); // 4

这样也完全实现了闭包。

那我们能不能用「立即执行函数」实现一个闭包呢?

  1. var res = (function () {
  2. var a = 1;
  3. function add() {
  4. a++;
  5. console.log(a);
  6. }
  7. return add;
  8. })();
  9. res(); // 2
  10. res(); // 3
  11. res(); // 4

结果依然是能够操作函数内部的变量a

以上代码依然是通过return的方式实现闭包,我们用window接着再来优化一下:

  1. (function () {
  2. var a = 1;
  3. function add() {
  4. a++;
  5. console.log(a);
  6. }
  7. window.add = add;
  8. })();
  9. add(); // 2
  10. add(); // 3
  11. add(); // 4

插件开发

既然我们知道了可以通过window来实现闭包,我们发现有些工具库开发的时候是直接可以实例化的

  1. new Vue({
  2. // ...
  3. })

其实它内部的原理就是通过挂载到window上,然后再实例化

  1. (function () {
  2. // 防止变量污染
  3. // 内部的变量随意声明使用,外界无法访问和互相干扰
  4. function Test() {}
  5. Test.prototype.name = "ceshi";
  6. // 主要的是挂载到 window 上
  7. window.Test = Test;
  8. })();
  9. var test = new Test();
  10. console.log(test)