JS原型

  1. JS原型
  2. 原型链
  3. 原型的用途
  4. ES6 class
  5. 原型继承

JS原型

*__proto__

这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person = new Person('Alex', 16);
// Person 类型实例的原型,默认也是一个空对象
console.log(person.__proto__); // => Student {}
//es6 新方法
console.log({}.__proto__ === Object.getPrototypeOf({})); // true

我们可以直接修改对象的原型,不过被设置的值的类型只能是对象或者 null,其它类型不起作用:

const obj = { name: 'Alex' };
// 原型为空对象
console.log(obj.__proto__); // {}

obj.__proto__ = 666;
// 非对象和 null 不生效
console.log(obj.__proto__); // {}

obj.__proto__ = null;
// 设置为 null 返回 undefined
console.log(obj.__proto__); // undefined

// 设置原型为对象
obj.__proto__ = { constructor: 'Function Student' };
console.log(obj.__proto__); // { constructor: 'Function Student' }

如果被设置的值是不可扩展的,将抛出 TypeError:

const frozenObj = Object.freeze({});
// Object.isExtensible(obj) 可以判断 obj 是不是可扩展的
console.log(Object.isExtensible(frozenObj)); // => false
frozenObj.__proto__ = null; // => TypeError: #<Object> is not extensible

注:原型上的属性都是不可枚举的:Object.keys(obj)// [ ]

其实 __proto__ 是个访问器属性(getter 和 setter 都有),通过 __proto__ 访问器我们可以访问对象的[[Prototype]], 也就是原型。

Object.prototype = {
  get __proto__() {
    return this['[[prototype]]'];
  },
  set __proto__(newPrototype) {
    if (!Object.isExtensible(newPrototype)) throw new TypeError(`${newPrototype} is not extensible`);

    if (newPrototype === null) {
      this['[[prototype]]'] = undefined;
      return;
    }

    const isObject = typeof newPrototype === 'object' || typeof newPrototype === 'function';

    if (isObject) {
      this['[[prototype]]'] = newPrototype;
    }
  }
};

*prototype

function Person() {
}
Person.prototype.name = 'Alex';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Alex
console.log(person2.name) // Alex

这里,Person 就是一个构造函数,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。
可以将原型理解为:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

每个函数都有一个 prototype属性,默是一个空的纯对象,所有由构造器构造的实例的原型都是指向它 ,比如:

// 实例的原型即 person1.__proto__
console.log(person1.__proto__ === Person.prototype); // => true
console.log(person2.__proto__ === Person.prototype); // => true

*constructor

在 JavaScript 中,函数都可以用作构造器。构造器我们也可以称之为类,我们可以通过 new 构造器来构造一个实例。如上面的person1,person2。

每个原型都有一个 constructor 属性指向关联的构造函数。

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

__proto__ ,prototype,constructor,Person函数,实例 person 和原型对象 [[prototype]] 之间的关系:

__proto__ 存在于所有的对象上,prototype 存在于所有的函数上,他俩的关系就是:函数的 prototype 是所有使用 new 这个函数构造的实例的 __proto__。函数也是对象,所以函数同时有 __proto__ 和prototype。

*实例与原型

当在一个对象 obj 上访问某个属性时,如果不存在于 obj,那么便会去对象的原型也就是 obj.__proto__ 上去找这个属性。如果有则返回这个属性,没有则去对象 obj 的原型的原型也就是 obj.__proto__.__proto__去找,重复以上步骤。一直到访问纯对象的原型的原型{}.__proto.__proto__,也就是 null,直接返回 undefined。

function Person() {
}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

原型链

由上可知Object.prototype 的原型为null,即Object.prototype.__proto__ 的值为 null ,也可以说 Object.prototype 没有原型。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

各个原型之间构成的链,我们称之为原型链。

函数(构造器) Person 的原型链:

原型的用途

在使用构造器定义一个类型的时候,一般会将类的方法定义在原型上

function Cat(workingYears) {
  this.category = category;
}

// 不能使用箭头函数,箭头函数的 this 在声明的时候就根据上下文确定了
Cat.prototype.voice = function() {
  console.log(`${this.category} miaomiao叫`);
};

const cat = new Cat('大橘');
cat.voice(); // 大橘 miaomiao叫

通过这种方式,所有的实例都可以访问到这个方法,并且这个方法只需要占用一份内存,节省内存,this 的指向还能正确指向类的实例。

不过这种方式定义的方法都是不可枚举的,毕竟不是自身的属性:

const obj = {
  func() {}
};

console.log(Object.keys(obj)); // => [ 'func' ]

function Func() {}
Func.prototype.func = function() {};
// 空数组,说明 func 不可枚举
console.log(Object.keys(new Func())); // => []

ES6 class

ES6 class 就是构造器的语法糖。ES6 的 class 就是构造器,class 上的方法定义在构造器的 prototype 上。

原型继承

对于类 A 和类 B,如果满足 A.prototype.__proto__ === B.prototype,那么 A 原型继承 B

实现方法1

function A() {}
function B() {}

A.prototype = new B();

console.log(A.prototype.__proto__ === B.prototype); // true

实现方法2 (避免 B 的实例属性污染 A 的原型)

function A(p) {
  this.p = p;
}

function B() {}

// 空函数
function Empty() {}

Empty.prototype = B.prototype;
A.prototype = new Empty();
// 修正 constructor 指向
A.prototype.constructor = A;

// 满足原型继承的定义
console.log(A.prototype.__proto__ === B.prototype); // => true

const a = new A('p');
console.log(a instanceof A); // => true

const b = new B();
console.log(b instanceof B); // => true

// a 也是 B 的实例
console.log(a instanceof B); // => true
console.log(a.__proto__.__proto__ === B.prototype); // => true

实现方法3 (利用 Object.create):

function _inheritsLoose(subClass, superClass) {
  // Object.create(prototype) 返回一个以 prototype 为原型的对象
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  // 我们上面实现的原型继承没有设置这个,但是 class 的继承会设置子类的原型为父类
  subClass.__proto__ = superClass;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值