原型?一次弄懂👌

572 阅读3分钟

原型是什么

  • 所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
const arr = []
console.log(typeof arr.__proto__); // object
  • 所有引用类型__proto__属性指向构造函数的prototype
console.log(arr.__proto__ === Array.prototype); // true
  • 当一个对象在查找一个属性的时候,自身没有就会根据__proto__向它的原型进行查找,终点是Object.prototype.__proto__null,查找的过程形成了原型链
console.log(arr.toString); // [Function: toString]

探索原型链

小伙伴们可以移步到JavaScript 世界万物诞生记,一篇帮助理解原型链的好文👀

下面请出万恶之源

0.jfif

  • 为什么需要原型和原型链
function Person(name) {
  this.name = name;
  this.sayname = function() {
    console.log(name);
  }
}

let p1 = new Person('xguo');
let p2 = new Person('xguo');

console.log(p1.sayname === p2.sayname); // false

用构造函数调用生成实例对象,有一个缺点,无法共享属性和方法。每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费,考虑到这些,原型和原型链诞生了

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

Person.prototype.sayname = function() {
  console.log('----');
}

let p1 = new Person('xguo');
let p2 = new Person('xguo');

console.log(p1.sayname === p2.sayname); // true
  • 如何理解原型链

我们知道Object.prototype.__proto__ === null,为什么呢?因为JS从设计之初就想用null表示空对象,最直接的证据:typeof null === 'object';因此ES5中用Object.create(null)来生成没有原型的空对象

console.log(Object.prototype.__proto__ == Object.create(null).__proto__); // true

也就是说,Object.prototype 是使用这种方式生成的,然后才绑在 Objectprototype 属性上,因为对象可以先生成再赋予新属性

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

Function 是一个函数,只要是函数,它的__proto__就指向 Funtion.prototype这是继承的设定。Function 生成时,是没有__proto__属性的,它是一个BOM对象,Function __proto__prototype属性都是后面才指向同一个 BOM 对象的

console.log(Object instanceof Function) // true
console.log(Object.__proto__=== Function.prototype); // true

Object是构造器函数, 因此它的__proto__属性会指向Funtion.prototype

function Person() {}
console.log(typeof Person.prototype);
console.log(typeof Function.prototype);

Function.prototype是一个函数,也是对象,只有Functionprototype是函数其他都是普通对象。它是一个桥梁,我们一直说函数也是对象,只有这个满足了, 函数才能是对象

“构造函数” new

function Cat(name, color) {
    // 像其他语言的封装性 降低其他语言开发者的门槛
    this.name = name
    this.color = color
}

var cat1 = new Cat('狗蛋', '白色')
console.log(cat1.constructor === Cat); // true

我们使用new关键字,看起来是执行了类的构造函数方法,Cat的首字母也大写了,并且cat1.constructor === Cat,一切都好像在提示它是一个类

  • 构造函数还是调用 实际上Cat和程序中的其他函数没有任何区别,幕后黑手是new,它会劫持所有普通函数并用构造对象的形式调用它。换句话说,在JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用

  • .constructor属性

console.log(cat1.constructor === Cat.prototype.constructor); // true

实际上,Cat.prototype.constructor默认指向Cat,这和“构造”毫无关系。

function Cat() {}
Cat.prototype = {}

var cat1 = new Cat()
console.log(cat1.constructor === Cat); // false
console.log(cat1.constructor === Object); // true

.constructor属性只是Cat函数在声明时的默认属性,如果创建一个新对象并替换函数默认的.prototype引用,那么新对象并不会自动获得.constructor属性

  • 手动实现简版new
function Person(name, age, sex="女") {
    // console.log(arguments)
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 实例除了属性各有各的(私有的 constructor)通过prototype 
// 对象间共享的方法(prototype )
// construtor 完成对象构造,再使用prototype连接共享方法

Person.prototype.sayHi = function() {
    console.log('你好啊');
}
// 手动实现new
const objectFactory = function() {
    var obj = new Object();
    // [].shift [] 对象 shift 上的方法 
    // this -> [] call  -> arguments 
    // console.log(Array.from(arguments).shift());
    var varConstructor = [].shift.call(arguments);
    
    varConstructor.call(obj, ...arguments);
    obj.__proto__ = varConstructor.prototype;
    return obj;
}

const obj = objectFactory(Person, 'xguo', 19, '男');
console.log(obj); // Person {name: 'xguo', age: 19, sex: '男'}
obj.sayHi(); // 你好啊