JS学习笔记整理五 面向对象的程序设计
栏目: JavaScript · 发布时间: 5年前
内容简介:对象的属性类型包含:数据属性、访问器属性
对象的属性类型包含:数据属性、访问器属性
-
数据属性
- 可配置:Configurable,是否能用delete删除。能否修改属性特性。此值一旦设为false,就不可逆。
- 可修改:Writable,能否修改value值。
- 可枚举:Enumerable,能否通过for...in枚举。
- 值:Value
-
访问器属性
没有value和writable,多了一对get和set。
不管writable真假,只要configurable为真,通过Object.defineProperty就能把特性改为访问器属性。
Object.getOwnPropertyDescriptor(o,”prop”);
:读取属性特性。
Object.getOwnPropertyDescriptors(o);
:读取所有属性特性。
犀牛书里把访问器accessor称为存取器,我觉得这样更形象。可以使用直接量语法的扩展语法来定义属性,比如下面两种方法:
var o={ name:'tiedan', get ga(){return this.name}, set ga(value){this.name=value} } var p={name:'tiedan'}; Object.defineProperty(p,'ga',{ set:function(value){this.name}, get:function (){return this.name}}); console.log(Object.getOwnPropertyDescriptors(o)); console.log(Object.getOwnPropertyDescriptors(p)); /*{ name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get ga],set: [Function: set ga],enumerable: true,configurable: true } } { name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get],set: [Function: set],enumerable: false,configurable: false } }*/ //方括号里有细微区别。 复制代码
使用 Object.defineProperty
或者 Ojbect.defineProperties
,不显示定义configurable、writable、enumerable,则这三个特性默认值都为false
vue的核心实现就是利用 Object.definProperty
劫持属性的get和set特性,来实现双向绑定的。
Object.preventExtensions
对象不能添加新属性(可删除原有属性)。可通过Object.isExtensible来检测对象是否能扩展。
Ojbect.seal
可以密封对象,密封后的对象不能删除和添加属性。所有属性特性的[[configurable]]都为false。可通过Object.isSeal来检测对象是否为密封对象。
Object.freeze
可以冻结对象,比Object.seal更进一步,对象不能有任何变化。所有特性均为false。可通过Object.isFrozen来检测对象是否为冻结对象。
通过 Object.getOwnPropertyNames
看到的Object.prototype的属性,这部分属性都是可继承的。
[ 'constructor', //给所有对象的构造器属性,创建函数时,就会自动创建函数原型,和其constructor属性,如果重写原型,则继承这个 '__defineGetter__', //一些浏览器的非标准方法,在Object.defineProperty不支持的时候,可以尝试用这个定义属性的特性。 '__defineSetter__', //同上类似 'hasOwnProperty', //检测对象是否有自有的属性 '__lookupGetter__', //非标准方法,用来返回命名属性的Getter方法 '__lookupSetter__', //同上类似 'isPrototypeOf', //是否为检测对象的原型。其原理同instanceof一样都是查找原型链,只是instanceof后面是构造函数 'propertyIsEnumerable',//属性是否是可枚举的,这个和Object.getOwnPropertyDescriptor()里得到的enumberable属性是一样的 'toString', // 'valueOf', // '__proto__', //一般是内部属性,一个原型指针 'toLocaleString' ] // 复制代码
也可以通过 Object.getOwnPropertyNames
看看Object的属性和方法。
创建对象
犀牛书例子,最先由道格拉斯.克罗克福德提出,这个人在两本书里出现N多次。下面的是犀牛书p122的例子,红皮书类似的例子在p169。 Object.create()
创建一个新对象,参数是新对象的原型。create方法有两个参数,第二个参数与defineProperties的第二个参数一样。
function inherit(p){ if(p==null) throw TypeError(); //首先排除null,本身typeof就可能有null。Object.create是可以传null的 if(Object.create){ return Object.create(p); } var t=typeof p; if(t!="object"&&t!="function")throw TypeError;//不能是基本类型值 function a(){} a.prototype=p; return new a(); } 复制代码
new的实现大致如下:
o = {}; o.__proto__=f.prototype; f.apply(o,arguments); 复制代码
所以 new Object();
不如直接用 {}
执行快就是因为这个原因吗?
自定义的new如下:
function New(f) { //返回一个func return function () { o = {}; o.__proto__=f.prototype; f.apply(o, arguments);//继承父类的属性 return o; //返回一个Object } } 复制代码
通过 new fn();
或者 Object.create(p);
其实都是返回新对象的方法。
工厂模式
“生产对象”,所以一般直接调用。不使用new(使用也能返回正常对象)。
function Person(name,age){ var o=new Object(); o.name=name; o.age=age; return o; } 复制代码
弊端 :解决了对象创建问题,但没有解决对象识别问题
构造函数模式
function Person(name,age){ this.name=name; this.age=age; } 复制代码
没有显示创建对象,直接将属性和方法赋给this,(可以)没有return。通过new来调用。
实际上步骤:
var o={};//创建新对象 o.__proto__=fn.prototype;//对象的原型指针赋值 fn.apply(o,arguments);//this指向新对象,执行构造函数代码 return o;//返回新对象 复制代码
new fn()
就是类似上面的过程。构造函数fn的执行实际上就是给o对象赋值操作的过程。
弊端 :方法重复定义,即便放到全局也只适合对象调用,还容易污染全局作用域。
原型模式
function Person(){ } Person.prototype.name='tiedan'; Person.prototype.age='1'; 复制代码
对于原型有两种写法:
fn.prototype.p1=value1; fn.protptype.p2=value2; 复制代码
fn.prototype={ p1:value1, p2:value2 } 复制代码
第二种看起来更清楚,但是存在问题,因为这相当于重写原型对象。之前定义好的一些属性方法可能会丢失,constructor属性需要显示重新指向构造函数。要不就没了。在第二种重写原型之前不能先new fn,这会导致重写原型之后切断了构造函数fn和最初的原型之间的联系。因为prototype属性本身存储的就是一个指针。而对象和原型之间的联系仅靠__proto__存储的一个指针。相当于原型链出了问题,无法通过这个属性找到新的原型对象,获得新对象的属性和方法。说白了就是fn和obj指向了不同的原型对象。
function Person(name,age){ this.name=name; this.age=age; } Person.prototype.height=100; var p1=new Person("tiedan",36); Person.prototype={ constructor:Person, height:180 } var p2=new Person("someone",29); console.log(p1 instanceof Person);//false,因为其__proto__指向的已经不是最新的Person.prototype。经管如此,p1.constructor.prototype还是对的。 console.log(p1.height);//100 console.log(p2 instanceof Person);//false console.log(p2.height);//180 复制代码
弊端 :除了上面所说的,还有就是因为原型对象的属性和方法都是实例共享的。那么对于属性是引用类型,比如数组,大家也是共享的同一个引用对象。修改这类属性值,实际上会影响到所有实例。
建议属性值是引用类型的,不要用原型模式,除非你就需要这种共享引用类型的方式。
in
运算符有两种用法,一种用于for...in。另一种在单独使用时, in
操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中!
function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name)&&(name in object); } 复制代码
动态原型模式
function Person(name,age){ this.name=name; this.age=age; if(typeof this.sayName!='function'){ Person.prototype.sayName=function(){console.log(this.name);} } } 复制代码
相对比较完美的一种模式。
使用这种模式,同样不能用对象字面量重写原型。思考一下new的实现步骤,就能明白,构造函数里重写原型,则必然会在o对象创建之后切断与原有原型的联系。
寄生构造函数模式
function Person(name,age){ var o=new Object(); o.name=name; o.age=age; return o; } var tiedan=new Person('铁蛋',1); console.log(tiedan instanceof Person);//false 复制代码
这个模式和工厂模式的函数完全一样,只是这个直接通过new来调用。测试了一下,当做构造函数使用,末尾return会代替构造函数正常的return值。
弊端 :返回的对象和构造函数没什么关系。不能依赖instanceof来检测对象。(返回的实际上是里面的o类型)
稳妥构造函数模式
durable object持久对象 克罗克福德发明
function Person(name,age){ var o=new Object(); o.sayName=function(){ alert(name); }; return o; } var tiedan=Person('铁蛋'); 复制代码
类似于寄生构造函数模式。没有公共属性,不引用this对象。适合在安全环境或防止数据被其他应用程序改动时使用。1.不引this,2.不用new
其实是利用了闭包原理。即便对象被添加属性和方法,也无法篡改原始值。
继承
继承分为接口继承和实现继承
红皮书p162说es中无法实现接口继承,其实es4版本是实现了接口继承的(as3.0)。说不定哪天interface就从保留字变关键字了。
原型链
-
别忘记默认原型:
Object.prototype
-
确定原型和实例的关系,
instanceof
,isPropertyOf
-
谨慎的定义方法,方法覆盖,红皮书p166必须用SuperType实例替换SubType的原型后再定义原型方法,否则原型对象一重写就没了。
-
原型链的问题:
类似之前原型模式引用类型的问题。
SubType.prototype=new SuperType();
实际上就是重写原型。原型属性包含引用类型就容易出现意料之外的情况。
借用构造函数
constructor stealing 明明是偷非说是借~
function SubType(age){ SuperType.call(this,"tiedan"); this.age=age; } 复制代码
- 可向父类构造函数传参
- 问题:父类原型对子类不可见。
组合继承
将借用构造函数和原型链技术结合
-
借用构造函数得到实例属性
-
原型链技术继承原型属性和方法,同时还可以扩充自己的原型方法
SubType.prototype=new SuperType(); SubType.prototype.constructor=SubType; SubType.prototype.prop=… 复制代码
原型式继承
又是道格拉斯.克罗克福德... 提到了object(o)函数,犀牛书里名为inherit(p)
var person={}; var anotherPerson=Object.create(person); anotherPerson.prop=... //继承的引用类型会被实例共享 复制代码
Object.create在使用一个参数的时候,行为和object(o);相同,使用两个参数的时候,第二个参数和Object.defineProperties的第二个参数格式相同。以这种方式指定的属性会覆盖原型对象的同名属性。
其实inherit也可以加第二个参数,也就是做一个浅复制,这可以参看犀牛书的浅复制。extend方法。
var p2=Object.create({},{"hehe":{value:30 }}); console.log(p2);//{} //什么都看不见,因为和Object.defineProperties一样,enumerable不显示定义默认为false,所以看不到。 复制代码
寄生式继承
克罗克福德推广的
function createAnother(o){ var anotherPerson=Object.create(o); anotherPerson.sayHello=function(){console.log('hello');} return anotherPerson; } var anotherPerson=createAnother({}); anotherPerson.sayHello(); 复制代码
主要考虑对象而不是自定义类型和构造函数的情况下使用
寄生式继承的思路与寄生构造函数和工厂模式类似。创建一个用来封装继承过程的函数。在函数内部以某种方式增强对象,最后像自己做了所有工作一样返回对象。
寄生组合式继承
之前的组合式继承最大的问题是要调用两次父类构造函数,一次是借用构造函数,一次是重写子类原型。其实没必要调用两次构造函数。可以只调用一次借用构造函数,另一次寄生式new一个空构造函数。只为了复制父类原型属性和方法。
function SuperType(name){ this.name=name; this.colors=['red','green','blue']; } SuperType.prototype.sayName=function(){console.log(this.name);} function subType(name,age){ SuperType.call(this,name);//第二次调用 this.age=age; } SubType.prototype=new SuperType();//第一次调用 SubType.prototype.constructor=SubType; SubType.prototype.sayAge=function(){console.log(this.age);} 复制代码
改为:
function inheritPrototype(SubType,SuperType){ var prototype=Object.create(SuperType.prototype);//创建对象 prototype.constructor=SubType;//增强对象 SubType.prototype=prototype;//指定对象,依然重写原型啊... } function SuperType(name){ this.name=name; this.colors=['red','green','blue']; } SuperType.prototype.sayName=function(){console.log(this.name);} function SubType(name,age){ SuperType.call(this,name);//第二次调用 this.age=age; } inheritPrototype(SubType,SuperType);//第一次调用 SubType.prototype.sayAge=function(){console.log(this.age);} 复制代码
上面例子依然重写了原型啊。所以,顺序很重要。否则,最后两行换一下位置,sayAge就被盖掉了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
TensorFlow实战
黄文坚、唐源 / 电子工业出版社 / 2017-2-1 / 79
Google近日发布了TensorFlow 1.0候选版,这个稳定版将是深度学习框架发展中的里程碑的一步。自TensorFlow于2015年底正式开源,距今已有一年多,这期间TensorFlow不断给人以惊喜,推出了分布式版本,服务框架TensorFlow Serving,可视化工具TensorFlow,上层封装TF.Learn,其他语言(Go、Java、Rust、Haskell)的绑定、Wind......一起来看看 《TensorFlow实战》 这本书的介绍吧!