面向对象编程
prototype
- 函数具有的属性
- 之所以存在prototype属性,是为了把需要共享属性放到父类实例中,或者说该属性是为了约定其实例的规格。可以看出构造函数与实例对象的微妙关系:构造函数通过定义prototype约定其实例的规格,再通过new构造实例(面向对象编程的原理)
- 函数与函数原型对象的关系,如下:js
// 函数的显式原型的构造函数指向其函数本身 Function.prototype.constructor === Function Object.prototype.constructor === Object - 默认值:用户自定义函数的原型对象默认是一个空的对象实例,即
{} - 应用:方法挂载js
// 方式一 function WrongStudent(name) { this.name = name this.study = function() { console.log('study...') } } // 方式二 function Student(name) { this.name = name } Student.prototype.study = function(){ console.log('study...') } // 方式一存在性能问题,每当创建一个Student实例,study方法都会占用一块内存;而方式二,挂载在原型上则避免了此问题 // 那属性为什么不也挂载到原型上呢?一般属性是可读写的,不共享;而方法一般是调用,很少被重写
proto
- 对象具有的属性
- 对象的隐式原型属性指向其构造函数的显式原型实例js
// 一个对象的隐式属性指向其构造函数的显式原型 INSTANCE.__proto__ === CONSTRUCTOR.prototype // 特别地,函数对象的构造函数是其本身(函数原型链) Function.__proto__ === Function.prototype // JS一切皆是函数实现(对象原型链) Object.__proto__ === Function.prototype // ------------------------------------- // 函数创建时的原型链 new Func().__proto__ === Func.prototype Func.prototype.__proto__ === Object.prototype // 函数调用时的原型链 Func.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype // 对象显式原型对象的原型连 Object.prototype.__proto__ === null - 隐式原型属性__proto__是在对象创建时添加的,而显式原型属性prototype是在函数定义时添加的
原型链
- 定义:访问一个对象的属性,先从自身属性查找,若有直接返回,若没有再沿着__proto__隐式原型链向上查找,直到__proto__为null则停止查找,有则返回,无则undefined。注意原型链是用于查找,不能用于修改。
- 原型链图解

Function是自身创建的,所以
Function.__proto__ == Function.prototype本地对象类型是Function,所以Object是由Function创建的,于是
Object.__proto__ == Function.prototype可以反映出JS的设计是一切皆对象,具体实现是函数
- 断开原型链:
targetObj.__proto__ = null; - 牛刀小试js
// 题目一 function func() { } func.prototype.a = 1; var f1 = new func(); // 重写原型对象 func.prototype = { a: 2, b: 3 }; var f2 = new func(); console.log(f1.a,f1.b,f2.a,f2.b); //1, undefined, 2, 3 // 题目二 var f = function(){}; var F = new f(); Object.prototype.a = function(){console.log(‘a’);} Function.prototype.b = function(){console.log(‘b’);} f.a();//a f.b();//b F.a();//a F.b();//error: not a function
拓展:instanceof关键字是通过原型链判断是否该类型实例,若该类型是在对象的原型链上,则为true,否则为false
继承
原型继承
jsfunction extend(target, parentInst) { if (typeof target !== 'function') { throw new Error('目标不是一个函数') } if (!(parentInst instanceof Function)) { throw new Error('要继承的不是一个函数实例') } target.prototype = parentInst // 修正constructor属性,原本指向父构造函数 target.prototype.constructor = target return target }缺点:当原型上的属性是引用数据类型时,所有实例都会共享这个属性,即某个实例对这个属性重写会影响其他实例
js// 举例 function Pupil(name) { console.log('我是小学生') } Pupil.prototype = new Student('小辉'); Pupil.prototype.constructor = Pupil; const pupil1 = new Pupil('北大附小'); console.log(pupil1.constructor === Pupil); // true console.log(pupil1.constructor === Student); // false // 如果替换了prototype对象,必须手动将prototype.constructor重新指向其构造函数,否则就出现如下效果 Pupil.prototype = new Student('小辉'); const pupil1 = new Pupil('北大附小'); console.log(pupil1.constructor === Pupil); // false console.log(pupil1.constructor === Student); // true构造函数继承
jsfunction extend(parent, ...params) { return function(init, ...initParams) { parent.call(this, ...params) const initFn = init || function(){} initFn.call(this, ...initParams) } }缺点:
- 要求在被继承的构造函数中定义方法。通过盗用构造函数(对象伪装),继承的本质变成实例自己的方法,非公共方法,在子类之间失去复用性
- 子类无法使用父类原型上的方法
组合继承:结合原型继承和构造函数继承,前者继承方法,后者继承属性
jsfunction extend(init, parent, ...params) { if (typeof parent !== 'function') { throw new Error('要继承的不是一个函数') } const initFn = init || function(){} const target = function(...initParams) { parent.call(this, params); initFn.call(this, ...initParams) } target.prototype = new parent(...params) // 修正constructor属性,原本指向父构造函数 target.prototype.constructor = target // 另一种写法,防止parent实例占用大量内存的写法 // const obj = Object.create(parent.prototype) // obj.constructor = target // target.prototype = obj return target }类继承
js// class继承与原型继承虽然语法糖不同,但是本质都是基于原型链实现,最终类中的方法都是定义在原型上的。使用class会更为方便 class Student { constructor(name){ this.name = name } study(){ console.log('study............') } } const stu = new Student("小红") console.log(stu.__proto__.study === Student.prototype.study); //true console.log(stu.hasOwnProperty('study')); // false // 用function实现es6 class语法,需要注意以下几点: // 暂时性死区(防止函数提升) —— 使用IIFE // 构造只能new,不能调用 —— 函数调用时抛错,通过Object.getPrototypeOf(this) === XXX.prototype或new.target判断 // 静态属性 —— 直接挂载在函数上 // 访问器属性 —— 通过Object.defineProperty对函数原型对象声明getter和setter,并配置为不可枚举 // 实例属性 —— 通过Object.defineProperty对函数原型对象声明getter和setter,并配置为不可枚举。由于函数二义性,所以这里也需限制非new方式调用,通过Object.getPrototypeOf(this) === XXX.prototype或new.target判断 // 继承 —— 组合继承一个关于Date继承的特别例子
jsfunction MyDate(...params) { // console.log(new.target) this.a = 1 return Date.call(this, [...params]) } const obj = Object.create(Date.prototype) obj.constructor = MyDate MyDate.prototype = obj MyDate.prototype.test = function() { // Date { constructor: [Function: MyDate], test: [Function (anonymous)] } console.log(this.__proto__) // {} console.log(this.__proto__.__proto__) // [Object: null prototype] {} console.log(this.__proto__.__proto__.__proto__) // TypeError: this is not a Date object. // !!!说明要想调用Date方法,必须是Date构造的实例 console.log(this.getTime()) } const d = new MyDate() d.test()
