Javascript高级编程学习笔记(21)—— 对象原型
JS中对象相关的最重要的恐怕就是原型链了
原型链也是JS中对象继承的实现的基础
接昨天的文章,我们使用构造函数创建对象的时候仍然存在一些问题
那就是所有的实例没法共用一个函数
这样无疑会造成极大的内存浪费
那么解决的办法是什么呢?
那就是通过对象的原型来实现
原型链
在JS中我们创建的每一个函数都有一个 prototype(原型)属性
该属性是一个指针指向一个对象,这个对象的用途是包含由特定类型所有实例的共享属性和方法
这个对象也就是我们常说的原型对象
原型链就是对象实例和对象的原型对象组成的继承链
而JS中所有的对象都继承自Object类,所以原型链的顶端也就是Object构造函数的prototype 指向的对象
可以参考下图,可能画的有点丑请多见谅
对于这样一个继承链,在其它的面向对象的语言中,最下面的有关对象的部分应该是公有的,和类的概念十分类似
而不太好理解的,就是有关构造函数的那一部分
造成JS继承灵活多变的地方就在于这里,JS中的函数也是一个对象
所以函数也有自己的原型链,为了让构造函数和被构造函数创建的实例之间联系起来,所以函数除了有自己的 [[Prototype]] 属性之外,还有prototype属性指向被其构造的对象实例的原型对象
与此同时被函数prototype属性指向的对象,其constructor属性也会指向这个函数,这样就使构造函数和对象紧密地结合在一起
PS. 上文中的 [[Prototype]] 属性,就是对象的__proto__属性,但是这一属性只在浏览器中实现,JS的其他实现,对象不一定会有 __proto__属性
在这些没有__proto__ 的实现中虽然无法访问,但是可以通过 isPrototypeOf() 来检测对象之间的这种联系
而在ES5以上的脚本实现中,还支持一个Object.getPrototypeOf() 用于获取一个对象的原型对象
原型模式
在此基础上,我们可以发现,一个构造函数构造的所有函数的实例都有办法能够访问到原型对象
那么一类对象的共享属性的存放位置就显而易见了,那就是将其存放到原型对象上
以昨天的例子来说,使用原型模式创建对象如下:
function Person(){} var pPrototype = Person.prototype;
pPrototype.name = 'lhy';
pPrototype.job = 'font end';
pPrototype.age = 21;
pPrototype.sayName = function(){
alert(this.name);
} var person1 = new Person();
能够访问的原因在于,当代码读取对象的属性的时候 ,都会执行一次搜索,该搜索过程会从该对象实例开始
如果没有找到指定属性,则顺着原型链进行查找,直到Object.prototype为止
找到相应属性则返回,没找到则报未定义的错误
PS. 虽然我们可以通过实例对象访问原型对象的属性,但是我们无法通过实例对象来重写原型对象的属性
什么意思呢?就是说虽然我们可以通过实例对象的属性访问原型对象的某个属性,但是我们对实例对象对应属性的修改不会映射到原型对象上
如果我们在实例对象上设置了,一个本来只有原型对象上才有的属性,那么JS会为实例对象创建一个同名属性,并设置它的值
然后原型对象上的属性就被同名的属性屏蔽了,再也无法访问到原型对象上的这个属性,除非使用delete删除实例上的该属性
例如:
为了能够判断一个属性是来自于该实例,还是其原型对象
可以使用 hasOwnProperty() 方法来进行判断,对于来自实例的属性返回 true
Tips. in操作符不在for-in循环中单独使用的时候,用于判断对象上是否存在该属性,上面的方法与in操作符配合就可以判断属性到底来自实例,还是原型
function test(propertyName,obj){
if(obj.hasOwnProperty(propertyName)){
alert('来自实例');
)else if(propertyName in obj){
alert('来自原型');
}
}
Ps. for-in 循环会遍历对象上的所有可枚举属性,包括来自原型的属性,需要注意的是,如果原型上有个不可枚举的属性,而实例上有个与之同名的可枚举属性,那么实例上的这个属性会被遍历出来,不会受原型的影响
除此而外JS提供了Object.keys() 方法,可以获取到对象所有可枚举属性的集合
原型模式还有另一种简化写法
function Person (){}
Person.prototype = {name:"lhy",age:"21"};
但是这种写法存在一个问题,由于这里直接将一个对象指定为函数的原型对象
所以这里的函数原型的 constructor 属性也被改变了,这样我们就没法通过 constructor 来判断函数类型
那怎么办呢?
办法还是有的,那就是手动将要指定的原型对象的 constructor 设置为构造函数
不过要注意的是如果手动设置,那么该属性会被for-in 遍历出来,因为这种方式的就是为实例 创建一个 constructor 属性,以此来屏蔽不正确的constructor属性
所以最后还要将 constructor 属性特性描述符的 Enumerable 设为false,才能完美地解决
原型的动态性
从之前举的例子,和刚才简化的写法,我们不难发现原型是一个动态地对象
因为我们对原型对象的修改都会立即体现在实例对象上
同样的我们在操作原型时就要更加地小心,尤其是在重写原型对象时
拿上方的例子来说,虽然我们通过一些操作尽量减少了重写原型对象带来的差异,但还有一些是没法消除的
那就是我们重写原型对象时,会切断已有实例和重写的原型对象的实例,因为它们仍旧指向旧的原型对象
讲了这么多,那么原型模式是我们创建一类对象的最好的选择嘛?
虽然对于函数来说十分地合适,但对于一些引用类型的值可能就会有一些问题
比如我希望对象拥有自己独立的属性,那么原型模式可能就无法胜任了
那么怎么办呢?
构造函数与原型的组合模式
构造函数的缺点是没法共用数据,原型模式的缺点是没法有独立数据
那将他们组合起来不就行了吗
所以这种组合模式就产生了
function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
} Person.prototype.sayName = function(){
alert(this.name);
}
这种模式基本上就能满足我们平时的所有需求,所以这也是我们广泛使用的方式
动态原型模式
上面的方法虽然从功能上说,无可挑剔,但是暴露在外的独立的原型属性的声明,让追求极致的程序员们十分难受
所以动态原型模式就出现了
function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
if(this.sayName !== 'function'){
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
寄生构造函数模式
虽然动态原型模式,已经能够完美地处理绝大部分情况,但是有些特殊情况,仍会存在问题
就像我们不建议给原生的对象的原型添加属性,但有时候我们确实需要根据某个原生的对象来创建一类对象,那么怎么办呢?
所以就有了寄生构造函数模式,其原理就是我不在原生的原型上添加,但我可以使用工厂方法,对其构造的原型对象进行加工,从而得到我想要的对象
function specialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.myName = function(){
return 'lhy';
}
return values;
}
这种只是改良了的工厂方法,存在的弊端和工厂方法几乎一致,无法确定对象类型等
所以使用场景很少
稳妥构造函数
我们知道在一些安全环境下,我们没法使用this、new等来创建,或者需要防止数据被其他应用程序篡改时使用
pS. 不使用new 是指不用new调用构造函数
function Person(name, job, age ){
var o = new Object();
o.sayName = function(){
alert(name);
}
return o;
}
这样创建对象时传入的原始数据,只能通过返回的对象o 上面的方法进行访问,所以保证了原始数据的安全性
总的来说,创建一类函数使用组合模式的场景更为广泛,使用频率也更高
Javascript高级编程学习笔记(21)—— 对象原型的更多相关文章
- Javascript高级编程学习笔记(20)—— 创建对象
由于今天有点事,加上对象原型链的东西有点多,所以今天这篇就讲一个小的知识点吧 也算为明天的对象继承做铺垫 工厂模式 虽然使用对象字面量来创建一个对象十分地便捷,但是这个方法有一个显著的缺点 那就是如果 ...
- Javascript高级编程学习笔记(22)—— 对象继承
继承是所有面向对象的语言最让人津津乐道的概念 许多面向对象的语言都支持两种实现继承的方式: 1.接口继承 2.实现继承 由于ECMAScript中没有函数签名,所以自然也是不支持接口继承 所以JS中能 ...
- Javascript高级编程学习笔记(18)—— 引用类型(7)单体内置对象
什么是内置对象呢? js高级程序设计中给出的定义为:由ES规定不依赖于宿主环境的对象,这些对象在JS执行前就已经存在 前面我们介绍的引用类型都是内置对象 除了这些对象外ECMA还规定了两个单体内置对象 ...
- Javascript高级编程学习笔记(59)—— 事件(3)事件对象
事件对象 在触发DOM‘事件时,会产生一个事件对象 event 该对象包含着所有与事件有关的信息 所有浏览器都支持 event 对象但是支持的方式有所不同 DOM事件对象 兼容DOM的浏览器会将eve ...
- Javascript高级编程学习笔记(31)—— BOM(5)screen、history对象
screen对象 screen对象应该是BOM对象中最不常用的对象了 其主要用于提供客户端的显示能力信息 包括浏览器外部显示的信息,和像素的宽高等 这个对象的主要用于检测客户端能力,一般不会影响功能 ...
- Javascript高级编程学习笔记(30)—— BOM(4)navigator对象
window对象作为浏览器的全局对象.location对象保存了页面的url信息 那么navigator对象又有什么作用呢? navigator对象 该对象最早由 Netspace Navigator ...
- Javascript高级编程学习笔记(29)—— BOM(3)location对象
在JS中location是一个神奇的对象 它既是window对象的属性,也是document对象的属性 它的作用主要在于保存当前文档页面的信息,以及将 url 解析为独立的片段 location对象属 ...
- Javascript高级编程学习笔记(28)—— BOM(2)window对象2
今天讲一下window对象和浏览器导航,弹窗等有关的内容 导航和打开窗口 window.open() 用于导航到某个特定 url 该方法接收四个参数 1.url 2.窗口目标(当页面中有多个框架fra ...
- Javascript高级编程学习笔记(27)—— BOM(1)window对象1
ECMAScript是JS的核心 但是对于在浏览器中运行的JS,BOM显然才是真正的核心 我们知道JS是由三个部分组成的 BOM.DOM.ECMAScript 之前的文章我们主要介绍的是ECMAScr ...
随机推荐
- 【CSS】Sticky Footer 布局
什么是 Sticky Footer 布局? Sticky Footer 布局是一种将 footer 吸附在底部的CSS布局. footer 可以是任意的元素,该布局会形成一种当内容不足,footer ...
- 路飞ORM练习
# a.查看所有学位课并打印学位课名称以及授课老师 # degree_list = DegreeCourse.objects.all().values('name', 'teachers__name' ...
- 过滤器(Filter)与拦截器(Interceptor )区别
目录 过滤器(Filter) 拦截器(Interceptor) 拦截器(Interceptor)和过滤器(Filter)的区别 拦截器(Interceptor)和过滤器(Filter)的执行顺序 拦截 ...
- cookie设置和清除,解决跨目录读取不到cookie值
cookies.setCookie("UserType", result.UserType, null, '/'); cookies.deleteCookie("User ...
- Appium+Python自动化 4 -appium元素定位
appium定位app上的元素方式 在前面文章中有介绍,(通过UIautomator工具查看app元素) appium定位app上的元素,可以通过id,name,class这些属性定位到 1.id 定 ...
- H5canvas画类似心电图
HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像,我们可以使用canvas来绘制类似心电图的东西. 效果图如下: <!DOCTYPE html> <ht ...
- django中的modelform和modelfoemset
一. ModelForm ModelForm是根据Model来定制的Form 二. ModelForm的创建 from django import forms from app import mode ...
- metasploit渗透测试魔鬼训练营环境
metasploitable winxpensp2 owasp_broken_web_apps win2k3 metasploitable 链接:https://pan.baidu.com/s/1oZ ...
- 防范 SQL 注入攻击
防范 SQL 注入攻击 我们执行的 SQL语句中包含变量,执行的时候会直接把变量内容替换进去.而如果攻击者在输入框中输入一些危险的字符(通常包含 SQL 注释符 --,以及其他预先精心设置的内容), ...
- 默认空间和webapps下项目部署
用eclipse默认的工作区和webapps的细节 用{WORKSPACE}表示你的eclipse的工作空间根目录,然后你打开 {WORKSPACE}\.metadata\.plugins\org.e ...