最近又攀登了一下JS三座大山中的第二座。爬山过程很酸爽,一路发现了许多以前没曾注意到的美景。本着独乐乐不如众乐乐的原则,这里和你们分享一下。es6
有些人认为 JavaScript 不是真正的面向对象的语言,好比它没有像许多面向对象的语言同样有用于建立class类的声明(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)
。JavaScript 用一种称为构建函数的特殊函数来定义对象和它们的特征。不像“经典”的面向对象的语言,从构建函数建立的新实例的特征并不是全盘复制,而是经过一个叫作原形链的参考链连接过去的。同理,原型链也是实现继承的主要方式(
ES6的extends只是语法糖
)。面试
一直在犹豫,究竟是先讲建立对象的方法仍是先讲原型。为了后面保证讲建立对象方法的连贯性,这里仍是先讲讲原型吧,
这里为了权威,直接就摘抄MD
N的定义了segmentfault
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)
——每一个对象拥有一个原型对象
,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)
,它解释了为什么一个对象会拥有定义在其余对象中的属性和方法。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例自己。数组
这个__proto__属性有什么用呢?在传统的 OOP 中,首先定义“类”,此后建立对象实例时,类中定义的全部属性和方法都被复制到实例中。在 JavaScript 中并不如此复制,而是在对象实例和它的构造器之间创建一个连接(它是__proto__属性,是从构造函数的prototype属性派生的),以后经过上溯原型链,在构造器中找到这些属性和方法。浏览器
简单的说,就是实例对象能经过本身的__proto__属性去访问“类”
原型(prototype)上的方法和属性,类若是也是个实例,就会不断往上层类的原型去访问,直到找到app
补充:
1.“类”的原型有一个属性叫作constructor指向“类”
2.__proto__已被弃用,提倡使用Object.getPrototypeOf(obj)
函数
举例:性能
var arr = [1,2,3] //arr是一个实例对象(数组类Array的实例) arr.__proto__ === Array.prototype //true 实例上都有一个__proto__属性,指向“类”的原型 Array.prototype.__proto__ === Object.prototype //true “类”的原型也是一个Object实例,那么就必定有一个__proto__属性,指向“类”object的原型
这里补充一个知识点:
浏览器在在Array.prototype上内置了pop方法,在Object.prototype上内置了toString方法测试
上图是我画的一个原型链图this
[1,2,3].pop() //3 [1,2,3].toString() //'1,2,3' [1,2,3].constructor.name //"Array" [1,2,3].hehe() //[1,2,3].hehe is not a function
当咱们调用pop()的时候,在实例[1,2,3]上面没有找到该方法,则沿着原型链搜索"类"Array的原型,找到了pop方法并执行,同理调用toString方法的时候,在"类"Array没有找到则会继续沿原型链向上搜索"类"Object的原型,找到toString并执行。
当执行hehe方法的时候,因为“类”Object的原型上并无找到,搜索“类”Object的__proto__,因为执行null,中止搜索,报错。
注意,[1,2,3].constructor.name显示‘Array’不是说明实例上有constructor属性,而是正是由于实例上没有,因此搜索到
类的原型上了,找到了constructor
怎么建立对象,或者说怎么模拟类。这里我就不学高程同样,给你们介绍7种方法了,只讲我以为必须掌握的。毕竟都es6 es7了,不少方法基本都用不到,有兴趣本身看高程。
const Person = function (name) { this.name = name this.sayHi = function () { alert(this.name) } } const xm = new Person('小明') const zs = new Person('张三') zs.sayHi() //'张三' xm.sayHi() //'小明'
缺点: 每次实例化都须要复制一遍函数到实例里面。可是不论是哪一个实例,实际上sayHi都是相同的方法,不必每次实例化的时候都复制一遍,增长额外开销。
function specialArray() { var arr = new Array() arr.push.apply(arr,arguments) arr.sayHi = function () { alert('i am an specialArray') } return arr } var arr = new specialArray(1,2,3)
这个和在数组的原型链上增长方法有啥区别?
原型链上增长方法,全部数组均可以用。寄生构造函数模式只有被specialArray类new出来的才能用。
//共有方法挂到原型上 const Person = function () { this.name = name } Person.prototype.sayHi = function () { alert(this.name) } const xm = new Person('小明') const zs = new Person('张三') zs.sayHi() //'张三' xm.sayHi() //'小明'
缺点:基本没啥缺点了,建立自定义类最多见的方法,动态原型模式
也只是在这种混合模式下加了层封装,写到了一个函数里面,好看一点,对提升性能并无卵用。
es6的‘类’class其实就是语法糖
class Person { constructor(name) { this.name = name } say() { alert(this.name) } } const xm = new Person('小明') const zs = new Person('张三') zs.sayHi() //'张三' xm.sayHi() //'小明'
在es2015-loose模式下用bable看一下编译
"use strict"; var Person = /*#__PURE__*/ function () { function Person(name) { this.name = name; } var _proto = Person.prototype; _proto.say = function say() { alert(this.name); }; return Person; }();
分析:严格模式,高级单例模式封装了一个类,实质就是组合使用原型和构造函数
知识点:
以前在JS核心知识点梳理——数据篇里面说了一下判断判断类型的四种方法,这里借着原型再来分析一下
只能判断基础类型中的非Null,不能判断引用数据类型(由于所有为object)它是操做符
用于测试构造函数的prototype属性是否出如今对象的原型链中的任何位置 风险的话有两个
//判断不惟一 [1,2,3] instanceof Array //true [1,2,3] instanceof Object //true //原型链能够被改写 const a = [1,2,3] a.__proto__ = null a instanceof Array //false
仿写一个instanceof,而且挂在Object.prototype上,让全部对象都能用
//仿写一个instance方法 Object.prototype.instanceof = function (obj) { let curproto = this.__proto__ while (!Object.is(curproto , null)){ if(curproto === obj.prototype){ return true } curproto = curproto.__proto__ } return false } [1,2,3].instanceof(Array) //true [1,2,3].instanceof(Object) //true [1,2,3].instanceof(Number) //false [1,2,3].instanceof(Function) //false 1..instanceof(Function) //false (1).instanceof(Number) //true
constructor 这玩意已经介绍过了,“类”的原型执行constructor指向“类”
风险的话也是来自原型的改写
[1,2,3].constructor.name //'Array' // 注意下面两种写法区别 Person.protorype.xxx = function //为原型添加方法,默认constructor仍是在原型里 Person.protorype = { //原型都被覆盖了,没有constructor了,所要要手动添加,要否则constructor判断失效 xxx:function constructor:Person }
试了下,好像这个方法也不是很准
null 能够用object.is(xxx,null)代替
Array 能够用Array.isArray(xxx)代替
Object.prototype.toString.call([1,2,3]) //"[object Array]" Object.prototype.toString.call(function(){}) //"[object Function]" Object.prototype.toString.call(1) //"[object Number]" Object.prototype.toString.call(null) //"[object Null]" Object.prototype.toString.call({}) //"[object Object]" Object.prototype.toString.call(undefined) //"[object Undefined]" Object.prototype.toString.call(true) // 特别注意 特别注意 特别注意"[object Object]" Object.prototype.toString.call('string') // 特别注意 特别注意 特别注意 "[object Undefined]"
对于for in 和in 都是沿着原型链查找属性是否存在,能够利用hasOwnProperty进行相关过滤
// 'in' operation test class Person { constructor (name) { this.name = name } sayHi() { console.log('Hi') } } var p1 = new Person('小明') 'name' in p1 //true 'sayHi' in p1 //true for (var i in p1) { if (p1.hasOwnProperty(i)) { console.log('ownProperty:' + i) } else { console.log('prototypeProperty: ' + i) } } //'ownProperty: name' // prototypeProperty: sayHi
参照各类资料,结合本身的理解,在尽可能不涉及到继承的状况下,详细介绍了原型及其衍生应用。因为本人技术有限,若是有说得不对的地方,但愿在评论区留言。