JavaScript 常被描述为一种基于原型的语言 (prototype-basedanguage)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。 原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。 准确地说,这些属性和方法定义在Object的构造器函数(constructorfunctions)之上的prototype属性上,而非对象实例本身。通过原型这种机制,JavaScript 中的对象从其他对象继承功能特性
在javascript中,每个函数都有一个特殊的属性叫作原型(prototype)对象的原型可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得prototype是函数才有的属性,__proto__属性是对象的属性,大多数情况下,__proto__ 可以理解为“构造器的原型”,即__proto__===constructor.prototype,但是通过 Object.create()创建的对象有可能不是.Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
function Constructor(){} o = new Constructor(); // 等价于: o = Object.create(Constructor.prototype); let obj1 = { a: 1 }; let obj2 = Object.create(obj1); console.log('obj2.constructor',obj2.constructor) console.log("obj2.__proto__:",obj2.__proto__); //Object {a: 1}Object.getPrototypeOf(obj) 在ES5中,如果传递给方法的参数不是对象,则会抛出TypeError异常。 在ES6中,如果传递给方法的参数不是对象,则会强制类型转换为对象。
ES5实现继承
原型链(无法设置参数)借用构造函数组合继承原型式继承寄生式继承寄生组合式继承 ES6实现继承es6继承原型链继承(无法设置参数) 让新实例的原型等于父类的实例。 优点:能通过instanceOf和isPrototypeOf的检测
function Person() {} function Man(name,nationality) { this.name = name this.nationality = nationality } Man.prototype = new Person() console.log(Man.prototype) var man = new Man('zhangsan','English') console.log(man.name) VM219:7 Person {} VM219:9 zhangsan function Person (name,age) { this.name = name; this.age = age; this.talk = function() { console.log('person talk') } function say() { console.log(this.name + 'is' + this.age) } } Person.prototype.weight = '50' Person.prototype.sayWeight = function() { console.log('i am' + this.weight) } function Femal() {} Femal.prototype = new Person() // console.log(Femal) var a = new Femal console.log(a) // Femal {} // __proto__: Person // age: undefined // name: undefined // talk: ƒ () a.talk() // person talk // 可以继承父类原型上的属性 console.log(a.weight) // 50借用构造函数 用.call()和.apply()将父类构造函数引入子类函数 优点:可以在子类给父类传递参数 缺点:无法继承父类原型上的属性和方法
function Person (name,age) { this.name = name; this.age = age; this.talk = function() { console.log('person talk') } } Person.prototype.weight = '50' Person.prototype.sayWeight = function() { console.log('i am' + this.weight) } // 使用构造函数 function Student(sex,name,age) { Person.call(this, name, age) this.sex = sex } var s1 = new Student('girl', 'wangwu', 8) console.log(s1) // Student {name: "wangwu", age: 8, sex: "girl", talk: ƒ} console.log(s1.weight) // undefined组合继承 结合原型链继承和使用构造函数继承
缺点: 调用了两次构造函数,耗内存
function Person (name,age) { this.name = name; this.age = age; this.talk = function() { console.log('person talk') } } Person.prototype.weight = '50' function Child(sex,name,age) { this.sex = sex Person.call(this,name,age) } Child.prototype = new Person() // console.log(Child) var c1 = new Child('boy', 'lihua', 2) console.log(c1) // Child {sex: "boy", name: "lihua", age: 2, talk: ƒ} console.log(c1.weight) // 50原型式继承 必须有一个对象可以作为另一个对象的基础 使用场景:一个对象与另外一个对象保持相似,类似于Object.create 方法; 不需要构造函数。
定义一个构造函数把传入的对象赋给构造函数的原型返回构造函数的一个实例 var cat = { eye: true, eat: function() { console.log('i can eat') } } function Dag(name) { function Animal() { this.name = name } Animal.prototype = cat return new Animal() } var d1 = Dag('大黄') console.log(d1) // Animal {name: "大黄"} console.log(d1.name) // 大黄 d1.eat() // i can eat略作处理
var chicken = { eye: true, fly: function() { console.log('I can fly') } } function duck(obj, props) { function bird() { for(var prop in props) { this[prop] = props[prop] } } bird.prototype = obj return new bird() } var bjduck = duck(chicken, { name: 'roast duck', cooking: function() { console.log('北京烤鸭') } }) bjduck.fly() // I can fly寄生式继承 把原型式继承再次封装,然后在对象上扩展新的方法,再把新对象返回
var car = { wheel: 4, run: function() { console.log('我可以跑') } } function clone(obj) { function cloneObj() {} cloneObj.prototype = obj; return new cloneObj(); } function createAnother(obj, props) { var copy = clone(obj) for(var prop in props) { copy[prop] = props[prop] } copy.method = function() { console.log('我自己的方法') } return copy } var anotherCar = createAnother(car,{ color: 'red' }) console.log(anotherCar) /* 输出结果 cloneObj {color: "red", method: ƒ} color: "red" method: ƒ () __proto__: wheel: 4 run: ƒ () */寄生组合式继承, 通过借用构造函数来继承属性, 在原型上添加共用的方法, 通过寄生式实现继承.
function SuperType(name){ this.name = name; } function inherit(subType, superType){ //创建对象 var prototype = Object.create(superType.prototype); //增强对象,弥补因重写原型而失去的默认的constructor属性 prototype.constructor = subType; //指定对象 subType.prototype = prototype; // SubType.prototype = new SuperType(); } // 组合 function SubType(name, age){ SuperType.call(this, name); this.age = age; } inherit(SubType, SuperType);继承时修复实例的意义: 为了让prototype的构造函数重新指回这个类本身,否则的话它会变成之前继承的那个类的构造函数。
es6 继承
class Point { constructor(x, y) { this.x = x; this.y = y; } // 方法之间不需要逗号分隔,加了会报错。 // 类的内部所有定义的方法,都是不可枚举的 toString() { return '(' + this.x + ', ' + this.y + ')'; } } // 类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面 Object.assign(Point.prototype, { toValue(){} }); // 类必须使用new调用 var point = new Point(2, 3); // Class之间可以通过extends关键字实现继承 class ColorPoint extends Point { constructor(x, y, color) { //因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。 super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }Class不存在变量提升(hoist)