位置: 编程技术 - 正文
1、对象再认识
(1)对象属性和特性
什么是属性(Property),什么是特性(Attribute),这有什么区别?我不想也不会从语义学上去区分,对于这系列文章来说,属性就是组成对象的一个部分,广义上也包括对象的方法,而特性则是指被描述主体所具有的特征,换句话说,属性是我们可以通过编码来访问的具体存在,而特性则主要是为了便于理解概念的抽象存在,当然,特性也可以通过相应的属性来具体外化。这一小节所讲的对象属性的特性就是对对象属性特征的一个描述,主要来自于ECMA-规范的第5版,该规范使用两个中括号的形式来描述不能直接访问的内部特性。
A、属性类型(先给属性分下类):
数据属性:直接访问属性值的属性 访问器属性:通过getter/setter方法来访问属性值的属性 内部属性:不能通过代码直接访问的属性,只是为了规范说明目的而存在,在规范中也使用两个中括号的形式来描述B、对象内部属性
内部属性不能通过代码直接访问,它主要是为了描述规范,也是给ECMAScript实现者参考的,而对于开发者来说,了解这些可以便于理解一些内部机制。比如在给一个属性赋值时,在实现中会调用[[Put]]内部方法,而读取一个属性值时,则调用[[Get]]方法。
所有对象公共的内部属性个别对象特有的内部属性名称规范名称规范对象[[Prototype]]Object/Null[[PrimitiveValue]]primitiveBoolean|Date|Number|String[[Class]]String[[Construct]]SpecOp(a List of any) → Objectnew[[Extensible]]Boolean[[Call]]SpecOp(any, a List of any) → any|Referencecall[[Get]]SpecOp (propName) →any[[HasInstance]]SpecOp(any) → BooleanFunction[[GetOwnProperty]]SpecOp (propName) →Undefined|Property Descriptor[[Scope]]Lexical EnvironmentFunction[[GetProperty]]SpecOp (propName) →Undefined|Property Descriptor[[FormalParameters]]List of StringsFunction[[Put]]SpecOp (propName, any, Boolean)[// 创建一个包含一个默认属性job的对象(job属性可以修改、删除、在for-in中枚举) var person = {job:'it'}; // 添加一个不能被修改、删除的name属性 Object.defineProperty(person, 'name', { value:'linjisong',//这里的配置属性和上面特性列表中的配置属性一致 enumerable:true }); // 定义多个属性(数据属性year和访问器属性age) Object.defineProperties(person, { year:{ value : , configurable:true, writable:true }, age:{ get : function(){ return this.year-; } } }); var job = Object.getOwnPropertyDescriptor(person, 'job'); console.info(job.configurable);//true,直接添加属性时默认为true var name = Object.getOwnPropertyDescriptor(person, 'name'); console.info(name.configurable);//false,使用属性定义方法添加属性时默认为false console.info(person.name);//linjisong person.name = 'oulinhai';//由于不能修改,所以值不会改变,在严格模式下会抛出异常 console.info(person.name);//linjisong person.year = ; console.info(person.year);// console.info(person.age);//,在修改year的同时,也修改了age属性defineProperties()定义一组属性(1)目标对象
(2)多个属性描述符组成的一个对象
getOwnPropertyDescriptor()获取属性的特性(1)目标对象
(2)属性的名字
(3)返回一个包括了属性特性的对象
注:这些方法设置或获取的属性特殊和属性的类型有关,比如数据属性只能设置[[Confirurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。
(2)防篡改对象
所谓防篡改对象,就是给对象一定级别的保护以防止在这个级别上对对象的变更,在ES5规范中,定义了依次升高的三种保护级别:
保护级别描述操作方法判断方法说明不可扩展不能给对象添加新属性和方法,但可以修改已有属性和方法preventExtensions()isExtensible():不能扩展时返回false 密封不可扩展,并且已有成员的[[Configurable]]设置为false,不能删除属性,但可以修改属性值seal()isSeal():被密封时返回trueisSeal()为true时一定有isExtensible()为false冻结密封,其[[Writable]]设置为false,但如果定义了[[Set]],访问器属性仍然可写freeze()isFrozen():被冻结时返回trueisFrozen()为true时一定有isSeal()为true,isExtensible()为false
注:一旦定义成了防篡改对象,就不能撤销。
(3)对象的其它方法
名称描述create(prototype[,descriptors])创建一个具有指定原型且可选择性地包含指定属性的对象getOwnPropertyNames(object)返回对象的属性(方法)的名称getPrototypeOf(object)返回对象的原型keys(object)返回对象的可枚举属性(方法)的名称
这里的create(prototype[,descriptors])是一个非常有意思的方法,规范中这样描述它的行为:[code] ①如果prototype不是Null或Object,抛出TypeError异常 ②var obj = new Object() ③设置obj的内部属性[[Prototype]]为prototype ④如果descriptors存在且不为undefined,使用Object.defineProperties(obj,descriptors)来添加属性 ⑤返回obj 由于一般对象的[[Prototype]]不能直接访问,可以使用函数来进行下面模拟实现: 测试一下: 这里实现了一个简单的继承,这也引出下一个主题。 2、原型对象 (1)原型与原型链 每个对象都有一个原型对象,而原型对象本身也是一个对象,也有自己的原型对象,这样就形成了一个原型链直至null对象。对象的内部属性[[Prototype]]指向的就是对象的原型对象,而Object.prototype的原型对象为null。 (2)属性查找 在访问一个对象的属性(方法)时,引擎会先查找对象本身有没有这个属性,如果有,返回这个属性值,如果没有,则查找对象的原型是否有这个属性,如果有,返回,如果没有就继续查找原型对象的原型直至最后一个原型对象。 注意区分属性查找和和前面说过的标识符查找的异同。属性查找是沿着原型链,标识符查找是沿着作用域链,但都有一个逐级查找的过程。 (3)对象的原型对象[[Prototype]]与函数的原型属性prototype 每一个对象都有一个原型对象,在规范中使用[[Prototype]]来表示,这个对象一般不能直接访问,但可以通过getPrototypeOf()这个方法来获取,而在Firefox中还可以通过__proto__直接访问,来验证一下: 每一个函数都有一个属性prototype,这个属性是在函数定义过程中添加的,它指向的对象就是所有使用该函数创建的实例对象的原型对象。 当然,fn本身也是一个对象,也有自己的原型对象,它的原型对象就是Function的属性prototype了(fn.__proto__===Function.prototype)。 我们知道,每一个对象都可以访问一个属性constructor,指向创建这个对象的函数(构造函数),实际上,constructor属性只不过是构造函数的原型对象的一个属性而已,因此通过构造函数创建的实例都能够访问constructor。 函数的原型对象具有动态性,即便先创建实例,后修改原型对象,也还是能够通过实例访问到对原型对象所做的变更。
3、创建对象
创建方式示例说明传统方式var person = new Object(); person.name = 'linjisong'; person.job = 'it';传统方式创建对象容易产生大量重复的代码对象字面量var person = { name : 'linjisong', job : 'it' };使用对象字面量创建简洁明了,非常适合创建作为函数实参的对象工厂模式function createPerson(name, job){ var o = new Object(); o.name = name; o.job = job; return o; } var person = createPerson('linjisong','it');
1、工厂模式能有效解决重复代码问题。
2、但是不能判定对象的类型
构造函数模式function Person(name, job){ this.name = name; this.job = job; this.getName = function(){ return this.name; } } var person = new Person('linjisong','it');构造函数模式能解决重复代码问题,也能够判定对象的类型
但是这种模式下创建的每个实例都有一份属性和方法的Copy
对于方法来说,每个实例都保存一份是没有必要的
使用new调用构造函数的内部步骤:
(1)创建一个新对象
(2)将构造函数的作用域赋给新对象(构造函数内this指向新创建对象)
(3)执行构造函数中的代码
(4)返回新对象
原型模式function Person(){} Person.prototype.name = 'linjisong'; Person.prototype.job = 'it; Person.prototype.getName = fucntion(){ return this.name; }; var person = new Person();原型模式能够解决构造函数模式的方法实例有多个副本的问题
但是同时每个实例的属性也共享了,对于引用类型的属性来说
这会导致非常严重的问题,修改一个实例的属性会导致另一个实例也修改
而且也不能接受参数
function Angle(){}; Angle.prototype.coordinate = [0,0]; var a1 = new Angle(); var a2 = new Angle(); a1.coordinate[0] = 1; console.info(a2.coordinate);//[1,0]修改a1会导致a2变更组合构造原型模式function Person(name, job){ this.name = name; this.job = job; } Person.prototype.getName = fucntion(){ return this.name; }; var person = new Person('linjisong','it');结合构造函数模式和原型模式
使用构造函数模式创建属性,每个实例保存一份
使用原型模式共享方法,所有实例共享保存一份
这是目前使用最广泛的对象创建方式
动态原型模式function Person(name, job){ this.name = name; this.job = job; if(!Person.prototype.getName){ Person.prototype.getName = fucntion(){ return this.name; }; } } var person = new Person('linjisong','it');这种模式实际上是对于不习惯将构造函数和原型分离而引入的
在判断的时候,可以只判断其中一个属性
寄生构造函数模式function Person(name, job){ var o = new Object(); o.name = name; o.job = job; o.getName = fucntion(){ return this.name; }; return o; } var person = new Person('linjisong','it');工厂模式不使用new,寄生构造函数模式使用new操作符
构造函数模式不返回,寄生构造函数模式返回对象
不能使用instanceof判断类型
稳妥构造函数模式function Person(name, job){ var o = new Object(); o.getName = fucntion(){ return name; }; return o; } var person = Person('linjisong','it');稳妥对象:不使用this和new
稳妥构造模式类似寄生构造模式,但只能通过提供的方法访问成员
不能使用instanceof判断类型
各种创建对象的模式需要根据具体情况来看,最常用的还是对象字面量和组合构造原型方式。
4、继承
在ECMAScript中,没有接口继承,只有实现继承,这些继承主要是通过原型链来实现的。像对象创建一样,下面也通过一张表格来浏览一下一些实现继承的方法。
继承方式示例说明原型链function Square(){//正方形 this.width = ;//边长 this.coordinate = [0,0];//左上顶点的坐标 } Square.prototype.getArea = function(){//计算面积 return this.width * this.width; }; function ColorSquare(){//有颜色的正方形 this.color = 'red'; } ColorSquare.prototype = new Square();//实现了继承 ColorSquare.prototype.getColor = function(){//获取颜色 return this.color; } var cs = new ColorSquare(); console.info(cs.width);// console.info(cs.getArea());// console.info(cs.color);//red console.info(cs.getColor());//red
1、通过修改子类型创建函数的原型实现继承。
2、通过原型给子类型添加新方法时,一定要在替换子类型原型之后添加,而后也不能通过对象字面量修改子类型的原型。
3、可以通过两种方法确定原型和实例之间的关系:只要实例原型链中出现过构造函数fn,都返回true
(1)instance instanceof fn
(2)fn.prototype.isPrototype(instance)
4、使用原型链继承时,创建子对象时无法传递参数。
5、引用类型的父类属性会被所有子类型实例共享从而产生问题:
修改一个子类型实例的引用类型属性会导致其它所有子类型实例相应的修改
var cs2 = new ColorSquare(); console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs2.coordinate);//[0,1],修改cs会导致cs2也修改借用构造函数function Square(){//正方形 this.width = ;//边长 this.coordinate = [0,0];//左上顶点的坐标 } Square.prototype.getArea = function(){//计算面积 return this.width * this.width; }; function ColorSquare(){//有颜色的正方形 Square.call(this);//实现继承 this.color = 'red'; } var cs = new ColorSquare(); var cs2 = new ColorSquare(); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,0],互相独立,修改cs不影响cs2 try{ console.info(cs.getArea());//异常,不能访问父类原型中方法 }catch(e){ console.info(e);//TypeError }1、使用借用构造函数时,可以在call调用时传递参数。
2、同时也不存在引用类型共享的问题。
3、借用构造函数的缺点是,子类不能访问父类原型中定义的方法
组合继承function Square(){//正方形 this.width = ;//边长 this.coordinate = [0,0];//左上顶点的坐标 } Square.prototype.getArea = function(){//计算面积 return this.width * this.width; }; function ColorSquare(){//有颜色的正方形 Square.call(this);//创建子类实例时,第二次调用父类构造函数 this.color = 'red'; } ColorSquare.prototype = new Square();//第一次调用 ColorSquare.prototype.getColor = function(){//获取颜色 return this.color; } var cs = new ColorSquare(); var cs2 = new ColorSquare(); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,0],互相独立,修改cs不影响cs2 console.info(cs.getArea());//,可以访问1、组合继承也称为伪经典继承,是将原型链和借用构造函数两种方式结合起来的继承方式。
2、基本思想是:
(1)使用原型链实现对原型属性和方法的继承
(2)使用借用构造函数实现对实例属性的继承
3、组合继承避免了原型链和借用构造函数的缺点,融合了它们的优点,是最常用的继承方式。
4、组合继承的缺点是需要调用两次父类的构造函数
原型式继承function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } var square = { width:, coordinate:[0,0] }; var cs = create(square); var cs2 = create(square); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,1],和原型链一样,会有共享问题1、这种方式实际上就是前面说的模拟ES5中create函数来实现继承。
2、ES5及前面模拟的create还可以接受另外的属性描述参数。
3、和原型链与借用构造函数不同的是,这种方式需要先有一个对象,然后直接创建子对象。
前者是构造函数的继承,而后者是对象实例的继承。
4、和使用原型链继承一样,也会有引用类型实例属性的共享问题。
寄生式继承function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } var square = { width:, coordinate:[0,0] }; function colorSquare(original){ var s = create(original); s.color = 'red'; return s; } var cs = colorSquare(square); console.info(cs.width);// console.info(cs.coordinate);//[0,0]1、首先,这里的create函数不是必需的,任何返回新对象的函数都可以。
2、其次,这种模式也有引用类型实例属性共享的问题。
3、这种方式,可以看成将上面的对象继承包装成构造函数。
寄生组合式继承function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } function inherit(sub, sup){ var prototype = create(sup.prototype); prototype.constructor = sub; sub.prototype = prototype; } function Square(){//正方形 this.width = ;//边长 this.coordinate = [0,0];//左上顶点的坐标 } Square.prototype.getArea = function(){//计算面积 return this.width * this.width; }; function ColorSquare(){//有颜色的正方形 Square.call(this); this.color = 'red'; } inherit(ColorSquare, Square); ColorSquare.prototype.getColor = function(){//获取颜色 return this.color; } var cs = new ColorSquare(); console.info(cs.width);// console.info(cs.getArea());// console.info(cs.color);//red console.info(cs.getColor());//red var cs2 = new ColorSquare(); console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs2.coordinate);//[0,0]1、这种方式只调用了一次父类构造函数,从而避免了在子类型的原型对象上创建不必要的属性。
2、能够保证原型链不变,从而可以正常使用instanceof和isPrototypeOf()。
推荐整理分享JavaScript高级程序设计(第3版)学习笔记10 再访js对象(javascript高级程序设计第三版),希望有所帮助,仅作参考,欢迎阅读内容。
文章相关热门搜索词:javaScript高级程序设计有几版,javascript高级程序设计pdf百度云,javaScript高级程序设计有几版,javascript高级程序设计电子版,javaScript高级程序设计,javascript高级程序设计第六版,javascript高级程序设计电子版,javascript高级程序设计第六版,内容如对您有帮助,希望把文章链接给更多的朋友!
JavaScript高级程序设计(第3版)学习笔记 内建js对象 内建对象就好比是JDK中的类库,开发者可以直接拿来使用,这极大的方便了常见的编程任务。这篇文章就来浏览一下主要的内建对象,当然,我们并不
JavaScript高级程序设计(第3版)学习笔记 js正则表达式 需要指出的是,这里只是总结了正则表达式的常用的且比较简单的语法,而不是全部语法,在我看来,掌握了这些常用语法,已经足够应对日常应用了
JavaScript高级程序设计(第3版)学习笔记 ECMAScript5新特性 接下来应该是BOM和HTML5了,但是鉴于ECMAScript5相对于ECMAScript3的新变化比较多,而且这些变化也非常的有意思,因此在这篇文章中再将我认为的有意思的
上一篇:JavaScript高级程序设计(第3版)学习笔记9 js函数(下)(javascript高级程序设计第五版 pdf下载)
友情链接: 武汉网站建设