原型-js

108 阅读3分钟

1.对象的隐式原型

每个对象都有一个特殊的内置属性[[prototype]]隐式原型,这个特殊的内置属性指向另一个对象,即隐式原型

1.1查看

// obj.__proto__:早期由浏览器实现,兼容性差
// Object.getPrototypeOf()

const a = { name: "a" };
console.log(a.__proto__);
console.log(Object.getPrototypeOf(a));
console.log(Object.getPrototypeOf(a) === a.__proto__); // true

1.2作用

// 访问对象的属性,会触发[[get]]
// 访问对象的属性会先查找该对象是否有,没就沿着原型链找,找不到就返回undefined
const p1 = { name: "a" };
console.log(p1.height); // undefined

const p2 = { name: "b" };
Object.prototype.height = "180";
console.log(p2.height); // 180

2.函数的显示原型

所有函数都有一个显示原型prototype,它指向一个对象,可以通过这一特性实现继承

2.1查看

// 注:所有函数都有一个prototype属性(箭头函数除外)
function fn() {}
console.log(fn.prototype);

2.2作用

// 通过new Person调用构造函数创建的实例对象p1\p2,p1和p2的proto(隐式原型)指向函数的显示原型Person.prototype

function Person(name) {
  this.name = name;
}
const p1 = new Person("a");
const p2 = new Person("b");
console.log(p1.__proto__ === p2.__proto__); // true
console.log(p1.__proto__ === Person.prototype); // true
console.log(p2.__proto__ === Person.prototype); // true

2.3构造函数(类)

> 1. new Person()发生了什么?

创建一个新对象
对象的隐式指向函数的显示原型
函数的this指向该对象
执行函数体
根据函数的返回值确定返回值

> 2. 显示原型上添加属性

function Person() {}
Person.prototype.money = 100;
Person.prototype.getDoubleMoney = function () {
  return this.money * 2;
};

const p1 = new Person();
console.log(p1.money, p1.getDoubleMoney()); // 100 200


/* 
为什么要添加到原型上?

结论:
属性应该写在构造函数内部,而不是原型对象上,∵每个实例都应该有属于自己的属性
公共方法应该写在原型对象上,因为可以复用,避免多次调用多次定义浪费性能 
*/

2.4constructor属性

/* 
每个原型对象上都有一个constructor,默认指向了当前函数,形成循环引用
V8引擎GC采用标记清除算法,不会存在内存泄漏问题
每个函数都会有name属性和length属性,分别对应函数名称\形参个数
*/

function Person() {}
const p1 = new Person();
console.log(Person.name); // Person
console.log(Person.length); // 0
console.log(Person.prototype.constructor === Person); //  true

2.5重写函数的显式原型

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  getDoubleMoney: function () {
    return this.age * 2;
  },
};

const p1 = new Person("p1", 10);
console.log(p1.getDoubleMoney()); // 20

> 1. 探究此时的constructor

console.log(Person.prototype.constructor); // ƒ Object() { [native code] }
console.log(Person.prototype.constructor === Object); // true

> 2. 为什么?

/* 
构造函数的显式原型prototype是一个对象,对象上的constructor就指向当前构造函数
对原型对象进行重写,此时的Person.prototype等于一个全新的对象obj,该对象由构造函数Object创建
即const obj = new Object(), 字面量写法const obj = {}, 那么obj的隐式原型就指向Object.prototype
所以Person.prototype去查找constructor属性时,没找到,就沿着原型链找到了Object上的constructor,找到了Object

总结:万物皆对象 
*/

> 3. 自定义constructor

// 正确重写函数的显示原型
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  getDoubleMoney() {
    return this.age * 2;
  },
};

Object.defineProperty(Person.prototype, "constructor", {
  value: Person,
  writable: true,
  configurable: false,
  enumerable: false,
});

console.log(Person.prototype.constructor === Person); // true

3.函数的隐式原型

/* 
 函数即是一个函数,也是一个特殊的对象
 作为对象,就有隐式原型
 */
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.height = 180;

console.log(Person.height); // 180
console.log(Person.__proto__ === Function.prototype); // true