原创不易,如需转载请【联系作者】或【署名作者并注明文章出处】
历史
背景
- 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。
简单设计
- 工程师Brendan Eich负责开发这种新语言,他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。
万物皆对象
- 1994年正是面向对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。Brendan Eich无疑受到了影响,Javascript里面所有的数据类型都是对象(object)
继承与不要类
- 如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
- 考虑不引入类,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度 (在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)
原型与构造函数
- Brendan Eich把new命令引入了Javascript,用来从原型对象生成一个实例对象,C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
new运算符的缺点
- 无法共享属性和方法,每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
prototype属性的引入
- 为了解决new的缺点,Brendan Eich决定为构造函数设置一个prototype属性。
- 这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
- 实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
原理
继承与链式查找
- 当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例
构造函数、原型对象、实例
- 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
原理的边界理解
原型做什么
- 原型是为了共享多个对象之间的一些共有特性(属性或方法)
最顶层的原型对象
- 这个对象就是Object.prototype,这个对象中保存了最常用的方法,如toString、valueOf、hasOwnProperty等,因此我们才能在任何对象中使用这些方法
构造函数
- 函数的构造调用(注意,我们不把它叫做构造函数,因为JavaScript中同样没有构造函数的概念,所有的函数都是平等的,只不过用来创建对象时,函数的调用方式不同而已)
属性遮蔽 (property shadowing)
- 对象自身的属性会优先与原型链上的同名属性被访问到,比如:o.b, b是o的自身属性吗?是的,原型上也有一个'b'属性,但是它不会被访问到。
this指向
- 当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。注意闭包与箭头函数
创建非拷贝
- JavaScript的对象创建不存在拷贝,对象的原型实际上也是一个对象,它和对象本身是完全独立的两个对象
委托非继承
- 继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
关键属性介绍
prototype
- 每个构造函数都有一个prototype原型对象
- prototype是函数才具有的属性
- 被构造函数创建的实例对象的 [[Prototype]] 指向 func 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。
- 默认情况下,new 一个函数创建出的对象,其原型都指向这个函数的prototype属性。
proto 与getPrototypeOf+setPrototypeOf
- 每个对象都有一个__proto__属性,并且指向它的prototype原型对象
- someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__。
__proto__属性指向问题
字面量创建的对象
- 创建: var a = {}
- 指向: obj.__proto__ === Object.prototype; a.__proto__ === a.constructor.prototype
构造器方式
- 创建: var A = function (); var a = new A()
- 指向: a.__proto__ === A.prototype; a.__proto__ === a.constructor.prototype
Object.create方式
- 创建: var a1 = {} var a2 = Object.create(a1)
- 指向: a2.__proto__ === a1; a.__proto__ !== a.constructor.prototype
prototype与__proto__
- prototype是函数才具有的属性
- __proto__是每个对象都具有的属性,但是不是规范属性,只有部分浏览器实现了,标准属性是someObject.[[Prototype]]
constructor
- 原型对象上默认有一个属性constructor,该属性也是一个指针,指向其相关联的构造函数。
- prototype原型对象里的constructor指向构造函数本身
hasOwnProperty 与 Object.keys()
- JavaScript 中唯一2个处理属性并且不会遍历原型链的方法。
边界情况
- 对于JavaScript中的内置对象,如String、Number、Array、Object、Function等,因为他们是native代码实现的,他们的原型打印出来都是ƒ () { [native code] }
- 内置对象本质上也是函数,所以可以通过他们创建对象,创建出的对象的原型指向对应内置对象的prototype属性,最顶层的原型对象依然指向Object.prototype。
- Object.create(null) 创建出的对象,不存在原型。 Object.create(null).__proto__ == undefined
创建对象的方法
1.字面量方式, var obj = {};
- 当通过字面量方式创建对象时,它的原型就是Object.prototype。
- 虽然我们无法直接访问内置属性[[Prototype]],但我们可以通过Object.getPrototypeOf()或对象的__proto__获取对象的原型。
- Object.getPrototypeOf(obj) === Object.prototype;
- obj.__proto__ === Object.prototype;
2.函数的构造调用, var obj = new f();
- javaScript在定义一个函数时,同时为这个函数定义了一个 默认的prototype属性,所有共享的属性或方法,都放到这个属性所指向的对象中.
- 通过一个函数的构造调用创建的对象,它的原型就是这个函数的prototype指向的对象。
- obj.__proto__ === f.prototype
3.Object.create(), var obj2 = Object.create(obj);
- 这个方法会以你传入的对象作为创建出来的对象的原型。
- 这种方式还可以模拟对象的“继承”行为。
- obj2.__proto__ === obj;
应用
实现继承
构造函数的继承
- 1、 构造函数绑定,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:Animal.apply(this, arguments);
- 2、 prototype模式,"猫"的prototype对象,指向一个Animal的实例; 【注意!!!如果替换了prototype对象,那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。不然容易造成原型链紊乱】Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;
3、 改进prototype模式,让Cat()跳过 Animal(),直接继承Animal.prototype;
- Cat.prototype = Animal.prototype; Cat.prototype.constructor = Cat;
- 优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。
- 缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
4、 利用空对象作为中介
- var F = function(){}; F.prototype = Animal.prototype;
- Cat.prototype = new F(); Cat.prototype.constructor = Cat;
- F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
5、 拷贝继承,把父对象的所有属性和方法,拷贝进子对象
function extend2(Child, Parent) {
- var p = Parent.prototype; var c = Child.prototype;
- for (var i in p) { c[i] = p[i]; }
- c.uber = p; }
非构造函数的继承
object()方法,使用prototype链
- function object(o) { function F() {} F.prototype = o; return new F(); }
- 把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起
- 浅拷贝, 把父对象的属性,全部拷贝给子对象,也能实现继承
- 深拷贝, 递归调用"浅拷贝"
属性方法的全局作用域
- vue.prototype.xxx
如何体现原型的扩展性-插件机制 JQ / zepto
思考
- 为何要把原型方法放在$.fn?:扩展插件。(第一,构造函数的 prototype 肯定得指向能扩展的对象;第二,$.fn 肯定得指向能扩展的对象)
好处:
- 1,只有$会暴露在window全局变量
- 2,将插件扩展统一到 $.fn.xxx 这一个接口,方便使用
instanceOf
- 遍历原型链查找是否是某个原型对象的实例
this
this指向问题
- 当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。注意闭包与箭头函数
- class中的this指向
bind
- 改变this指向,并返回一个函数
通过原型链取到某些特殊的方法实现拼接参数
- 思路:通过构造函数取到特殊方法实现拼接参数+apply
- Array.prototype.slice.call(arguments);
- call / apply
new // new运算符及背后工作原理
- new后跟构造函数
- 1,一个新对象被创建,它继承自func.prototype(构造函数的原型对象)
- 2,构造函数func被执行,执行的时候,相应的传参会被传入,同时上下文(this)会被指定为这个新实例
- 3,如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果,如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象
发散思考
primitive types
问题: 以下两种写法有什么区别
- var a = 1; a.__proto__; a.constructor
- 1.__proto__ ???
- JavaScript中对象转换为原始值的步骤https://blog.csdn.net/u010533180/article/details/54427200
参考链接
- 【手动加精】https://github.com/mqyqingfeng/Blog
- 【手动加精】https://github.com/mqyqingfeng/Blog/issues/2
- https://developer.mozilla.org...
- http://www.ruanyifeng.com/blo...
- https://zhuanlan.zhihu.com/p/...
- https://zhuanlan.zhihu.com/p/...
- https://www.cnblogs.com/liyus...
- https://www.cnblogs.com/sarah...
- https://segmentfault.com/a/11...
【By: 360手机助手团队-XTeam】
原创不易,如需转载请【联系作者】或【署名作者并注明文章出处】
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。