你要看的全部在这里——原型和原型链

898 阅读11分钟

原型和原型链

主要包含以下内容:

  • 原型继承的方法
  • 原型的动态性
  • 原型对象上的引用问题
  • constructor属性
  • 原型对属性设置的影响
  • ES6中的类是如何实现的
  • 原型的一些常用方法

原型: 每个对象A都有一个[[prototype]]属性,指向另一个对象B,可通过__proto__访问,B就是A的原型。

原型链:实例可访问原型上的属性和方法,通过原型链接,实现继承的方法。 存在构造函数A、B, B.prototype = new A();这样B的原型就指向A的实例(A实例也有属性),A的实例又可以访问A原型A.prototype上的属性和方法,此时可以在B.prototype上添加其余的属性和方法。B的实例就可以访问B.prototype,又可以访问A.prototype,形成了原型链。

继承并不会进行复制,改为委托更合适些。

原型继承的方法

原型链的继承是通过重写原型对象实现的。 利用原型让一个引用类型继承另一个引用类型的方法和属性。

继承方式特点缺点
原型链继承SubType.prototype = new SuperType();
使用的是构造函数
属性的引用,给父类的传参
原型式继承anotherPerson = Object.create(person);
使用的是对象
属性的引用
构造函数SuperType.call(this, name);
解决属性引用及传参问题
方法无法继承
组合继承SuperType.call(this, name);
SubType.prototype = new SuperType();
调用两次父类的构造函数
寄生式继承创建一个封装继承过程的函数,在该函数中增加方法几乎不单独使用,
比原型式继承多增加了方法,
存在属性引用问题
寄生组合式继承SuperType.call(this, name);
SubType.prototype = Object.create(SuperType.prototype);
解决父类构造函数调用两次的问题

原型链继承 vs 原型式继承

原型链继承使用的是构造函数,原型式继承使用的是对象。继承方法,属性继承有问题

原型链继承:

function SuperType() {
  this.name = '123'
}

SuperType.prototype.sayName = function(){console.log(this.name)}

function SubType() {
  this.age = 456
}
//通过将SubType的原型指向SuperType的实例,实现继承SuperType
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){console.log(this.age)}

原型式继承:

var person = {
  name: '123',
  age: 456
}

var anotherPerson = Object.create(person);

存在的问题:1.原型对象的引用问题; (new出来的对象属性中存在obj,arr等引用类型属性) 2. 没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

构造函数 :

属性共享,解决原型对象的引用和传参问题。继承属性,不继承方法

存在的问题:方法无法继承。

function SuperType(name){
  this.name = name
  this.colors = ['red', 'yellow'] // 原型对象的引用问题。
}
SuperType.prototype.sayName = function(){console.log(this.name)};
function SubType(name, age){
  // 1. 传参name 2. this为new出来的实例instance,传入SuperType,所以属性是直接挂载在实例上的。
  // sayName方法并未继承
  SuperType.call(this, name);
  this.age = age
}
var instance = new SubType('123', 456);

组合继承

相比构造函数就是把子类的原型指向父类的原型。

通过构造函数继承属性,通过原型继承方法。

缺点:会调用两次父类的构造函数

function SuperType(name){
  this.name = name
  this.colors = ['red', 'yellow']
}
SuperType.prototype.sayName = function(){console.log(this.name)};

function SubType(name, age){
  SuperType.call(this, name);
  this.age = age
}
// 与构造函数方法区别之处。
SubType.prototype = new SuperType();
// 	给原型添加方法的代码一定要放在替换原型的语句之后。
SubType.prototype.sayAge = function(){console.log(this.age)};

var instance = new SubType('123', 456);

寄生式继承

只不过是比原型式继承多增加了方法。仅创建一个封装继承过程的函数。依然无法解决属性引用问题

function createAnother(original){
  var clone = Object.create(original);
  clone.sayHi = function(){console.log('hi')};
  return clone;
}
var person = {naem: '123', colors: ['red', 'yellow']}
var another = createAnother(person);

寄生组合式继承

原型的继承使用Object.create的方式。

function SuperType(name){
  this.name = name
  this.colors = ['red', 'yellow']
}
SuperType.prototype.sayName = function(){console.log(this.name)};
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age
}
// 与组合继承区别:改用Object.create()。
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function(){console.log(this.age)};

var instance = new SubType('123', 456);

原型的关联有几种方法:

// 会凭空创建一个新对象,并把新对象内部的[[prototype]]关联到你指定的对象a上。缺点是创建一个新对象,然后把就对象抛弃掉。
Bar.prototype = Object.create(Foo.prototype);
// 其实是将Bar原型指向Foo的原型上,它们引用同一个对象,所以更改Bar的原型会影响到Foo的原型。
Bar.prototype = Foo.prototype;
// 会指向Foo的构造函数,如果此时函数Foo有一些副作用,就会造成不必要的影响。
Bar.prototype = new Foo();
// ES6的方法,完美。
Object.setPrototypeOf(Bar.prototype, Foo.prototype);

Object.create()

// 创建一个object,并将a的原型指向b。
var a = Object.create(b);

Object.create(null); //创建一个空[[prototype]]链接的对象,也就是没有原型链的对象。此时使用instanceof总是会返回false。通常被称为字典,用来存储数据,不会受到原型链的干扰。
// Object.create polyfill
if(!Object.create) {
  Object.create = function(o) {
    function F(){};
    F.prototype = o; // 将F.prototype指向o
    return new F(); // new出来的对象原型为F.prototype
  }
}

原型的动态性

实例和原型的链接只不过是一个指针。

  1. 若实例后,给原型加方法,相当于给原型对象加方法,依然可以访问到。
  2. 若实例后,重写原型对象,相当于切断实例与最初原型的联系,重写后的原型跟实例已经没有关系了。
function Person(){}
var friend = new Person();
Person.prototype.sayHi = () => {console.log('hi')};
friend.sayHi();// 'hi'
friend instanceof Person; // true

Person.prototype = {
  sayBye: function(){console.log('bye')}
}

friend instanceof Person; // false。因为Person.prototype已经不是friend的原型了。
friend.sayHi(); // 'hi', friend的原型还是之前的原型
friend.sayBye(); //error
// 重写后原型无constructor属性,但不影响instanceof
var friend2 = new Person();
friend2 instanceof Person; // true
// delete可以删除实例上的方法,无法删除原型上的方法
friend2.sayBye = () => {console.log('no')};
friend2.sayBye(); //'no'
delete friend2.sayBye
friend2.sayBye(); //'bye'
delete friend2.sayBye
friend2.sayBye(); // 'bye'

原型对象上的引用问题

function Person() {}
Person.prototype = {
  friends: ["Shelby", "Court"],
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");

alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

constructor属性

constructor属性存在原型上,并不存在实例上。该属性不被信任,尽量避免使用。

function Foo(){}
var a = new Foo();
Foo.prototype.constructor === Foo; // true. 原型上有一个constructor属性指向构造函数Foo
a.constructor === Foo // true
// 以上是a的constructor属性并不存在,此时会查找原型Foo.prototype,原型上的constructor指向Foo。

Foo.prototype = {}; // 重写原型对象,无constructor属性
var b = new Foo();
Foo.prototype.constructor === Foo; // false
b.constructor === Foo //false
// 修复方法如下
Foo.prototype.constructor = Foo;

原型对属性设置的影响

属性的设置实际上就是查找对象及原型链的过程。原型上的属性不会改变,但原型上的属性会影响是否会在对象上创建新属性。设置myObject.a = 'bar',有三种情况:

var person = {};

Object.defineProperties(person, {
    name: {
        writable: false,
        value: 'langlang',
    },
    age: {
        writable: true,
        value: '3'
    },
    money: {
        set: function(count) {
            console.log('set: ', count);
        },
        get: function() {
            return 18000
        }
    }
})

var another = Object.create(person);

another.name = '227' //1.原型上存在该属性且不可写,无法修改或创建属性,非严格模式下不起作用,严格模式下报错。
// another无name属性,person上的name属性未改变。
another.age = '18' // 2.原型上存在该属性且可写,会在对象上创建该属性。 another上增加age属性
another.money = 30000 // 3.原型上该属性setter,会触发该setter,不会在对象上创建属性。

ES6中的类是如何实现的

ES6代码

class Parent {
  constructor(name, age) {
    this.name = name;
    //箭头函数相当于下面的语句
    //this.ask = () => {}
  }
  // 正常函数式定义在原型上的。
  speakSomething() {
    console.log("parent speak");
  }
  // 箭头函数式定义在实例上的。
  ask = () => {
    console.log("parent ask");
  };
 // 静态函数式定义在构造函数上的。
  static sayHello(){
  	console.log('hello')
  }
}// 运行到控制到一眼便知。new Parent()

class Children extends Parent {
  constructor(props) {
    super(props);
  }

  speakSomething() {
    // super相当于调用父类原型上的方法。
    super.speakSomething();
    console.log("children speak");
  }

  ask = () => {
    super.ask();
    console.log("children ask");
  };
}

Children.sayHello() // hello. 构造函数的继承

var a = new Children('chen', 3);

var parentObj = new Parent();
parentObj.ask(); // 会报错。因为箭头函数没有super。另外ask是在实例parentObj上的,super也就是原型Parent.prototype上并没有ask方法。

转移后的代码:

"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); }

function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _inherits(subClass, superClass) { 
  //superClass可以为function或者null
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function"); 
  } 
  //创建原型的关联:构造函数原型的原型关联。
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); 
  //创建构造函数的关联: 构造函数的原型关联
  if (superClass) _setPrototypeOf(subClass, superClass); 
}
// Symbol.hasInstance在Function.prototype上定义。right[Symbol.hasInstance](left):
// 如果传入的值left为函数right的实例,则返回ture。 该函数就是instanceof
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
// 构造函数不能直接调用。
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _createClass(Constructor, protoProps, staticProps) { 
  //正常的函数是定义在原型上的。
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
  //静态属性是定义在构造函数中的
  if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; 
}
--------------------------------工具函数结束-----------------------------------------

var Parent = /*#__PURE__*/function () {
  function Parent(name, age) {
    // 检查是否通过new调用
    _classCallCheck(this, Parent);
		// this指向new出来的实例,ask是定义在这个实例上的。也就是上面说的箭头函数为实例上的属性
    _defineProperty(this, "ask", function () {
      console.log("parent ask");
    });
    this.name = name; 
  }

  _createClass(Parent, [{// 第二个参数。为定义在原型上的方法
    key: "speakSomething",
    value: function speakSomething() {
      console.log("parent speak");
    }
  }], [{// 第三个参数。为定义在构造函数上的静态方法。
    key: "sayHello",
    value: function sayHello() {
      cosnole.log('hello');
    }
  }]);

  return Parent;
}();

// 可以看到使用的是寄生组合式继承
var Children = /*#__PURE__*/function (_Parent) {
  //1. 原型的关联 2. 构造函数的关联
  _inherits(Children, _Parent);

  var _super = _createSuper(Children);

  function Children(props) {
    var _thisSuper, _this;
		// 检查是否通过new调用
    _classCallCheck(this, Children);
		// 相当于构造函数方式,继承方法:SuperType.call(this, props)
    _this = _super.call(this, props);

    _defineProperty(_assertThisInitialized(_this), "ask", function () {
      _get((_thisSuper = _assertThisInitialized(_this), _getPrototypeOf(Children.prototype)), "ask", _thisSuper).call(_thisSuper);

      console.log("children ask");
    });

    return _this;
  }

  _createClass(Children, [{
    key: "speakSomething",
    value: function speakSomething() {
      // super相当于调用父类原型上的方法。并传入this
      _get(_getPrototypeOf(Children.prototype), "speakSomething", this).call(this);

      console.log("children speak");
    }
  }]);

  return Children;
}(Parent);

var a = new Children('chen', 3);

箭头函数和正常函数babel在转义的时候会的区别?

答:箭头函数是绑定在实例上的,而正常函数式在原型上的。

参考文章:

类的写法:

Class PersonClass {
  construtor(name) {
    this.name = name
  }
  sayName() {
    // PersonClass = '123'; 在内部修改名称是错误的
    console.log(this.name)
  }
}
// 命名表达式写法, 此时typeof PesonClass2为undefined
let PersonClass = class PersonClass2 {}

等价于ES5的写法:

let PersonType = (function(){
  'use strict';
  // 此处标明为啥不能在内部修改类的名称。
  // 命名表达式写法此处应为const PersonClass2 = ....
  const PersonType = function(name){
    if(typeof new.target === 'undefined') {
      throw new Error('必须通过关键字new来调用构造函数');
    }
    this.name = name;
  }
  Object.defineProperty("PersonType.prototype", 'sayName', {
    value: fucntion(){
			if(new.target !== 'undefined') {
	    	throw new Error('不可使用关键字new调用该方法');
  		}   
  		console.log(this.name);
  	},
    enumerable: false, // 不可枚举
    writable: true,
    configurable: true,
  })
  return PersonType;
})();

原型的一些常用方法

枚举属性

function Person(){
  this.name = 'oulang' // 实例'name'
}
var person = new Person();
Person.prototype.age = 30; // 原型'age'
Object.defineProperty(person, 'awesome', { // 不可枚举'awesome'
  value: true,
  enumerable: false,
})

Object.keys(person); // 'name'
for(var prop in person){console.log(prop)}; //'name', 'age'
Object.getOwnPropertyNames(person); //'name', 'awesome'
'awesome' in person; 'name' in person; 'age' in person; //true
方法枚举属性范围
Object.keys(obj)实例上,可枚举
for(var prop in obj){}实例和原型上,可枚举
Object.getOwnPropertyNames(obj)实例
prop in obj实例和原型上

判断属性在原型上还是实例上:

hasOwnerProperty(),属性存在对象上才返回true

function hasPrototypeProperty(key, obj) {
  return !obj.hasOwnerProperty(key) && (key in obj)
}

判断原型

isPrototypeOfObject.getPrototypeOf()instanceof

function Person() {}
var friend = new Person();
Person.prototype.isPrototypeOf(friend); // true
Object.getPrototypeOf(friend) === Person.prototype; // true
friend instanceof Person; // true

isPrototypeOf vs instanceof

// isPrototypeOf 更通用,instanceof右侧必须是函数
var superObj = {}
var sub = Object.create(superObj);
super.isPrototypeOf(sub); // true
sub instanceof super; // Right-hand side of 'instanceof' is not callable

isPrototpyeof如何实现:

function isRelatedTo(o1, o2) {
  function F(){};
  F.prototype = o2;
  return o1 instanceof F;
}
// 使用
var a = {};
var b = Obeject.create(a);
isReatedTo(a, b); // true

Object.setPrototypeOf(obj, prototype): 设置obj的原型为prototype。

// bable上的兼容
function _setPrototypeOf(obj, prototype) {
	obj.__prototype__ = prototype;
  return obj;
}

判断对象的类型

// typeof 不能区分数组,对象,null
typeof null; typeof []; typeof window; // 'object'
typeof function(){}; // 'function'

// Object.prototypeof.toString 通用
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(); // "[object Undefined]"
Object.prototype.toString.call(window); // "[object Window]"
var arr=[1,2,3];
arr.toString(); // '1,2,3'
Object.prototype.toString.call(arr); // 'object, Array'
// 这是因为Array.protoType重写了toString方法
// Object.prototype.toString 返回的就是由 [object、[[Cass]]、 ]组成的字符串 
// http://es5.github.io/#x15.7.4