Skip to content

原型与原型链

原型链

原型的定义

  • 每一个函数,对象,数组都有一个 prototype 属性,函数的实例对象没有 prototype 属性,但有__proto__属性并且指向函数的 prototype 属性

原型链的形成

  • 实例对象在访问属性时,首先寻找本身的属性和方法,如果没有通过__proto__去找上层原型,如果上层也没有,继续沿着__proto__往上查找,形成原型链,最顶层的是 null

原型链指向

  • 1、特殊的地方 Function.prototype===Function.proto
  • 2、特殊的地方 Object.prototype.proto === null
  • 3、函数的祖宗是 Function
  • 4、Object 的祖宗是 null
  • 5、[[Prototype]]和__proto__是等价的都是隐式原型

对象创建

TIP

Object 是 Function 的实例对象,所以Object.__proto__ === Function.prototype

js
// 方式一:
let obj = {};
// 方式二:
let obj1 = new Object();

Object.__proto__ === Function.prototype;

函数创建

TIP

所有的函数是 Function 的实例对象,所以fn.__proto__ === Function.prototype

js
// 方式一:
function fn(age) {
  console.log(age);
}
// 方式二:
let fn1 = new Function("age", "console.log(age)");

函数 constructor

js
function fn() {}
let f = new fn();
// 当获取 f.constructor 时,其实 f 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 f 的原型也就是 f.prototype 中读取,正好原型中有该属性
f.constructor === fn;
// 构造函数原型的构造指向构造函数本身
fn.prototype.constructor === fn;

console.log([].constructor === Array); // true
console.log([].constructor === Object); // false
console.log(function () {}.constructor === Function); // true

原型链的优点

  • 共享数据,减少空间占用,节省内存
  • 可以实现继承

instanceof 类型检查

  • 用于检测左变量是否在右变量的原型链上
  • 如果上层没找到会,一直往上,一直到原型链的顶端 null 为止
js
// 定义构造函数
function A() {}
function B() {}

var a = new A();
console.log(a instanceof A, a.__proto__ === A.prototype);
console.log(a instanceof B, a.__proto__ === B.prototype);
console.log(a instanceof Object, a.__proto__.__proto__ === Object.prototype);

instanceof 实现

js
function myInstanceof(left, right) {
  // 显示的原型
  let rightProto = right.prototype;
  // 实例对象的隐士原型
  let leftProto = left.__proto__;
  while (true) {
    // 如果找到返回true
    if (leftProto === rightProto) {
      return true;
    }
    // 到原型链顶端
    if (leftProto === null) {
      return false;
    }
    // 继续向上查找
    leftProto = leftProto.__proto__;
  }
}

function A() {}
function B() {}
var a = new A();
console.log(myInstanceof(a, A)); // true
console.log(myInstanceof(a, B)); // false
console.log(myInstanceof(a, Object)); // true

instanceof 可能异常

  • 未声明的变量 instanceof 会报错
js
// 安全性不如typeof
aaaa instanceof Object;
// Uncaught SyntaxError: Unexpected identifier

typeof aaaa; // 'undefined'

hasOwnProperty

  • 判断某个属性,是不是当前对象上的(不包含原型上的)
js
var obj = {
  name: "hl",
};
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("age")); // flase

new 实现

  • 1.创建一个新的对象
  • 2.新对象的__proto__ 指向构造函数的 prototype
  • 3.调用构造函数,并把 this 指向新创建的对象(通过 call)
  • 4.如果返回值的类型是基本类型直接返回新的对象,否则返回返回值
js
function myNew(obj) {
  let newObj = {};
  newObj.__proto__ = obj.prototype;
  const result = obj.call(newObj);
  if (result instanceof Object) {
    return result;
  }
  return newObj;
}

function fn2() {
  return [];
}

var o = myNew(fn2);

console.log(o);

Object.create 实现原理

  • 创建一个函数,让函数的 prototype 指向构造的原型,返回函数的实例
  • Object.create(null) 构造一个非常干净的对象,不是继承 Object 原型上的属性
js
Object.create = function (proto) {
  function F() {}
  F.prototype = proto;
  return new F();
};
let obj = { name: "hulei" };
let o = Object.create(obj);
o.age = 18;
console.log(obj); // {name:'hulei'}
console.log(o); // {age:'18'}

console.log(o === obj); // false
console.log(o.__proto__ === obj); // true

继承

  • 优点:子类具有父类的能力,代码复用
  • 缺点:如果继承的层级比较多,会造成代码混乱不好维护

构造继承

  • 构造继承只能继承私有的属性,不能继承原型的属性
js
function f() {
  this.name = "hulei";
  this.arr = [0];
}
f.prototype.save = function () {
  console.log(1);
};
function f1() {
  this.age = "25";
  f.apply(this);
}
var o1 = new f1();
console.log(o1); //{name: "hulei", age: "25"}
// o1.save(); //报错

原型继承

  • 实例对象都指向同一个原型地址,如果直接更改原型地址,会影响所有的实例对象
js
function f() {
  this.name = "hulei";
  this.arr = [0];
}
function f2() {
  this.age = 25;
}
f2.prototype = new f();
f2.prototype.constructor = f2; // 修正constructor
var o3 = new f2();
var o4 = new f2();
o3.arr.push(1);
console.log(o3.arr); // [0,1]
console.log(o4.arr); // [0,1]

组合式继承

  • 构造和原型继承的结合体,既能继承私用的,也能继承原型的,数据完全隔离互不影响
js
function f() {
  this.name = "hulei";
  this.arr = [0];
}
function f2() {
  f.apply(this);
  this.age = 25;
}
f2.prototype = new f();
f2.prototype.constructor = f2; // 修正constructor
var o3 = new f2();
var o4 = new f2();
o3.arr.push(1);
console.log(o3.arr);

console.log(o4.arr);

多继承

  • 多继承直接遍历函数原型,赋值,属性相同会后者会覆盖前者
js
function f4() {
  f1.apply(this);
  f2.apply(this);
}

for (const key in f1.prototype) {
  f4.prototype[key] = f1.prototype[key];
}

for (const key in f2.prototype) {
  f4.prototype[key] = f2.prototype[key];
}