原型和原型链
主要包含以下内容:
- 原型继承的方法
- 原型的动态性
- 原型对象上的引用问题
- 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
}
}
原型的动态性
实例和原型的链接只不过是一个指针。
- 若实例后,给原型加方法,相当于给原型对象加方法,依然可以访问到。
- 若实例后,重写原型对象,相当于切断实例与最初原型的联系,重写后的原型跟实例已经没有关系了。
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)
}
判断原型
isPrototypeOf, Object.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