胡说-JavaScript函数类型

回到了自己的家乡,期待中有感觉到了很多的陌生,一个有“故事”的环境中,你我是否“孤独”!函数的类型在我看来function共有三种类型,作为对象使用,处理业务以及穿件object的实例对象。跟这三种用法相对应的有三种子类型,分别是对象的属性、变量(包括参数)和创建出来的object类型实例对象的属性。这三种子类是相互独立的,而且也很容易区分。但是,我们刚刚接触的时候很容易混淆。1.function 作为对象来使用这种情况下,function对象的子类型就是对象自己的属性,这时通过操作符“.”(或者方括号操作符)使用,示例如下:function book(){}book.price=999;book[“getPrice”]=function(){ return this.price;}console.log(book.getPrice()); //输出结果:999我很少碰到function来作为object类型的对象来使用。2.funciton 用于处理业务这种情况下,function的子类型就是自己定义的局部变量(包括参数),这时的变量实在方法被调用时通过变量作用域链来管理的。关于变量我之前的文档中有涉及到,这里就不过多的说明了。3.function 用于创建对象这种情况下,对应的子类型是使用function创建实例对象的属性(很常用),主要包括在function中通过this添加属性,以及创建完成之后实例对象自己添加的属性。另外,还可以调用function的prototype属性对象所包含的属性,示例如下:function Car(color,displacement){ this.color = color; this.displacement = displacement;}Car.prototype.logMessage = function(){ console.log(this.color+","+this.displacement); };var car = new Car(“yellow”,“2.4T”);//看看是不是类似构造函数?哈哈这个例子中创建的car对象就包含有color和displacement两个属性,而且还可以调用Car.prototype的logMessage方法。当然,创建完之后还可以使用点操作符给创建的car对象添加或者修改属性,也可以使用delete删除其中的属性,示例如下:function Car(color,displacement){ this.color = color; this.displacement = displacement;}//所有创建出来的car都有该方法Car.prototype.logMessage = function(){ console.log(this.color+","+this.displacement); };var car = new Car(“yellow”,“2.4T”);//看看是不是类似构造函数?哈哈//给car对象添加属性car.logColor = function(){ console.log(this.color); };//完成调用测试car.logColor();//输出结果: yellowcar.color = “red”;car.logColor();//输出结果:reddelete car.color;//删除属性car.logColor();//输出结果:undefined代码分析:在创建完car对象之后,又给它添加了logColor的方法,可以打印car的color属性。添加完logColor方法后直接调用就可以打印出car原来的color属性值(yellow)。然后,将其修改为red,在打印出了red。最后,使用delete删除car的color的属性,这时在调用logColor方法会打印出undefined其实跟我的注释说明一致!!!三种子类型的关系function的三种子类型是相互独立的,他们只能在自己所对应的环境中使用而不能相互调用,示例如下:function log(msg){//第二种业务处理 console.log(msg);}function Bird(){ var name = “kitty”; this.type = “pigeon”; this.getName = function(){ return this.name;//创建的对象没有name属性 }}Bird.color = “white”;//第一种object类型的对象Bird.getType = function(){//第一种object类型的对象 return this.type;};Bird.prototype.getColor = function(){//第三种创建对象 return this.color;}var bird = new Bird();log(bird.getColor());// undefinedlog(bird.getName()); // undefinedlog(bird.getType()); // undefinedBird 作为对象时包含 color 和 getType 两个属性,作为处理业务的函数是包含一个名为name的局部变量,创建的实例对象bird具有type和getName两个属性,而且还可以调用Bird.prototype的getColor属性,getColor也可以看作bird的属性。用 法子 类 型对象(Bird)color 、getType处理业务(Bird方法)name创建实例对象(bird)type、getName、(getColor)每种用法中所定义的方法只能调用相对应所对应的属性,而不能交叉调用,从对应关系中可以看出,getName、getColor和getType三个方法中无法获取到值,大家再仔细分析一下!另外,getName和getColor是bird的属性方法,getType是Bird的属性方法,如果用Bird对象调用getName或getColor方法或者使用bird对象调用getType方法都会抛出找不到的错误。三种子类型不可以相互进行调用之外,还有一种情况也非常重要:那就是对象的属性并没有继承的关系。function obj(){}obj.v=1;obj.func = { logV : function(){ console.log(this.v); }}obj.func.logV();代码分析:这个例子中的obj是作为对象使用的,obj是有一个属性v和一个对象属性func,func对象中又有一个logV方法,logV方法用于打印对象的v属性。这里需要特别注意:logV方法打印的是func对象的v属性,但是func对象中并没有v属性,所以最后结果是undefined。这个例子中,虽然obj对象中包含v属性,但是由于属性不可以继承,所以obj的func属性对象中的方法不可以使用obj中的属性v.请大家一定要记住,并且不要和prototype的继承以及变量作用域链相混淆关联三种子类型三种子类型本来是相互独立、各有各的使用环境的,但是,有一些情况下需要操作不属于自己所对应环境的子类型,这时就需要使用一些技巧来实现了。约定如下function 作为对象使用时记作 O(Object)作为函数使用时记作 F(Function)创建出来的对象实例记作 I(Instance)op(object property)v(variable)ip(instance property) opvipO直接调用在函数中关联到O的属性不可调用F使用O调用直接调用不可调用I使用O调用在函数中关联到I的属性直接调用纵向表头表示function的不同用法横向表头表示三种类型,表格的主体表示在function相应用法中调用各种子类的方法。因为function创建的实例对象在创建之前还不存在,所以function作为方法(F)和作为对象(O)使用时无法调用function创建的实例对象的属性(ip)。调用参数可以在函数中将变量关联到相应的属性,调用function作为对象(O)时的属性可以直接使用 function 对象来调用function log(msg){ console.log(msg);}function Bird(){ //私有属性 var name = “kitty”; var type = “pigeon”; //将局部变量name关联到新创建的对象的getName,setName属性方法 //闭包可以使用局部变量 //公有属性 this.getName = function(){ return name; } this.setName = function(n){ name = n; } //将局部变量type关联到Bird对象getType属性方法 //静态属性 Bird.type = function(){ return type; } //在业务处理中调用Bird对象的color属性 log(Bird.color);//输出结果: white,F调用op }Bird.color = “white”;// 代表 O//在创建出的实例对象中调用Bird对象的color属性Bird.prototype.getColor = function(){//I return Bird.color;//OP}var bird = new Bird(); // 创建实例 Ilog(bird.getColor()); // 输出结果:white , I 调用 oplog(bird.getName());// 输出结果:kitty , I 调用 v 局部变量log(Bird.getType());// 输出结果:pigeon , O 调用 v 局部变量bird.setName(“petter”); // I 调用 vlog(bird.getName());// 输出结果:petter , I 调用 v 局部变量好好分析上述的代码,非常经典的,了解三种子类型的不同环境用法中交叉调用的方法附录:“公有属性” “私有属性” 和 “静态属性”上面的示例中我们涉及到了公有属性、私有属性和静态属性的说明,由于JS并不是基本类而是基于对象的语言,因此JS本身并没有这些概念。公有属性:一般指使用function对象创建出object实例对象所拥有的属性。私有属性:一般指function的内部变量静态属性:一般指function对象自己的属性 ...

January 26, 2019 · 1 min · jiezi

关于属性描述符PropertyDescriptor

本文首发于本博客 猫叔的博客,转载请申明出处前言感谢GY丶L粉丝的提问:属性描述器PropertyDescriptor是干嘛用的?本来我也没有仔细了解过描述符这一块的知识,不过粉丝问了,我就抽周末的时间看看,顺便学习一下,粉丝问的刚好是PropertyDescriptor这个属性描述符,我看了下源码。/** * A PropertyDescriptor describes one property that a Java Bean * exports via a pair of accessor methods. /public class PropertyDescriptor extends FeatureDescriptor { //…}emmmm,假装自己英语能厉害的说,属性描述符描述了一个属性,即Java Bean 通过一对访问器方法来导出。(没错,他确实是存在于java.beans包下的)通过类关系图,可以知道,我们应该提前了解一下FeatureDescriptor才行了。很好,起码目前还没有设计抽象类或者接口。FeatureDescriptor/* * The FeatureDescriptor class is the common baseclass for PropertyDescriptor, * EventSetDescriptor, and MethodDescriptor, etc. * <p> * It supports some common information that can be set and retrieved for * any of the introspection descriptors. * <p> * In addition it provides an extension mechanism so that arbitrary * attribute/value pairs can be associated with a design feature. /public class FeatureDescriptor { //…}okay,这是很合理的设计方式,FeatureDescriptor为类似PropertyDescriptor、EvebtSetDescriptor、MethodDescriptor的描述符提供了一些共用的常量信息。同时它也提供一个扩展功能,方便任意属性或键值对可以于设计功能相关联。这里简单的说下,在我大致看了一下源码后(可能不够详细,最近有点忙,时间较赶),FeatureDescriptor主要是针对一下属性的一些get/set,同时这些属性都是基本通用于PropertyDescriptor、EvebtSetDescriptor、MethodDescriptor。 private boolean expert; // 专有 private boolean hidden; // 隐藏 private boolean preferred; // 首选 private String shortDescription; //简单说明 private String name; // 编程名称 private String displayName; //本地名称 private Hashtable<String, Object> table; // 属性表其实该类还有另外几个方法,比如深奥的构造函数等等,这里就不深入探讨了。PropertyDescriptor那么我们大致知道了FeatureDescriptor,接下来就可以来深入了解看看这个属性描述符PropertyDescriptor。说到属性,大家一定会想到的就是get/set这个些基础的东西,当我打开PropertyDescriptor源码的时候,我也看到了一开始猜想的点。 private final MethodRef readMethodRef = new MethodRef(); private final MethodRef writeMethodRef = new MethodRef(); private String writeMethodName; private String readMethodName;这里的代码是我从源码中抽离的一部分,起码我们这样看可以大致理解,是分为写和读的步骤,那么就和我们初学java的get/set是一致的。同时我还看到了,这个,及其注释。 // The base name of the method name which will be prefixed with the // read and write method. If name == “foo” then the baseName is “Foo” private String baseName;这好像可以解释,为什么我们的属性在生成get/set的时候,第一个字母变成大写?!注释好像确实是这样写的。由于可能需要一个Bean对象,所以我以前在案例中先创建了一个Cat类。public class Cat { private String name; private String describe; private int age; private int weight; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescribe() { return describe; } public void setDescribe(String describe) { this.describe = describe; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; }}构造函数起码目前,我还不知道我应该怎么使用它,那么我们就一步一步来吧,我看到它有好几个构造函数,这是一个有趣而且有难度的事情,我们先试着创建一个PropertyDescriptor吧。第一种构造函数 /* * Constructs a PropertyDescriptor for a property that follows * the standard Java convention by having getFoo and setFoo * accessor methods. Thus if the argument name is “fred”, it will * assume that the writer method is “setFred” and the reader method * is “getFred” (or “isFred” for a boolean property). Note that the * property name should start with a lower case character, which will * be capitalized in the method names. * * @param propertyName The programmatic name of the property. * @param beanClass The Class object for the target bean. For * example sun.beans.OurButton.class. * @exception IntrospectionException if an exception occurs during * introspection. / public PropertyDescriptor(String propertyName, Class<?> beanClass) throws IntrospectionException { this(propertyName, beanClass, Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName), Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName)); }这个好像是参数最少的,它只需要我们传入一个属性字符串,还有对应的类就好了,其实它也是调用了另一个构造函数,只是它会帮我们默认生成读方法和写方法。方法中的Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName)其实就是自己拼出一个默认的get/set方法,大家有兴趣可以去看看源码。那么对应的实现内容,我想大家应该都想到了。 public static void main(String[] args) throws Exception { PropertyDescriptor CatPropertyOfName = new PropertyDescriptor(“name”, Cat.class); System.out.println(CatPropertyOfName.getPropertyType()); System.out.println(CatPropertyOfName.getPropertyEditorClass()); System.out.println(CatPropertyOfName.getReadMethod()); System.out.println(CatPropertyOfName.getWriteMethod()); }第二种构造函数/* * This constructor takes the name of a simple property, and method * names for reading and writing the property. * * @param propertyName The programmatic name of the property. * @param beanClass The Class object for the target bean. For * example sun.beans.OurButton.class. * @param readMethodName The name of the method used for reading the property * value. May be null if the property is write-only. * @param writeMethodName The name of the method used for writing the property * value. May be null if the property is read-only. * @exception IntrospectionException if an exception occurs during * introspection. / public PropertyDescriptor(String propertyName, Class<?> beanClass, String readMethodName, String writeMethodName) throws IntrospectionException { if (beanClass == null) { throw new IntrospectionException(“Target Bean class is null”); } if (propertyName == null || propertyName.length() == 0) { throw new IntrospectionException(“bad property name”); } if ("".equals(readMethodName) || “".equals(writeMethodName)) { throw new IntrospectionException(“read or write method name should not be the empty string”); } setName(propertyName); setClass0(beanClass); this.readMethodName = readMethodName; if (readMethodName != null && getReadMethod() == null) { throw new IntrospectionException(“Method not found: " + readMethodName); } this.writeMethodName = writeMethodName; if (writeMethodName != null && getWriteMethod() == null) { throw new IntrospectionException(“Method not found: " + writeMethodName); } // If this class or one of its base classes allow PropertyChangeListener, // then we assume that any properties we discover are “bound”. // See Introspector.getTargetPropertyInfo() method. Class[] args = { PropertyChangeListener.class }; this.bound = null != Introspector.findMethod(beanClass, “addPropertyChangeListener”, args.length, args); }没错,这个构造函数就是第一种构造函数内部二次调用的,所需要的参数很简单,同时我也希望大家可以借鉴这个方法中的一些检测方式。这次的实现方式也是同样的形式。 public static void main(String[] args) throws Exception { PropertyDescriptor CatPropertyOfName = new PropertyDescriptor(“name”, Cat.class,“getName”,“setName”); System.out.println(CatPropertyOfName.getPropertyType()); System.out.println(CatPropertyOfName.getPropertyEditorClass()); System.out.println(CatPropertyOfName.getReadMethod()); System.out.println(CatPropertyOfName.getWriteMethod()); }第三种构造函数 /* * This constructor takes the name of a simple property, and Method * objects for reading and writing the property. * * @param propertyName The programmatic name of the property. * @param readMethod The method used for reading the property value. * May be null if the property is write-only. * @param writeMethod The method used for writing the property value. * May be null if the property is read-only. * @exception IntrospectionException if an exception occurs during * introspection. */ public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException { if (propertyName == null || propertyName.length() == 0) { throw new IntrospectionException(“bad property name”); } setName(propertyName); setReadMethod(readMethod); setWriteMethod(writeMethod); }这个不用传类,因为你需要传递两个实际的方法进来,所以主要三个对应属性的参数既可。看看大致的实现内容 public static void main(String[] args) throws Exception { Class<?> classType = Cat.class; Method CatNameOfRead = classType.getMethod(“getName”); Method CatNameOfWrite = classType.getMethod(“setName”, String.class); PropertyDescriptor CatPropertyOfName = new PropertyDescriptor(“name”, CatNameOfRead,CatNameOfWrite); System.out.println(CatPropertyOfName.getPropertyType()); System.out.println(CatPropertyOfName.getPropertyEditorClass()); System.out.println(CatPropertyOfName.getReadMethod()); System.out.println(CatPropertyOfName.getWriteMethod()); }好了,大致介绍了几种构造函数与实现方式,起码我们现在知道它需要什么。一些使用方式其实在我上面写一些构造函数的时候,我想大家应该已经感受到与反射相关了,起码我感觉上是这样的,所以我一开始想到这样的案例形式,通过反射与这个属性描述类去赋予我的类。 public static void main(String[] args) throws Exception { //获取类 Class classType = Class.forName(“com.example.demo.beans.Cat”); Object catObj = classType.newInstance(); //获取Name属性 PropertyDescriptor catPropertyOfName = new PropertyDescriptor(“name”,classType); //得到对应的写方法 Method writeOfName = catPropertyOfName.getWriteMethod(); //将值赋进这个类中 writeOfName.invoke(catObj,“river”); Cat cat = (Cat)catObj; System.out.println(cat.toString()); }运行结果还是顺利的。Cat{name=‘river’, describe=‘null’, age=0, weight=0}可以看到,我们确实得到了一个理想中的对象。那么我是不是可以改变一个已经创建的对象呢? public static void main(String[] args) throws Exception { //一开始的默认对象 Cat cat = new Cat(“river”,“黑猫”,2,4); //获取name属性 PropertyDescriptor catPropertyOfName = new PropertyDescriptor(“name”,Cat.class); //得到读方法 Method readMethod = catPropertyOfName.getReadMethod(); //获取属性值 String name = (String) readMethod.invoke(cat); System.out.println(“默认:” + name); //得到写方法 Method writeMethod = catPropertyOfName.getWriteMethod(); //修改值 writeMethod.invoke(cat,“copy”); System.out.println(“修改后:” + cat); }上面的demo是,我先创建了一个对象,然后通过属性描述符读取name值,再进行修改值,最后输出的对象的值也确实改变了。默认:river修改后:Cat{name=‘copy’, describe=‘黑猫’, age=2, weight=4}收尾这是一个有趣的API,我想另外两个(EvebtSetDescriptor、MethodDescriptor)应该也差不多,大家可以再通过此方法去探究,只有自己尝试一次才能学到这里面的一些东西,还有一些项目场景的使用方式,不过一般的业务场景应该很少使用到这个API。那么这个东西究竟可以干什么呢?我想你试着敲一次也许有一些答案了。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

January 19, 2019 · 5 min · jiezi