精华内容
下载资源
问答
  • 深入理解Java类型信息(Class对象)与反射机制

    万次阅读 多人点赞 2017-05-01 23:19:19
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解

    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
    http://blog.csdn.net/javazejian/article/details/70768369
    出自【zejian的博客】

    关联文章:

    深入理解Java类型信息(Class对象)与反射机制

    深入理解Java枚举类型(enum)

    深入理解Java注解类型(@Annotation)

    深入理解Java并发之synchronized实现原理

    深入理解Java内存模型(JMM)及volatile关键字

    深入理解Java类加载器(ClassLoader)

    本篇主要是深入对Java中的Class对象进行分析,这对后续深入理解反射技术非常重要,主要内容如下:

    深入理解Class对象

    RRTI的概念以及Class对象作用

    认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下:

    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement {
        private static final int ANNOTATION= 0x00002000;
        private static final int ENUM      = 0x00004000;
        private static final int SYNTHETIC = 0x00001000;
    
        private static native void registerNatives();
        static {
            registerNatives();
        }
    
        /*
         * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类)
         * This constructor is not used and prevents the default constructor being
         * generated.
         */
        private Class(ClassLoader loader) {
            // Initialize final field for classLoader.  The initialization value of non-null
            // prevents future JIT optimizations from assuming this final field is null.
            classLoader = loader;
        }

    Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图):

    到这我们也就可以得出以下几点信息:

    • Class类也是类的一种,与class关键字是不一样的。

    • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。

    • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。

    • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载

    • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

    Class对象的加载及其获取方式

    Class对象的加载

    前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题(例子引用自Thinking in Java):

    package com.zejian;
    
    class Candy {
      static {   System.out.println("Loading Candy"); }
    }
    
    class Gum {
      static {   System.out.println("Loading Gum"); }
    }
    
    class Cookie {
      static {   System.out.println("Loading Cookie"); }
    }
    
    public class SweetShop {
      public static void print(Object obj) {
        System.out.println(obj);
      }
      public static void main(String[] args) {  
        print("inside main");
        new Candy();
        print("After creating Candy");
        try {
          Class.forName("com.zejian.Gum");
        } catch(ClassNotFoundException e) {
          print("Couldn't find Gum");
        }
        print("After Class.forName(\"com.zejian.Gum\")");
        new Cookie();
        print("After creating Cookie");
      }
    }

    在上述代码中,每个类Candy、Gum、Cookie都存在一个static语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果:

    inside main
    Loading Candy
    After creating Candy
    Loading Gum
    After Class.forName("com.zejian.Gum")
    Loading Cookie
    After creating Cookie
    
    Process finished with exit code 0

    从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象肯定会被加载,毕竟Candy实例对象的创建依据其Class对象。比较有意思的是

    Class.forName("com.zejian.Gum");

    其中forName方法是Class类的一个static成员方法,记住所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会导致Gum类被加载(前提是Gum类从来没有被加载过)。

    Class.forName方法

    通过上述的案例,我们也就知道Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象,如下的第2种方式是通过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。

    public static void main(String[] args) {
    
        try{
          //通过Class.forName获取Gum类的Class对象
          Class clazz=Class.forName("com.zejian.Gum");
          System.out.println("forName=clazz:"+clazz.getName());
        }catch (ClassNotFoundException e){
          e.printStackTrace();
        }
    
        //通过实例对象获取Gum的Class对象
        Gum gum = new Gum();
        Class clazz2=gum.getClass();
        System.out.println("new=clazz2:"+clazz2.getName());
    
      }

    注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。

    Class字面常量

    在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:

    //字面常量的方式获取Class对象
    Class clazz = Gum.class;

    这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。

    boolean.class = Boolean.TYPE;
    char.class = Character.TYPE;
    byte.class = Byte.TYPE;
    short.class = Short.TYPE;
    int.class = Integer.TYPE;
    long.class = Long.TYPE;
    float.class = Float.TYPE;
    double.class = Double.TYPE;
    void.class = Void.TYPE;

    前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下:

    • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    • 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。

    • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

    由此可知,我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程:

    import java.util.*;
    
    class Initable {
      //编译期静态常量
      static final int staticFinal = 47;
      //非编期静态常量
      static final int staticFinal2 =
        ClassInitialization.rand.nextInt(1000);
      static {
        System.out.println("Initializing Initable");
      }
    }
    
    class Initable2 {
      //静态成员变量
      static int staticNonFinal = 147;
      static {
        System.out.println("Initializing Initable2");
      }
    }
    
    class Initable3 {
      //静态成员变量
      static int staticNonFinal = 74;
      static {
        System.out.println("Initializing Initable3");
      }
    }
    
    public class ClassInitialization {
      public static Random rand = new Random(47);
      public static void main(String[] args) throws Exception {
        //字面常量获取方式获取Class对象
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");
        //不触发类初始化
        System.out.println(Initable.staticFinal);
        //会触发类初始化
        System.out.println(Initable.staticFinal2);
        //会触发类初始化
        System.out.println(Initable2.staticNonFinal);
        //forName方法获取Class对象
        Class initable3 = Class.forName("Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
      }
    }

    执行结果:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74

    从输出结果来看,可以发现,通过字面常量获取方式获取Initable类的Class对象并没有触发Initable类的初始化,这点也验证了前面的分析,同时发现调用Initable.staticFinal变量时也没有触发初始化,这是因为staticFinal属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量staticFinal存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量staticFinal的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因。但在之后调用了Initable.staticFinal2变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能确定,因此staticFinal2并不是编译期常量,使用该变量必须先初始化Initable类。Initable2和Initable3类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于forName方法获取Class对象,肯定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,ok~,到此这里可以得出小结论:

    • 获取Class对象引用的方式3种,通过继承自Object类的getClass方法,Class类的静态方法forName以及字面常量的方式”.class”。

    • 其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。

    • 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。

    关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化

    • 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。

    • 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。

    • 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。

    • 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类

    • 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)

    理解泛化的Class对象引用

    由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。如下:

    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClazzDemo {
    
        public static void main(String[] args){
            //没有泛型
            Class intClass = int.class;
    
            //带泛型的Class对象
            Class<Integer> integerClass = int.class;
    
            integerClass = Integer.class;
    
            //没有泛型的约束,可以随意赋值
            intClass= double.class;
    
            //编译期错误,无法编译通过
            //integerClass = double.class
        }
    }
    

    从代码可以看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上Integer.class就是一个Class<Integer>类的对象。面对下述语句,确实可能令人困惑,但该语句确实是无法编译通过的。

    //编译无法通过
    Class<Number> numberClass=Integer.class;

    我们或许会想Integer不就是Number的子类吗?然而事实并非这般简单,毕竟Integer的Class对象并非Number的Class对象的子类,前面提到过,所有的Class对象都只来源于Class类,看来事实确实如此。当然我们可以利用通配符“?”来解决问题:

    Class<?> intClass = int.class;
    intClass = double.class;

    这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用Class<?>呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此Class<?>总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题:

    //编译通过!
    Class<? extends Number> clazz = Integer.class;
    //赋予其他类型
    clazz = double.class;
    clazz = Number.class;

    上述的代码是行得通的,extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用Class<Number>是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。

    关于类型转换的问题

    在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型:

    package com.zejian;
    
    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClassCast {
    
     public void cast(){
    
         Animal animal= new Dog();
         //强制转换
         Dog dog = (Dog) animal;
     }
    }
    
    interface Animal{ }
    
    class Dog implements  Animal{ }

    之所可以强制转换,这得归功于RRTI,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。除了强制转换外,在Java SE5中新增一种使用Class对象进行类型转换的方式,如下:

    Animal animal= new Dog();
    //这两句等同于Dog dog = (Dog) animal;
    Class<Dog> dogType = Dog.class;
    Dog dog = dogType.cast(animal)

    利用Class对象的cast方法,其参数接收一个参数对象并将其转换为Class引用的类型。这种方式似乎比之前的强制转换更麻烦些,确实如此,而且当类型不能正确转换时,仍然会抛出ClassCastException异常。源码如下:

    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
             throw new ClassCastException(cannotCastMsg(obj));
         return (T) obj;
      }

    instanceof 关键字与isInstance方法

    关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)

    public void cast2(Object obj){
        if(obj instanceof Animal){
              Animal animal= (Animal) obj;
          }
    }

    而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子:

    public void cast2(Object obj){
            //instanceof关键字
            if(obj instanceof Animal){
                Animal animal= (Animal) obj;
            }
    
            //isInstance方法
            if(Animal.class.isInstance(obj)){
                Animal animal= (Animal) obj;
            }
      }

    事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceOf是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。一般形式:

    //判断这个对象是不是这种类型
    obj.instanceof(class)

    而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false;一般形式如下:

    //判断这个对象能不能被转化为这个类
    class.inInstance(obj)

    最后这里给出一个简单实例,验证isInstance方法与instanceof等价性:

    class A {}
    
    class B extends A {}
    
    public class C {
      static void test(Object x) {
        print("Testing x of type " + x.getClass());
        print("x instanceof A " + (x instanceof A));
        print("x instanceof B "+ (x instanceof B));
        print("A.isInstance(x) "+ A.class.isInstance(x));
        print("B.isInstance(x) " +
          B.class.isInstance(x));
        print("x.getClass() == A.class " +
          (x.getClass() == A.class));
        print("x.getClass() == B.class " +
          (x.getClass() == B.class));
        print("x.getClass().equals(A.class)) "+
          (x.getClass().equals(A.class)));
        print("x.getClass().equals(B.class)) " +
          (x.getClass().equals(B.class)));
      }
      public static void main(String[] args) {
        test(new A());
        test(new B());
      } 
    }

    执行结果:

    Testing x of type class com.zejian.A
    x instanceof A true
    x instanceof B false //父类不一定是子类的某个类型
    A.isInstance(x) true
    B.isInstance(x) false
    x.getClass() == A.class true
    x.getClass() == B.class false
    x.getClass().equals(A.class)) true
    x.getClass().equals(B.class)) false
    ---------------------------------------------
    Testing x of type class com.zejian.B
    x instanceof A true
    x instanceof B true
    A.isInstance(x) true
    B.isInstance(x) true
    x.getClass() == A.class false
    x.getClass() == B.class true
    x.getClass().equals(A.class)) false
    x.getClass().equals(B.class)) true

    到此关于Class对象相关的知识点都分析完了,下面将结合Class对象的知识点分析反射技术。

    理解反射技术

    反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

    Constructor类及其用法

    Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

    方法返回值方法名称方法说明
    static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。
    Constructor<T>getConstructor(Class<?>... parameterTypes)返回指定参数类型、具有public访问权限的构造函数对象
    Constructor<?>[]getConstructors()返回所有具有public访问权限的构造函数的Constructor对象数组
    Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)返回指定参数类型、所有声明的(包括private)构造函数对象
    Constructor<?>[]getDeclaredConstructor()返回所有声明的(包括private)构造函数对象
    TnewInstance()创建此 Class 对象所表示的类的一个新实例。

    下面看一个简单例子来了解Constructor对象的使用:

    package reflect;
    
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectDemo implements Serializable{
        public static void main(String[] args) throws Exception {
    
            Class<?> clazz = null;
    
            //获取Class对象的引用
            clazz = Class.forName("reflect.User");
    
            //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
            User user = (User) clazz.newInstance();
            user.setAge(20);
            user.setName("Rollen");
            System.out.println(user);
    
            System.out.println("--------------------------------------------");
    
            //获取带String参数的public构造函数
            Constructor cs1 =clazz.getConstructor(String.class);
            //创建User
            User user1= (User) cs1.newInstance("xiaolong");
            user1.setAge(22);
            System.out.println("user1:"+user1.toString());
    
            System.out.println("--------------------------------------------");
    
            //取得指定带int和String参数构造函数,该方法是私有构造private
            Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
            //由于是private必须设置可访问
            cs2.setAccessible(true);
            //创建user对象
            User user2= (User) cs2.newInstance(25,"lidakang");
            System.out.println("user2:"+user2.toString());
    
            System.out.println("--------------------------------------------");
    
            //获取所有构造包含private
            Constructor<?> cons[] = clazz.getDeclaredConstructors();
            // 查看每个构造方法需要的参数
            for (int i = 0; i < cons.length; i++) {
                //获取构造函数参数类型
                Class<?> clazzs[] = cons[i].getParameterTypes();
                System.out.println("构造函数["+i+"]:"+cons[i].toString() );
                System.out.print("参数类型["+i+"]:(");
                for (int j = 0; j < clazzs.length; j++) {
                    if (j == clazzs.length - 1)
                        System.out.print(clazzs[j].getName());
                    else
                        System.out.print(clazzs[j].getName() + ",");
                }
                System.out.println(")");
            }
        }
    }
    
    
    class User {
        private int age;
        private String name;
        public User() {
            super();
        }
        public User(String name) {
            super();
            this.name = name;
        }
    
        /**
         * 私有构造
         * @param age
         * @param name
         */
        private User(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
    
      //..........省略set 和 get方法
    }

    运行结果:

    User [age=20, name=Rollen]
    --------------------------------------------
    user1:User [age=22, name=xiaolong]
    --------------------------------------------
    user2:User [age=25, name=lidakang]
    --------------------------------------------
    构造函数[0]:private reflect.User(int,java.lang.String)
    参数类型[0]:(int,java.lang.String)
    构造函数[1]:public reflect.User(java.lang.String)
    参数类型[1]:(java.lang.String)
    构造函数[2]:public reflect.User()
    参数类型[2]:()

    关于Constructor类本身一些常用方法如下(仅部分,其他可查API),

    方法返回值方法名称方法说明
    Class<T>getDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
    Type[]getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。
    StringgetName()以字符串形式返回此构造方法的名称。
    Class<?>[]getParameterTypes()按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
    TnewInstance(Object... initargs)使用此 Constructor对象表示的构造函数来创建新实例
    StringtoGenericString()返回描述此 Constructor 的字符串,其中包括类型参数。

    代码演示如下:

    Constructor cs3=clazz.getDeclaredConstructor(int.class,String.class);
    
    System.out.println("-----getDeclaringClass-----");
    Class uclazz=cs3.getDeclaringClass();
    //Constructor对象表示的构造方法的类
    System.out.println("构造方法的类:"+uclazz.getName());
    
    System.out.println("-----getGenericParameterTypes-----");
    //对象表示此 Constructor 对象所表示的方法的形参类型
    Type[] tps=cs3.getGenericParameterTypes();
    for (Type tp:tps) {
        System.out.println("参数名称tp:"+tp);
    }
    System.out.println("-----getParameterTypes-----");
    //获取构造函数参数类型
    Class<?> clazzs[] = cs3.getParameterTypes();
    for (Class claz:clazzs) {
        System.out.println("参数名称:"+claz.getName());
    }
    System.out.println("-----getName-----");
    //以字符串形式返回此构造方法的名称
    System.out.println("getName:"+cs3.getName());
    
    System.out.println("-----getoGenericString-----");
    //返回描述此 Constructor 的字符串,其中包括类型参数。
    System.out.println("getoGenericString():"+cs3.toGenericString());
    /**
     输出结果:
     -----getDeclaringClass-----
     构造方法的类:reflect.User
     -----getGenericParameterTypes-----
     参数名称tp:int
     参数名称tp:class java.lang.String
     -----getParameterTypes-----
     参数名称:int
     参数名称:java.lang.String
     -----getName-----
     getName:reflect.User
     -----getoGenericString-----
     getoGenericString():private reflect.User(int,java.lang.String)
     */

    其中关于Type类型这里简单说明一下,Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。getGenericParameterTypesgetParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class仅代表String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。当然Type还有其他子类,如:

    • TypeVariable:表示类型参数,可以有上界,比如:T extends Number

    • ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:List<String>

    • WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer

    通过以上的分析,对于Constructor类已有比较清晰的理解,利用好Class类和Constructor类,我们可以在运行时动态创建任意对象,从而突破必须在编译期知道确切类型的障碍。

    Field类及其用法

    Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:

    方法返回值方法名称方法说明
    FieldgetDeclaredField(String name)获取指定name名称的(包含private修饰的)字段,不包括继承的字段
    Field[]getDeclaredField()获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
    FieldgetField(String name)获取指定name名称、具有public修饰的字段,包含继承字段
    Field[]getField()获取修饰符为public的字段,包含继承字段

     
    下面的代码演示了上述方法的使用过程

    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectField {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class<?> clazz = Class.forName("reflect.Student");
            //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
            // 否则抛NoSuchFieldException
            Field field = clazz.getField("age");
            System.out.println("field:"+field);
    
            //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
            Field fields[] = clazz.getFields();
            for (Field f:fields) {
                System.out.println("f:"+f.getDeclaringClass());
            }
    
            System.out.println("================getDeclaredFields====================");
            //获取当前类所字段(包含private字段),注意不包含父类的字段
            Field fields2[] = clazz.getDeclaredFields();
            for (Field f:fields2) {
                System.out.println("f2:"+f.getDeclaringClass());
            }
            //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
            Field field2 = clazz.getDeclaredField("desc");
            System.out.println("field2:"+field2);
        }
        /**
          输出结果: 
         field:public int reflect.Person.age
         f:public java.lang.String reflect.Student.desc
         f:public int reflect.Person.age
         f:public java.lang.String reflect.Person.name
    
         ================getDeclaredFields====================
         f2:public java.lang.String reflect.Student.desc
         f2:private int reflect.Student.score
         field2:public java.lang.String reflect.Student.desc
         */
    }
    
    class Person{
        public int age;
        public String name;
        //省略set和get方法
    }
    
    class Student extends Person{
        public String desc;
        private int score;
        //省略set和get方法
    }

    上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:

    //获取Class对象引用
    Class<?> clazz = Class.forName("reflect.Student");
    
    Student st= (Student) clazz.newInstance();
    //获取父类public字段并赋值
    Field ageField = clazz.getField("age");
    ageField.set(st,18);
    Field nameField = clazz.getField("name");
    nameField.set(st,"Lily");
    
    //只获取当前类的字段,不获取父类的字段
    Field descField = clazz.getDeclaredField("desc");
    descField.set(st,"I am student");
    Field scoreField = clazz.getDeclaredField("score");
    //设置可访问,score是private的
    scoreField.setAccessible(true);
    scoreField.set(st,88);
    System.out.println(st.toString());
    
    //输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} 
    
    //获取字段值
    System.out.println(scoreField.get(st));
    // 88

    其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:

    方法返回值方法名称方法说明
    voidset(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
    Objectget(Object obj)返回指定对象上此 Field 表示的字段的值
    Class<?>getType()返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
    booleanisEnumConstant()如果此字段表示枚举类型的元素则返回 true;否则返回 false
    StringtoGenericString()返回一个描述此 Field(包括其一般类型)的字符串
    StringgetName()返回此 Field 对象表示的字段的名称
    Class<?>getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
    voidsetAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

     
    上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

    Method类及其用法

    Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:

    方法返回值方法名称方法说明
    MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
    Method[]getDeclaredMethod()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    MethodgetMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
    Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

    同样通过案例演示上述方法:

    import java.lang.reflect.Method;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectMethod  {
    
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    
            Class clazz = Class.forName("reflect.Circle");
    
            //根据参数获取public的Method,包含继承自父类的方法
            Method method = clazz.getMethod("draw",int.class,String.class);
    
            System.out.println("method:"+method);
    
            //获取所有public的方法:
            Method[] methods =clazz.getMethods();
            for (Method m:methods){
                System.out.println("m::"+m);
            }
    
            System.out.println("=========================================");
    
            //获取当前类的方法包含private,该方法无法获取继承自父类的method
            Method method1 = clazz.getDeclaredMethod("drawCircle");
            System.out.println("method1::"+method1);
            //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
            Method[] methods1=clazz.getDeclaredMethods();
            for (Method m:methods1){
                System.out.println("m1::"+m);
            }
        }
    
    /**
         输出结果:
         method:public void reflect.Shape.draw(int,java.lang.String)
    
         m::public int reflect.Circle.getAllCount()
         m::public void reflect.Shape.draw()
         m::public void reflect.Shape.draw(int,java.lang.String)
         m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
         m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
         m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
         m::public boolean java.lang.Object.equals(java.lang.Object)
         m::public java.lang.String java.lang.Object.toString()
         m::public native int java.lang.Object.hashCode()
         m::public final native java.lang.Class java.lang.Object.getClass()
         m::public final native void java.lang.Object.notify()
         m::public final native void java.lang.Object.notifyAll()
    
         =========================================
         method1::private void reflect.Circle.drawCircle()
    
         m1::public int reflect.Circle.getAllCount()
         m1::private void reflect.Circle.drawCircle()
         */
    }
    
    class Shape {
        public void draw(){
            System.out.println("draw");
        }
    
        public void draw(int count , String name){
            System.out.println("draw "+ name +",count="+count);
        }
    
    }
    class Circle extends Shape{
    
        private void drawCircle(){
            System.out.println("drawCircle");
        }
        public int getAllCount(){
            return 100;
        }
    }

    在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:

    Class clazz = Class.forName("reflect.Circle");
    //创建对象
    Circle circle = (Circle) clazz.newInstance();
    
    //获取指定参数的方法对象Method
    Method method = clazz.getMethod("draw",int.class,String.class);
    
    //通过Method对象的invoke(Object obj,Object... args)方法调用
    method.invoke(circle,15,"圈圈");
    
    //对私有无参方法的操作
    Method method1 = clazz.getDeclaredMethod("drawCircle");
    //修改私有方法的访问标识
    method1.setAccessible(true);
    method1.invoke(circle);
    
    //对有返回值得方法操作
    Method method2 =clazz.getDeclaredMethod("getAllCount");
    Integer count = (Integer) method2.invoke(circle);
    System.out.println("count:"+count);
    
    /**
        输出结果:
        draw 圈圈,count=15
        drawCircle
        count:100
    */

    在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。

    方法返回值方法名称方法说明
    Objectinvoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
    Class<?>getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
    TypegetGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
    Class<?>[]getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
    Type[]getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
    StringgetName()以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
    booleanisVarArgs()判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
    StringtoGenericString()返回描述此 Method 的字符串,包括类型参数。

     
    getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息

    public interface Type {
        //1.8新增
        default String getTypeName() {
            return toString();
        }
    }

    而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。

    反射包中的Array类

    在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array,它提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值。在Class类中与数组关联的方法是:

    方法返回值方法名称方法说明
    Class<?>getComponentType()返回表示数组元素类型的 Class,即数组的类型
    booleanisArray()判定此 Class 对象是否表示一个数组类。

    java.lang.reflect.Array中的常用静态方法如下:

    方法返回值方法名称方法说明
    static Objectset(Object array, int index)返回指定数组对象中索引组件的值。
    static intgetLength(Object array)以 int 形式返回指定数组对象的长度
    static objectnewInstance(Class<?> componentType, int... dimensions)创建一个具有指定类型和维度的新数组。
    static ObjectnewInstance(Class<?> componentType, int length)创建一个具有指定的组件类型和长度的新数组。
    static voidset(Object array, int index, Object value)将指定数组对象中索引组件的值设置为指定的新值。

    下面通过一个简单例子来演示这些方法

    package reflect;
    
    import java.lang.reflect.Array;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectArray {
    
        public static void main(String[] args) throws ClassNotFoundException {
            int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            //获取数组类型的Class 即int.class
            Class<?> clazz = array.getClass().getComponentType();
            //创建一个具有指定的组件类型和长度的新数组。
            //第一个参数:数组的类型,第二个参数:数组的长度
            Object newArr = Array.newInstance(clazz, 15);
            //获取原数组的长度
            int co = Array.getLength(array);
            //赋值原数组到新数组
            System.arraycopy(array, 0, newArr, 0, co);
            for (int i:(int[]) newArr) {
                System.out.print(i+",");
            }
    
            //创建了一个长度为10 的字符串数组,
            //接着把索引位置为6 的元素设为"hello world!",然后再读取索引位置为6 的元素的值
            Class clazz2 = Class.forName("java.lang.String");
    
            //创建一个长度为10的字符串数组,在Java中数组也可以作为Object对象
            Object array2 = Array.newInstance(clazz2, 10);
    
            //把字符串数组对象的索引位置为6的元素设置为"hello"
            Array.set(array2, 6, "hello world!");
    
            //获得字符串数组对象的索引位置为5的元素的值
            String str = (String)Array.get(array2, 6);
            System.out.println();
            System.out.println(str);//hello
        }
        /**
         输出结果:
         1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
         hello world!
         */
    }

    通过上述代码演示,确实可以利用Array类和反射相结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了上的set/get外Array还专门为8种基本数据类型提供特有的方法,如setInt/getInt、setBoolean/getBoolean,其他依次类推,需要使用是可以查看API文档即可。除了上述动态修改数组长度或者动态创建数组或动态获取值或设置值外,可以利用泛型动态创建泛型数组如下:

    /**
      * 接收一个泛型数组,然后创建一个长度与接收的数组长度一样的泛型数组,
      * 并把接收的数组的元素复制到新创建的数组中,
      * 最后找出新数组中的最小元素,并打印出来
      * @param a
      * @param <T>
      */
     public  <T extends Comparable<T>> void min(T[] a) {
         //通过反射创建相同类型的数组
         T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length);
         for (int i = 0; i < a.length; i++) {
             b[i] = a[i];
         }
         T min = null;
         boolean flag = true;
         for (int i = 0; i < b.length; i++) {
             if (flag) {
                 min = b[i];
                 flag = false;
             }
             if (b[i].compareTo(min) < 0) {
                 min = b[i];
             }
         }
         System.out.println(min);
     }

    毕竟我们无法直接创建泛型数组,有了Array的动态创建数组的方式这个问题也就迎刃而解了。

    //无效语句,编译不通
    T[] a = new T[];

    ok~,到这反射中几个重要并且常用的类我们都基本介绍完了,但更重要是,我们应该认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于那种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已加载了这个类的Class对象,当然这点完全不必由我们操作,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在并且JVM能够正确找到。关于Class类的方法在前面我们只是分析了主要的一些方法,其实Class类的API方法挺多的,建议查看一下API文档,浏览一遍,有个印象也是不错的选择,这里仅列出前面没有介绍过又可能用到的API:

     /** 
      *    修饰符、父类、实现的接口、注解相关 
      */
    
    //获取修饰符,返回值可通过Modifier类进行解读
    public native int getModifiers();
    //获取父类,如果为Object,父类为null
    public native Class<? super T> getSuperclass();
    //对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的
    public native Class<?>[] getInterfaces();
    //自己声明的注解
    public Annotation[] getDeclaredAnnotations();
    //所有的注解,包括继承得到的
    public Annotation[] getAnnotations();
    //获取或检查指定类型的注解,包括继承得到的
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
    
    /** 
      *   内部类相关
      */
    //获取所有的public的内部类和接口,包括从父类继承得到的
    public Class<?>[] getClasses();
    //获取自己声明的所有的内部类和接口
    public Class<?>[] getDeclaredClasses();
    //如果当前Class为内部类,获取声明该类的最外部的Class对象
    public Class<?> getDeclaringClass();
    //如果当前Class为内部类,获取直接包含该类的类
    public Class<?> getEnclosingClass();
    //如果当前Class为本地类或匿名内部类,返回包含它的方法
    public Method getEnclosingMethod();
    
    /** 
      *    Class对象类型判断相关
      */
    //是否是数组
    public native boolean isArray();  
    //是否是基本类型
    public native boolean isPrimitive();
    //是否是接口
    public native boolean isInterface();
    //是否是枚举
    public boolean isEnum();
    //是否是注解
    public boolean isAnnotation();
    //是否是匿名内部类
    public boolean isAnonymousClass();
    //是否是成员类
    public boolean isMemberClass();
    //是否是本地类
    public boolean isLocalClass(); 

    ok~,本篇到此完结。


    展开全文
  • 对jQuery对象理解

    千次阅读 2017-01-04 12:05:07
    这里首先要理解DOM文档对象模型: (1)DOM解析XML文档时,DOM为XML文档在逻辑上建立一个树模型; (2)DOM把XML文档作为树结构来查看,XML文档的树形结构节点就是XML文档上的元素、文本和属性。 还要理解HTML页面为...

    1、什么是jQuery对象?

    jQuery对象是通过jQuery包装DOM对象后产生的对象。

    这里首先要理解DOM文档对象模型:

    (1)DOM解析XML文档时,DOM为XML文档在逻辑上建立一个树模型;

    (2)DOM把XML文档作为树结构来查看,XML文档的树形结构节点就是XML文档上的元素、文本和属性。

    还要理解HTML页面为什么与DOM对象联系在一起?

    因为HTML页面中的元素都表现为类似XML文档的树形结构:存在元素节点、属性节点和文本节点,所以用

    DOM对象也可以解析HTML页面中的元素(这也是形成jQuery选择器的原因)。

    再者,还要理解DOM对象如何转化为jQuery对象?

    HTML DOM 定义了多种查找HTML页面元素的方法,除了 getElementById() 之外,

    还有 getElementsByName() 和 getElementsByTagName()。找到的这个页面元素就是一个DOM对象。

    用jQuery构造这个DOM对象,就获得一个jQuery对象。示例:

    var domObj = document.getElementById("element");

    var $obj=jQuery(domObj);

    2、jQuery对象的表示:

    在jQuery库中,$是jQuery的一个简写形式,所以一个节点元素的jQuery对象可以写成:

    $(nodeString)

    其中,nodeString是:Html标签的id、class、或者标签名等。

    3、jQuery中DOM的操作

    DOM是一种与浏览器、语言无关的接口,利用该接口可以方便地访问HTML页面中的标准组件。jQuery提供了一系列方法

    对DOM进行各种操作,从而通过DOM实现对HTNL页面中各种元素的操作。

    每一个HTML页面都可以使用DOM表示,在使用jQuery进行DOM操作之前,HTML页面首先被看作一棵DOM树。

    展开全文
  • 全方位深入理解JavaScript面向对象

    万次阅读 多人点赞 2018-03-30 10:13:16
    JavaScript面向对象...理解JavaScript中的函数对象与普通对象 理解prototype和proto 理解原型和原型链 详解原型链相关的Object方法 了解如何用ES5模拟类,以及各种方式的优缺点 了解如何用ES6实现面向对象 目录...

    JavaScript面向对象程序设计

    本文会碰到的知识点:
    原型、原型链、函数对象、普通对象、继承

    读完本文,可以学到

    • 面向对象的基本概念
    • JavaScript对象属性
    • 理解JavaScript中的函数对象与普通对象
    • 理解prototype和__proto__
    • 理解原型和原型链
    • 详解原型链相关的Object方法
    • 了解如何用ES5模拟类,以及各种方式的优缺点
    • 了解如何用ES6实现面向对象

    目录

    1. 面向对象的基本概念

    面向对象也即是OOP,Object Oriented Programming,是计算机的一种编程架构,OOP的基本原则是计算机是由子程序作用的单个或者多个对象组合而成,包含属性和方法的对象是类的实例,但是JavaScript中没有类的概念,而是直接使用对象来实现编程。
    特性:

    • 封装:能够将一个实体的信息、功能、响应都封装到一个单独对象中的特性。

      由于JavaScript没有public、private、protected这些关键字,但是可以利用变量的作用域来模拟public和private封装特性

    var insObject = (function() {
        var _name = 'hello'; // private
        return {
            getName: function() { // public
                return _name; 
            }
        }
    })();
    
    
    insObject._name; // undefined
    insObject.getName(); // hello
    

    这里只是实现了一个简单的版本,private比较好的实现方式可以参考深入理解ES6 145页
    protected可以利用ES6的Symbol关键字来实现,这里不展开,有兴趣可以讨论

    • 继承:在不改变源程序的基础上进行扩充,原功能得以保存,并且对子程序进行扩展,避免重复代码编写,后面的章节详细描述

    • 多态:允许将子类类型的指针赋值给父类类型的指针;原生JS是弱类型语言,没有多态概念

      但是JavaScript也不是不能实现多态的概念,只是如果你之前是学静态语言的同学,理解起来可能有些误差。例子:

      比如我们有台电脑mac, 它有一个方法system来获取系统

      var mac = {
          system: function(){
             console.log('mac');
          }
      }
        
      var getSystem = function() {
          mac.system();  
      }
        
      getSystem();// mac
      

      某一天我们换成win,为了防止后面又换成mac,我们让getSystem函数有一定的弹性。

       var mac = {
        system: function(){
             console.log('mac');
         }
       }
       
       var win = {
         system: function(){
             console.log('win');
         }
       }
       
       var getSystem = function(type) {
         if (type == 'mac') {
             mac.system();
         } else if (type == 'win') {
             win.system();
         }
       }
       
       getSystem('mac');// mac
       getSystem('win');// win
      

      但是很明显这个函数还是有问题,某天我又换成centos呢。。。。我们改写一下getSystem这个函数

        var getSystem = function(ins) {
            if (ins.system instanceOf Function) {
                ins.system();
            }
        }
      

      这里我们是假设每个系统获取系统的名称都是system,实际开发过程中可能不会这样,这种情况可以用适配器模式来解决。

    JavsScript中面向对象的一些概念:

    • 类class: ES5以前就是构造函数,ES6中有class
    • 实例instance和对象object:构造函数创建出来的对象一般称为实例instance
    • 父类和子类:JavaScript也可以称为父对象和子对象

    2. JavaScript对象属性

    想弄懂面向对象,是不是先看看对象是啥呢?
    我们先看一个题目:

    [] + {}; // "[object Object]"
    {} + []; // 0
    

    解释:
    在第一行中,{}出现在+操作符的表达式中,因此被翻译为一个实际的值(一个空object)。而[]被强制转换为"“因此{}也会被强制转换为一个string:”[object Object]"。
    但在第二行中,{}被翻译为一个独立的{}空代码块儿(它什么也不做)。块儿不需要分号来终结它们,所以这里缺少分号不是一个问题。最终,+ []是一个将[]明确强制转换 为number的表达式,而它的值是0

    2.1 属性

    对象的属性

    • Object.prototype Object 的原型对象,不是每个对象都有prototype属性
    • Object.prototype.proto 不是标准方法,不鼓励使用,每个对象都有__proto__属性,但是由于浏览器实现方式的不同,__proto__属性在chrome、firefox中实现了,在IE中并不支持,替代的方法是Object.getPrototypeOf()
    • Object.prototype.constructor:用于创建一个对象的原型,创建对象的构造函数

    可能大家会有一个疑问,为什么上面那些属性要加上prototype
    在chrome中打印一下var a = {}

    属性描述符

    数据属性:

    特性名称描述默认值
    value属性的值undfined
    writable是否可以修改属性的值,true表示可以,false表示不可以true
    enumerable属性值是否可枚举,true表示可枚举for-in, false表示不可枚举true
    configurable属性的特性是否可配置,表示能否通过delete删除属性后重新定义属性true

    例子:
    这里写图片描述

    访问器属性:

    特性名称描述默认值
    set设置属性时调用的函数undefined
    get写入属性时调用的函数undefined
    configurable表示能否通过delete删除属性后重新定义属性true
    enumerable表示能否通过for-in循环返回属性true

    访问器属性不能直接定义,一般是通过Object.defineProperty()方法来定义,但是这个方法只支持IE9+, 以前一般用两个非标准方法来实现__defineGetter__()֖__defineSetter__()
    例子:

    var book = { _year: 2004, edition: 1 };
    
    
    Object.defineProperty(book, "year", { 
        get: function(){ 
            return this._year; 
        }, 
        set: function(newValue){
            if (newValue > 2004){ 
                this._year = newValue; 
                this.edition += newValue - 2004; 
            }
        }
    });
    
    
    book.year = 2005; 
    alert(book.edition);
    

    2.2 方法

    • Object.prototype.toString() 返回对象的字符串表示
    • Object.prototype.hasOwnProperty() 返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承,也就是说不会检查原型链上的属性
    • Object.prototype.isPrototypeOf() 返回一个布尔值,表示指定的对象是否在本对象的原型链中
    • Object.prototype.propertyIsEnumerable() 判断指定属性是否可枚举
    • Object.prototype.watch() 给对象的某个属性增加监听
    • Object.prototype.unwatch() 移除对象某个属性的监听
    • Object.prototype.valueOf() 返回指定对象的原始值
    • 获取和设置属性
      • Object.defineProperty 定义单个属性
      • Object.defineProperties 定义多个属性
      • Object.getOwnPropertyDescriptor 获取属性
    • Object.assign() 拷贝可枚举属性 (ES6新增)
    • Object.create() 创建对象
    • Object.entries() 返回一个包含由给定对象所有可枚举属性的属性名和属性值组成的 [属性名,属性值] 键值对的数组,数组中键值对的排列顺序和使用for…in循环遍历该对象时返回的顺序一致
    • Object.freeze() 冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象
    • Object.getOwnPropertyNames() 返回指定对象的属性名组成的数组
    • Object.getPrototypeOf 返回该对象的原型
    • Object.is(value1, value2) 判断两个值是否是同一个值 (ES6 新增)
    • Object.keys() 返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致
    • Object.setPrototypeOf(obj, prototype) 将一个指定的对象的原型设置为另一个对象或者null
    • Object.values 返回一个包含指定对象所有的可枚举属性值的数组,数组中的值顺序和使用for…in循环遍历的顺序一样

    2.3 应用

    • 如何检测某个属性是否在对象中?

      • in运算符,判断对象是否包含某个属性,会从对象的实例属性、继承属性里进行检测
      function Dogs(name) {
          this.name = name
      }
      
      function BigDogs(size) {
          this.size = size;
      }
      
      BigDogs.prototype = new Dogs();
      
      var a = new BigDogs('big');
      
      'size' in a;
      'name' in a;
      'age' in a;
      
      • Object.hasOwnProperty(),判断一个对象是否有指定名称的属性,不会检查继承属性
      a.hasOwnProperty('size');
      a.hasOwnProperty('name');
      a.hasOwnProperty('age');
      
      • Object.propertyIsEnumerable(),判断指定名称的属性是否为实例属性并且是可枚举的
      // es6
      var a = Object.create({}, {
          name: {
              value: 'hello',
              enumerable: true,
          },
          age: {
              value: 11,
              enumerable: false,
          }
      });
      
      // es5
      var b = {};
      Object.defineProperties(b, {
          name: {
              value: 'hello',
              enumerable: true,
          },
          age: {
              value: 11,
              enumerable: false,
          } 
      });
      
      a.propertyIsEnumerable('name');
      a.propertyIsEnumerable('age');
      
    • 如何枚举对象的属性,并保证不同了浏览器中的行为是一致的?

      • for/in 语句,可以遍历可枚举的实例属性和继承属性
      var a = {
        supername: 'super hello',
        superage: 'super name',
      }
      var b = {};
      Object.defineProperties(b, {
        name: {
            value: 'hello',
            enumerable: true,
        },
        age: {
            value: 11,
            enumerable: false,
        } 
      });
         
      Object.setPrototypeOf(b, a); // 设置b的原型式a 等效的是b.__proto__ = a
         
      for(pro in b) {
        console.log(pro); // name, supername, superage
      }
      
      • Object.keys(), 返回一个数组,内容是对象可枚举的实例属性名称
       var propertyArray = Object.keys(b);
       // name
      
      • Object.getOwnPropertyNames(),返回一个数组,内容是对象所有实例属性,包括可枚举和不可枚举
       var propertyArray = Object.getOwnPropertyNames(b);
       // name, age
      
    • 如何判断两个对象是否相等?
      我只想说,这个问题说简单很简单,说复杂也挺复杂的传送门
      我们看个简单版的

      function isEquivalent(a, b) {
           var aProps = Object.getOwnPropertyNames(a);
           var bProps = Object.getOwnPropertyNames(b);
           if (aProps.length != bProps.length){
               return false;
           }
      
      
           for (var i = 0; i < aProps.length; i++) {
               var propName = aProps[i];
               if (a[propName] !== b[propName]) {
                   return false;
               }
           }
           return true;
      

    }

    // Outputs: true
    console.log(isEquivalent({a:1},{a:1}));
    ```
    

    上面这个函数还有啥问题呢?

    • 没有对传入参数进行校验,例如判断是否是NaN,或者是其他内置属性
    • 没有判断传入对象的construct和prototype
    • 时间算法复杂度是O(n2)

    有同学可能会有疑问,能不能用Object.is,答案是否定的,Object.is简单来说就是在===的基础上特别处理了NaN,+0,-0,保证了-0和+0不相同,Object.is(NaN, NaN)返回true

    • 对象的深拷贝和浅拷贝
      其实如果大家理解了上面的那些方法,是很容易写出深拷贝和浅拷贝的代码的,我们先看一下这两者的却别。
      浅拷贝仅仅是复制引用,拷贝后a === b, 注意Object.assign方法实现的是浅复制(此处有深刻教训!!!)
      深拷贝这是创建了一个新的对象,然后把旧的对象中的属性和方法拷贝到新的对象中,拷贝后 a !== b
      深拷贝的实现由很多例子,例如jQuery的extend和lodash中的cloneDeep, clone。jQuery可以使用$.extend(true, {}, ...)来实现深拷贝, 但是jQuery无法复制JSON对象之外的对象,例如ES6引入的Map、Set等。而lodash加入的大量的代码来实现ES6新引入的标准对象
      这里需要单独研究分享/(ㄒoㄒ)/~~

    3. 对象分为函数对象和普通对象

    概念(什么是函数对象和普通对象)

    Object、Function、Array、Date等js的内置对象都是函数对象

    问题:

    function a1 () {}
    const a2 = function () {}
    const a3 = new Function();
    
    
    const b1 = {};
    const b2 = new Object();
    
    
    const c1 = [];
    const c2 = new Array();
    
    
    const d1 = new a1();
    const d2 = new b1();????
    const d3 = new c1();????
    
    
    typeof a1;
    typeof a2;
    typeof a3;
    
    
    typeof b1;
    typeof b2;
    
    
    typeof c1;
    typeof c2;
    
    
    typeof d1;
    
    
    

    上面两行报错的原因,是因为构造函数只能由函数来充当,而b1和c1不是Function的实例,所以不能充当构造器

    但是只有Function的实例都是函数对象、其他的实例都是普通对象

    我们延伸一下,在看个例子

    const e1 = function *(){};
    const e2 = new e1();
    // Uncaught TypeError: e1 is not a constructor
    console.log(e1.constructor) // 是有值的。。。
    // 规范里面就不能new
    const e2 = e1();
    

    GeneratorFunction是一个特殊的函数对象
    e1.__proto__.__proto__ === Function.prototype

    e1的原型实际上是一个生成器函数GeneratorFunction,也就是说
    e1.__proto__ === GeneratorFunction.prototype

    这行代码有问题么,啊哈哈哈,GeneratorFunction这个关键字主流的JavaScript还木有暴露出来,所以这个大家理解就好啦

    虽然不能直接new e1
    但是可以 new e1.constructor();哈哈哈哈

    4. 理解prototype和__proto__

    对象类型prototypeproto
    函数对象YesYes
    普通对象NoYes
    • 只有函数对象具有prototype这个属性

    • prototype__proto__都是js在定义一个对象时的预定义属性

    • prototype 被实例的__proto__指向

    • __proto__指向构造函数的prototype

    const a = function(){}
    const b = {}
    
    
    typeof a // function
    typeof b // object
    
    
    typeof a.prototype // object
    typeof a.__proto__ // function
    
    
    typeof b.prototype // undefined
    typeof b.__proto__ // object
    
    
    a.__proto__ === Function.prototype
    b.__proto__ === Object.prototype
    
    
    

    理解了prototype__proto__之后,我们来看看之前一直说的为什么JavaScript里面都是对象

    const a = {}
    const b = function () {}
    const c = []
    const d = new Date()
    
    
    a.__proto__
    a.__proto__ === Object.prototype
    
    
    b.__proto__
    b.__proto__ === Function.prototype
    
    
    c.__proto__
    c.__proto__ === Array.prototype
    
    
    d.__proto__
    d.__proto__ === Date.prototype
    
    
    Object.prototype.__proto__ //null
    
    
    Function.prototype.__proto__ === Object.prototype
    
    
    Array.prototype.__proto__ === Object.prototype
    
    
    Date.prototype.__proto__ === Object.prototype
    

    延伸一个问题:如何判断一个变量是否是数组?

    • typeof

    我们上面已经解释了,这些都是普通对象,普通对象是没有prototype的,他们typeof的值都是object

    typeof []
    typeof {}
    
    • 从原型来看, 原理就是看Array是否在a的原型链中

    a的原型链是 Array->Object

    const a = [];
    Array.prototype.isPrototypeOf(obj);
    
    • instanceof
    const a = [];
    a instanceof Array
    

    从构造函数入手,但是这个方法和上面的方法都有一问题,不同的框架中创建的数组不会相互共享其prototype属性

    • 根据对象的class属性,跨原型调用tostring方法
    const a = [];
    Object.prototype.toString.call(a);
    // [Object Array]
    

    ES5 中所有内置对象的[[Class]]属性的值是由规范定义的,但是 ES6 中已经没有了[[Class]]属性,取代它的是[[NativeBrand]]属性,这个大家有兴趣可以自行去查看规范
    原理:

    1. 如果this的值为undefined,则返回"[object Undefined]".
    2. 如果this的值为null,则返回"[object Null]".
    3. 让O成为调用ToObject(this)的结果.
    4. 让class成为O的内部属性[[Class]]的值.
    5. 返回三个字符串"[object ", class, 以及 "]"连接后的新字符串.

    问题?这个一定是正确的么?不正确为啥?
    提示ES6的Symbol属性

    • Array.isArray()
      部分浏览器中不兼容

    桌面浏览器
    这里写图片描述
    移动端浏览器
    这里写图片描述

    5. 理解原型与原型链

    其实上一节中的prototype和__proto__就是为了构建原型链而存在的,之前也或多或少的说到了原型链这个概念。

    看下面的代码:

    const Dogs = function(name) {
        this.name = name;
    }
    
    
    Dogs.prototype.getName = function() {
        return this.name
    }
    
    
    const jingmao = new Dogs('jingmao');
    console.log(jingmao);
    console.log(jingmao.getName());
    
    
    

    这段代码的执行过程
    1.首先创建了一个构造函数Dogs,传入一个参数name,Dogs.prototype也会自动创建
    2.给对象dogs增加了一个方法
    3.通过构造函数Dogs实例化了一个对象jingmao
    4.输出jingmao的值
    这里写图片描述
    可以看到jingmao有两个值name和__proto__,其中__proto__指向Dogs.prototype
    5.执行getName方法时,在jingmao中找不到这个方法,就会继续向着原型链继续往上找,也就是通过__proto__,然后就找到了getName方法。

    这个过程实际上就是原型继承,实际上JavaScript的原型继承就是利用了__proto__并借助prototype来实现的。

    试一试下面 看输出结果是啥?

    jingmao.__proto__ === Function.prototype
    
    
    Dogs.prototype 指向什么
    Dogs.prototype.__proto__ 指向什么
    Dogs.prototype.__proto__.__proto__ 指向什么
    

    上面例子中getName 最终是查找到了,那么如果在原型链中一直没查找到,会怎么样?
    例如console.log(jingmao.age)

    jingmao 是一个对象可以继续
    jingmao.age 不存在,继续
    jingmao.__proto__ 是一个对象可以继续
    jingmao.__proto__.age 不存在,继续
    jingmao.__proto__.__proto__ 是个对象可以继续
    jingmao.__proto__.__proto__.age 不存在,继续
    jingmao.__proto__.__proto__.__proto__ null,不是对象,到头啦
    

    原型链的概念其实不重要,重要的是要理解,简单来说,原型链就是利用原型让一个引用类型继承另一个应用类型的属性和方法。

    最后我们用一张图来结束本节
    这里写图片描述

    Array.__proto__ === Function.prototype
    Object.__proto__ === Function.prototype
    

    还有三点需要注意的:

    • 任何内置函数对象(类)本身的 **proto**都指向 Function 的原型对象;
    • 除了 Object 的原型对象的**proto** 指向 null,其他所有内置函数对象的原型对象的 proto 都指向 object。
    • 所有构造函数的的prototype方法的__proto__都指向Object.prototype(除了…Object.prototype自身)

    如果理解了上面这些内容,大家可以自行描述一下,构造函数、原型和实例之间的关系,也可以举例说明

    function Dogs (name) {
        this.name = name;
    }
    
    
    var jingmao = new Dogs('jingmao');
    

    这个图大家脑子里面自己构想一下?

    解释:
    构造函数首字母必须大写,用来区分普通函数,内部使用this指针,指向要生成的实例对象,通过new来生成实例对象。
    实例就是通过new一个构造函数产生的对象,它有一个属性[[prototype]]指向原型
    原型中有一个属性[[constructor]],指向构造函数

    6.与原型链相关的方法

    这里只是简单介绍一下

    6.1 hasOwnProperty

    Object.hasOwnProperty() 返回一个布尔值,表示某个对象的实例是否含有指定的属性,而且此属性非原型链继承。用来判断属性是来自实例属性还是原型属性。类似还有in操作符,in操作符只要属性存在,不管实在实例中还是原型中,就会返回true。同时使用in和hasOwnProperty就可以判断属性是在原型中还是在实例中

    const Dogs = function (age) {
        this.age = age
    }
    
    
    Dogs.prototype.getAge = function() {
        return this.age;
    }
    
    
    const jingmao = new Dogs(14);
    
    
    jingmao.hasOwnProperty(age);
    

    6.2 isPrototypeOf

    Object.prototype.isPrototypeOf() 返回一个布尔值,表示指定的对象是否在本对象的原型链中

    const Dogs = function (age) {
        this.age = age
    }
    
    
    Dogs.prototype.getAge = function() {
        return this.age;
    }
    
    
    const jingmao = new Dogs(11);
    Object.prototype.isPrototypeOf(Dogs);
    Dogs.prototype.isPrototypeOf(jingmao);
    

    6.3 getPrototypeOf

    Object.getPrototypeOf 返回该对象的原型

    const Dogs = function (age) {
        this.age = age
    }
    
    
    Dogs.prototype.getAge = function() {
        return this.age;
    }
    
    
    const jingmao = new Dogs(11);
    
    
    jingmao.__proto__ === Object.getPrototypeOf(jingmao) 
    

    7. ES5 对象继承

    7.1 原型继承

    原型继承就是利用原型链来实现继承

    function SuperType() {
        this.supername = 'super';
    }
    
    
    SuperType.prototype.getSuperName= function(){
        return this.supername;
    }
    
    
    function SubType () {
        this.subname='subname';
    }
    
    
    SubType.prototype = new SuperType();
    
    
    SubType.prototype.getSubName = function (){
        return this.subname;
    }
    
    
    var instance1 = new SubType();
    console.log(instance1.getSubName());
    console.log(instance1.getSuperName());
    

    这里写图片描述


    需要注意的地方:
    实现原型继承的时候不要使用对象字面量创建原型方法,因为这样做,会重写原型链。

    function SuperType() {
        this.supername = 'super';
    }
    
    
    SuperType.prototype.getSuperName= function(){
        return this.supername;
    }
    
    
    function SubType () {
        this.subname='subname';
    }
    
    
    SubType.prototype = new SuperType();
    
    
    SubType.prototype =  {
        getSubName: function (){
            return this.subname;
        }
    }
    
    
    var instance1 = new SubType();
    console.log(instance1.getSubName());
    console.log(instance1.getSuperName()); // error
    
    
    

    这里写图片描述

    上面使用SubType.prototype = {...}之后,SubType的原型就是Object了,而不是SuperType了。


    优点:原型定义的属性和方法可以复用
    缺点:

    1. 引用类型的原型属性会被所有实例共享
    2. 创建子对象时,不能向父对象的构造函数中传递参数

    7.2 构造函数继承

    这里的例子来源是JavaScript高级程序设计

    在说构造函数继承之前,我们先看一个例子

    var a = {
        name: 'a',
    };
    
    
    var name = 'window';
    
    
    var getName = function(){
        console.log(this.name);
    }
    
    
    getName() // 输出window
    getName.call(a) // 输出a
    

    执行getName()时,函数体的this指向window,而执行getName.call(a)时,函数体的this指向的是a对象,所以就可以理解啦。接下来我们看如何实现构造函数继承

    function SuperType () {
        this.colors = ['red', 'green'];
    }
    
    
    function SubType () {
        // 继承SuperType
        SuperType.call(this);
    }
    
    
    var instance1 = new SubType();
    instance1.colors.push('blue'); 
    console.log(instance1.colors); 
    // red, green, blue
    
    
    var instance2 = new SubType();
    console.log(instance2.colors);
    // red, green
    

    SuperType.call(this); 这一行代码,实际上意思是在SubType的实例初始化过程中,调用了SuperType的构造函数,因此SubType的每个实例都有colors这个属性

    优点:子对象可以传递参数给父对象。

    function SuperType(name) {
        this.name = name;
    }
    function SubType(name, age) {
        name = name || 'hello';
        SuperType.call(this, name);
        this.age = age;
    }
    
    
    var instance1 = new SubType('scofield', 28);
    console.log(instance1.name);
    console.log(instance1.age);
    

    需要注意的地方是在调用父对象的构造函数之后,再给子类型中的定义属性,否则会被重写。

    缺点:方法都需要在构造函数中定义,难以做到函数的复用,而且在父对象的原型上定义的方法,对于子类型是不可见的。 ??? 为什么不可见

    function SuperType(name) {
        this.name = name;
    }
    
    
    SuperType.prototype.getName = function() {
        return this.name;
    }
    
    
    SuperType.prototype.prefix = function() {
        return 'prefix';
    }
    
    
    function SubType(name) {
        SuperType.call(this, name);
    }
    
    
    var instance1 = new SubType('scofield');
    console.log(instance1.name);
    console.log(instance1.prefix);
    console.log(instance1.getName());
    // Uncaught TypeError: instance1.getName is not a function
    

    7.2 组合式继承

    组合式继承顾名思义,就是组合两种模式实现JavaScript的继承,借助原型链和构造函数来实现。这样子在原型上定义方法实现了函数的复用,而且能够保证每个实例都有自己的属性。

    function SuperType (name) {
        this.name = name;
        this.con = [];
    }
    
    
    SuperType.prototype.getName = function() {
        return this.name;
    }
    
    
    function SubType (name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    
    
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.getAge = function() {
        return this.age;
    };
    
    
    var instance1 = new SubType('li', 18);
    instance1.con.push('test1');
    console.log(instance1.con); // test1
    console.log(instance1.getAge()); // 18
    console.log(instance1.getName()); // li
    
    
    var instance2 = new SubType('hang', 18);
    console.log(instance2.con); // test1
    console.log(instance2.getAge()); // 18
    console.log(instance2.getName()); // hang
    
    
    

    优点:弥补了原型继承和构造函数的缺点
    缺点:父类构造函数调用了两次

    7.3 原型式继承

    原型式继承并没有使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象,例如:

    function createObject(o) {
        function newOrient () {};
        newOrient.prototype = o;
        return new newOrient();
    }
    

    简单来说createObject函数,对传入的o对象进行的一次浅拷贝。在ES5中新增加了一个方法Object.create(), 它的作用和createObject是一样的,但是只支持IE9+。

    var Dogs = {
        name: 'jingmao',
        age: 1
    }
    
    
    var BigDogs = Object.create(Dogs);
    BigDogs.name= 'bigjingmao';
    BigDogs.size = 'big';
    console.log(BigDogs.age);
    

    其中Object.create还支持传入第二个参数,参数与Object.defineProperties()方法的格式相同,并且会覆盖原型上的同名属性。

    7.4 寄生式继承

    寄生式继承其实和原型式继承很类似,区别在于,寄生式继承创建的一个函数把所有的事情做完了,例如给新的对象增加属性和方法。

    function createAnother(o) {
        var clone = Object.create(o);
        clone.size = 'big';
        return clone;
    }
    
    
    var Dogs = {
        name: 'jingmao',
        age: 1
    }
    
    
    var BigDogs = createAnother(Dogs);
    console.log(BigDogs.size);
    

    7.5 寄生组合式继承

    到最后一个了,看看我们之前遗留的问题:
    组合继承会调用两次父对象的构造函数,并且父类型的属性存在两组,一组在实例上,一组在SubType的原型上。解决这个问题的方法就是寄生组合式继承。

    function inheritPrototype(subType, superType){ 
        // 继承父类的原型
        var prototype = Object.create(superType.prototype);
        // 重写被污染的construct
        prototype.constructor = subType; 
        // 重写子类的原型  
        subType.prototype = prototype; 
    }
    

    这个函数就是寄生组合式继承的最简单的实现方式

    function SuperType(name){ 
        this.name = name; 
        this.colors = ["red", "blue", "green"];
    }
    
    
    SuperType.prototype.sayName = function(){ 
        alert(this.name); 
    };
    
    
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    
    
    inheritPrototype(SubType, SuperType);
    
    
    SubType.prototype.sayAge = function(){ 
        alert(this.age); 
    };
    
    
    var instance1 = new SubType('hello', 18);
    
    
    instance1.__proto__.constructor == SubType
    

    这里写图片描述
    可以看到

    1. 子类继承了父类的属性和方法,同时属性没有创建在原型链上,因此多个子类不会共享同一个属性。
    2. 子类可以动态传递参数给父类
    3. 父类构造函数只执行了一次

    但是还有一个问题:
    子类如果在原型上添加方法,必须要在继承之后添加,否则会覆盖原来原型上的方法。但是如果这两个类是已存在的类,就不行了

    优化一下:

    function inheritPrototype(subType, superType){ 
        // 继承父类的原型
        var prototype = Object.create(superType.prototype);
        // 重写被污染的construct
        prototype.constructor = subType; 
        // 重写子类的原型  
        subType.prototype = Object.assign(prototype, subType.prototype); 
    }
    

    虽然通过Object.assign来进行copy解决了覆盖原型类型的方法的问题,但是Object.assign只能够拷贝可枚举的方法,而且如果子类本身就继承了一个类,这个办法也不行。

    8. ES6 实现继承

    我们知道了ES5中可以通过原型链来实现继承,ES6提供了extends关键字来实现继承,这相对而言更加清晰和方便,首先看看ES6 Class的语法,此处参考http://es6.ruanyifeng.com/#docs/class

    8.1 Class基本语法

    1.需要注意的地方。ES6 中类内部定义的所有方法都是不可枚举的
    类的属性名称可以使用表达式(区别1)

    2.严格模式,ES6 class类和模块内部默认是严格模式

    3.construct方法
    也就是类的默认方法,如果没有显示的定义,那么会添加一个空的contruct方法
    返回值:默认返回实例对象,也就是this,当然也可以显式的返回另外一个对象。
    例如:

    Class Foo {
        constructor() {
        }
    }
    
    
    new Foo() instanceof Foo // true
    
    
    Class FakeFoo {
        constructor() {
            return Object.create(null);
        }
    }
    
    
    new Foo() instanceof Foo // false
    

    此外类必须通过new 操作符来调用,否则会报错,这个它与普通的构造函数的区别

    Foo()
    
    
    // TypeError: Class constructor Foo cannot be invoked without 'new'
    

    4.类的实例对象

    类的实例的属性,除非显式的定义在this上,否则都是定义在原型上,这里与ES5保持一致

    5.类的表达式

    与函数一样,类也可以用表达式的方式来定义

    const HClass = class Me {
        getClassName() {
            return Me.name;
        }
    }
    
    
    const hIns = new HClass();
    HClass.getClassName(); // Me
    Me.getClassName(); // error
    

    这里只有HClass是暴露在外部的,Me只有在class的内部使用,如果不需要使用Me,完全可以省略

    那么我们知道利用函数表达式可以创建一个立即执行函数,类可以么?

    
    
    let person = new class {
        constructor(name) {
            this.name = name;
        },
        sayName() {
            console.log(this.name);
        }
    }('jack');
    
    
    persion.sayName()
    

    6.不存在变量提升
    这点是和ES5不一样的, ES6并不会把class的声明提到当前作用域的顶部,这与下一节的继承有关系

    new Foo()
    class Foo {}
    

    7.私有属性和私有方法

    私有方法ES6并不提供,但是可以变通

    • 命名区分
    • 把方法移出模块
    • 利用Symbol来命名方法名
    const getAge = Symbol('getAge');
    
    
    export defalut class Person {
        // 公有方法
        getName(name) {
            return name;
        },
        // 私有方法
       [getAge](age) {
        return age;
       }
    }
    
    
    

    私有属性ES6也不支持,有提案说加个#表示私有属性

    8.this的指向(仔细看看)
    类的内部this的指向默认是指向this的实例的,如果单独使用类中的一些包含this的方法,很有可能会报错

    class Logger {
        printName (name = 'there') {
            this.print(`Hello ${name}`);
        },
        print (text) {
            console.log(text);
        }
    }
    
    
    const logger = new Logger();
    const {printName} = logger;
    printName();
    // Uncaught TypeError: Cannot read property 'print' of undefined
    logger.printName()
    // Hello there
    

    解决办法:

    • 在构造函数中绑定this,这样就不会找不到print方法了
    class Logger {
      constructor() {
        this.printName = this.printName.bind(this);
      }
    
    
      // ...
    }
    
    • 在构造函数中使用箭头函数
    • 使用proxy代理函数,包装

    9.name属性

    10.class中使用get和set函数,可以用来拦截这个属性的存取行为,利用getOwnPropertyDescriptor来查看属性的get和set函数是否有定义

    11.如果在类里面在某个方法上加上*,则表示这个方法是Generator函数

    12.在类的某个方法前面加上static关键字,表示这个方法是静态方法,这个方法不会被实例继承,只能够通过类来调用,如果这个静态方法中有this,那么this指向的是类,而不是实例
    此外静态方法,和非静态方法是可以重名滴

    class Foo {
      static bar () {
        this.baz();
      }
      static baz () {
        console.log('hello');
      }
      baz () {
        console.log('world');
      }
    }
    
    
    Foo.bar() // hello
    

    父类的静态方法可以被子类继承

    13.类的静态属性,也就是说是通过类直接访问的属性

    Class Foo {
        p = 1,
        
        static: 1,
    }
    

    上面的两种方法都是错误的,目前静态属性还处于提案中,

    Class Foo {
        p = 1;
        
        static p = 1;
    }
    

    以前我们定义实例属性只能够在construct中定义

    14.new.target属性, new.target返回new命令作用的那个构造函数,如果没有通过new来实例对象,那么这个属性的值是undefined

    function Person(name) {
      if (new.target !== undefined) {
        this.name = name;
      } else {
        throw new Error('必须使用 new 命令生成实例');
      }
    }
    
    
    var person = new Person('Jack'); // 正确
    var notAPerson = Person.call(person, 'Jack');  // 报错
    

    在Class内部调用的时候,new.target返回当前的Class,需要注意一点就是当子类继承父类的时候,返回当前的Class

    class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
      }
    }
    
    
    class Square extends Rectangle {
      constructor(length) {
        super(length, length);
      }
    }
    
    
    var obj = new Square(3); // 输出 false
    

    利用这个特点我们可以写出这样的代码

    class Rectangle {
      constructor(length, width) {
        if(new.Target === Rectangle) {
         throw new Error('本类不能实例化');
        }
      }
    }
    
    
    class Square extends Rectangle {
      constructor(length) {
        super(length, length);
      }
    }
    
    
    var obj = new Square(3);
    var notobj = new Rectangle();
    

    8.2 Class的继承

    1.基本概念

    Class可以通过extends关键字来实现继承,而ES5中是通过修改原型链来实现继承

    子类必须在constructor中调用super方法,否则新建实例的时候会报错,因为子类没有自己的this,是继承与父类,然后进行加工。

    class Point { /* ... */ }
    
    
    class ColorPoint extends Point {
      constructor() {
      }
    }
    
    
    let cp = new ColorPoint(); // ReferenceError
    

    我们回忆一下ES5的继承,实质是首先创建了子类的实例对象,然后把父类的方法添加到子类上。而ES6是先创建父类的实例对象,然后再用子类的构造函数修改this,如果子类没有添加constructor,这个方法会被自动添加

    class ColorPoint extends Point {
    }
    
    
    // 等同于
    class ColorPoint extends Point {
      constructor(...args) {
        super(...args);
      }
    }
    

    还有一点需要注意,在子类的构造函数中,只有调用super后,才可以使用this关键字,否则会报错

    2.super关键字,super可以作为函数和对象来使用

    • super作为函数调用时代表父类的构造函数,这里代表A的构造函数,但是返回的是B的实例。作为函数调用时,只能在子类的构造函数调用,如果在其他地方调用会报错。
    class A {}
    
    
    class B extends A {
      constructor() {
        super();
        // 等价于A.prototype.constructor.call(this)
      }
    }
    
    • super作为对象,在普通的方法中,指向父类的原型对象;在静态函数中,指向父类。
    class A {
      constructor() {
        this.x = 1;
      }
      print() {
        console.log(this.x);
      }
    }
    
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      m() {
        super.print();
      }
    }
    
    
    let b = new B();
    b.m() 
    
    
    

    ES6 规定,通过super调用父类的方法时,方法内部的this指向当前的子类实例

    由于this指向子类的实例,当对super的一个属性复制的时候,赋值会变成子类的属性

    3.ES6的__proto__和prototype

    我们知道在ES5中,每个对象的__proto__属性,指向对应构造函数的prototype。而ES6里面有两条继承链路,先看一个例子

    class A {
    }
    
    
    class B extends A {
    }
    
    
    B.__proto__ === A // true
    B.prototype.__proto__ === A.prototype // true
    
    • 子类的__proto__属性指向父类,表示构造函数的继承
    • 子类的原型的__proto__指向父类的原型,表示方法的继承
    class A {
    }
    
    
    class B {
    }
    
    
    // B 的实例继承 A 的实例
    Object.setPrototypeOf(B.prototype, A.prototype);
    
    
    // B 继承 A 的静态属性
    Object.setPrototypeOf(B, A);
    
    
    const b = new B();
    

    再看下一个问题,我们知道ES6是通过extends关键字来实现继承的,那么extends后面的值可以是什么类型呢?我们根据上的两条继承链路就知道,父类应该要有prototype属性,也就是说函数都可以作为父类被继承,此外我们看3中特殊情况

    • 子类继承于Object类
    class A extends Object {
    }
    
    
    A.__proto__ === Object // true
    A.prototype.__proto__ === Object.prototype // true
    
    • 不存在继承
    class A {
    }
    
    
    A.__proto__ === Function.prototype // true
    A.prototype.__proto__ === Object.prototype // true
    
    • 子类继承null
    class A extends null {
    }
    
    
    A.__proto__ === Function.prototype // true
    A.prototype.__proto__ === undefined // true
    
    1. 原生构造函数的继承

    我们知道,以前原生构造函数是无法继承的,原因是因为子类无法获得原生构造函数的内部属性。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性

    function MyArray() {
      Array.apply(this, arguments);
    }
    
    
    MyArray.prototype = Object.create(Array.prototype, {
      constructor: {
        value: MyArray,
        writable: true,
        configurable: true,
        enumerable: true
      }
    });
    
    
    var colors = new MyArray();
    colors[0] = "red";
    colors.length  // 0
    
    
    colors.length = 0;
    colors[0]  // "red"
    

    ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。因此我们可以自定义原生数据结构的子类,这些是ES5无法做到的

    class MyArray extends Array {
      constructor(...args) {
        super(...args);
      }
    }
    
    
    var arr = new MyArray();
    arr[0] = 12;
    arr.length // 1
    
    
    arr.length = 0;
    arr[0] // undefined
    

    6.Mixin的实现,也就是将多个对象合并成一个对象

    const a = {
      a: 'a'
    };
    const b = {
      b: 'b'
    };
    const c = {...a, ...b}; // {a: 'a', b: 'b'}
    

    上面是一个比较简单的做法,我们看一个完整的实现方式

    function mix(...mixins) {
      class Mix {}
    
    
      for (let mixin of mixins) {
        copyProperties(Mix, mixin); // 拷贝实例属性
        copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
      }
    
    
      return Mix;
    }
    
    
    function copyProperties(target, source) {
      for (let key of Reflect.ownKeys(source)) {
        if ( key !== "constructor"
          && key !== "prototype"
          && key !== "name"
        ) {
          let desc = Object.getOwnPropertyDescriptor(source, key);
          Object.defineProperty(target, key, desc);
        }
      }
    }
    
    
    class DistributedEdit extends mix(Loggable, Serializable) {
      // ...
    }
    
    展开全文
  • JSON是什么?如何正确理解

    万次阅读 多人点赞 2018-08-04 20:47:26
    JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 ...

    1.背景介绍

    什么是JSON

    JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。——百度百科

    数据传输是我们在敲代码时,经常遇到的一个场景,前后端交互。给数据一个统一的格式有利于我们编写和解析数据。

    json,是一种数据格式,在与后端的数据交互中有较为广泛的应用。

    JSON的诞生

    JSON是 (JavaScript Object Notation, JS 对象标记),它是一种数据交换格式。在JSON出现之前,大家一直用XML来传递数据。因为XML是一种纯文本格式,所以它适合在网络上交换数据。XML本身不算复杂,但是,加上DTD、XSD、XPath、XSLT等一大堆复杂的规范以后,任何正常的软件开发人员碰到XML都会感觉头大了,最后大家发现,即使你努力钻研几个月,也未必搞得清楚XML的规范。

    终于,在2002年的一天,道格拉斯·克罗克福特(DouglasCrockford)同学为了拯救深陷水深火热同时又被某几个巨型软件企业长期愚弄的软件工程师,发明了JSON这种超轻量级的数据交换格式。

    由于JSON非常简单,很快就风靡Web世界,并且成为ECMA标准。几乎所有编程语言都有解析JSON的库,而在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。

    转义

    我们在调用 jsonp 接口或者调用js文件的时候,由于文件编码不同会导致出现乱码的问题。 如果你的文件出现了非英文字符,如果调用时文件编码不一致,同样会出现乱码情况。

    这也就是为什么要数据统一格式的原因。

    JSON 是适用于 Ajax 应用程序的一种有效格式,原因是它使 JavaScript 对象和字符串值之间得以快速转换 JSON是一种传递对象的语法

    JSON是一个提供了stringify和parse方法的内置对象

    stringify将js对象转化为符合json标准的字符串

    parse将符合json标准的字符串转化为js对象


    2.知识剖析

    JSON对值的类型和格式有严格的规定
    复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
    简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)。
    字符串必须使用双引号表示,不能使用单引号。
    对象的键名必须放在双引号里面。
    数组或对象最后一个成员的后面,不能加逗号。
      PS: 需要注意的是,空数组和空对象都是合格的JSON值,null本身也是一个合格的JSON值。

     

    JSON 的语法规则
    JSON 的语法规则十分简单,可称得上“优雅完美”,总结起来有:
    数组(Array)用方括号(“[]”)表示。
    对象(Object)用大括号(”{}”)表示。
    名称/值对(name/value)组合成数组和对象。
    名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
    并列的数据之间用逗号(“,”)分隔

    实例

    JSON 数据的书写格式是:名称/值对。

    名称/值对组合中的名称写在前面(在双引号中),值对写在后面,中间用冒号隔开,

    其中 值 可以是:数字(整数或浮点数)、字符串(在双引号中)、布尔值(true或false)、数组(在方括号中)、对象(在花括号中)、null

    varjson= {"password":123456,"name":"myname","Booleans":true,"Array":[x,y,z],"object":{}}

    或者是嵌套使用

     

    转义概述

    为什么需要转义?在js中我们使用的js对象进行处理,但是在与后端数据交换的时候,我们发送规定的json格式的字符串,所以在给后端发送或接受数据的时候,需要转义

    {name:"myname",password:123456}

    "{"name":"myname","password":123456}"

    其中json字符串转js对象,调用parse方法:

    js对象 = JSON.parse(json字符串);

    js对象转json字符串,调用stringify方法:

    json字符串 = JSON.stringify(js对象);


    3.常见问题

    在json字符串转换成对象,还有eval_r('('+json字符串+')')这个方法,但是在对目标数据进行读取时,可能会出现一些意外的错误:

     


    4.解决方案

    原因:eval_r获取的json对象的值中,如果有执行代码,也将照样执行!所以若不能保证数据的安全性,不要使用eval_r方法进行转义。

     


    5.编码实战

    下面是一个简单的json数据发送应用,有兴趣可以阅读一下:

     

     

    6.扩展思考

    我们常看到

    {name:"myname",password:123456}

    {"name":"myname","password":123456}

    这样两种格式,即js对象和json,然而js对象和json有什么不一样的地方?

    很多人搞不清楚 JSON 和 Js 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:

    JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

    var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的

    var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串

     


    7.参考文献

    参考一:JSON官方文网

    参考二:Json对象和Json字符串的区别

    参考三:json属性名必须加引号的讨论

    参考四:json怎么理解


    8.更多讨论

    1、XML是什么?

    XML与Access,Oracle和SQL Server等数据库不同,数据库提供了更强有力的数据存储和分析能力,例如:数据索引、排序、查找、相关一致性等,XML的宗旨传输数据的,而与其同属标准通用标记语言的HTML主要用于显示数据。事实上XML与其他数据表现形式最大的不同是:他极其简单。这是一个看上去有点琐细的优点,但正是这点使XML与众不同。

    2、向后台传输数据要用什么格式?

    json字符串格式,如果是一个对象要先用JSON.stringify()转义,转义成为字符串才能将数据传给后端。

    3、后端传过来的字符串数据要怎样才能转义成为一个对象?

    用JSON.parse()方法转义。

    展开全文
  • 深入理解Python中的面向对象

    万次阅读 多人点赞 2017-06-12 06:42:41
    面向过程与面向对象的对比 id、type和value的讲解 类和对象的概念 初始化构造函数__init__的作用 self关键字的使用 继承的概念 组合的概念 接口的概念 抽象类的概念 1、面向过程与面向对象的对比面向过程的程序设计...
  • 对Java面向对象理解

    千次阅读 2019-04-06 10:10:19
    Java的面向对象理解 1.思想简述: 面向对象是一种思想。它将数据和操作数据的方法封装在对象中,从而使对象有了一些功能,也就是说面向对象是将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的...
  • 转自:https://blog.csdn.net/gao1440156051/article/details/51073433C++Primer教材上说引用能再改变绑定搞得对象,也就是引用再第二次赋值。可是下面的程序能正常运行,不会出错。这里怎么出现了引用赋值语句呢...
  • 正确理解ThreadLocal

    万次阅读 2007-07-28 11:24:00
    先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般...另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的
  • httpSession的正确理解

    万次阅读 多人点赞 2018-09-06 09:47:51
    下面说说我的理解吧: 一个session就是一系列某用户和服务器间的通讯。服务器有能力分辨出不同的用户。一个session的建立是从一个用户向服务器发第一个请求开始,而以用户显式结束或session超时为结束。 其工作原理...
  • 理解jquery对象和DOM对象的区别

    千次阅读 2009-04-05 13:38:00
    今天是刚接触JQuery,所以很多概念都理解,jquery里面用$(#id)获取返回的其实就是一个jquery对象。看了一篇文章:jquery对象和DOM对象的转换最近在闲暇时间用jQuery搞了一个多文件上传的东东,顺便写点笔记。...
  • 深入理解this对象

    千次阅读 2016-04-13 21:25:36
    this的定义在JavaScript中this表示函数运行的时候自动生成的一个内部对象,只能在函数内部使用,下面是一个简单的例子:function test(){ alert(this == window); } test();code1 有的时候this的使用会让许多人...
  • 谈谈对this 对象理解

    千次阅读 2018-08-16 09:28:41
    this表示当前对象,this的指向是根据调用的上下文来决定的,默认指向window对象,指向window对象时可以省略写, 例如:  this.alert() &lt;=&gt; window.alert()&lt;=&gt; alert();  1、调用...
  • 谈谈你对this对象理解

    千次阅读 2019-09-23 14:50:48
    理解: 1.this是js 的一个关键字,随着函数的使用场合的不同,this 的值会发生变化。 2.一个总原则:即this指的是调用函数的那个对象。 3.一般情况下,this 是全局对象,可以作为方法调用。 转载于:...
  • 深入理解Java对象的创建过程:类的初始化与实例化

    万次阅读 多人点赞 2017-05-18 14:17:45
    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。...
  • 面向对象经典例子理解

    千次阅读 2014-07-02 19:29:11
    Java面向对象  Java语言是面向对象的。Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。Java语言全面...
  • 可变对象可变对象

    万次阅读 2018-08-12 22:22:48
    ”中,因为在调用方法时与方法内部,输出id是相同,所以理解官方的那句话 Remember that arguments are passed by assignment in Python.&nbsp; 要记住,Python 里的参数是通过赋值传递的。  之后...
  • 正确理解转义字符\

    万次阅读 2017-05-27 16:59:26
    sizeof是C/C++中的一个关键字,不是函数,简单的说其作用就是返回一个对象或者类型所占的内存字节数。 strlen()是一个函数,求一个字符串的有效长度,strlen函数的结束条件是遇到\0结束计数。 二.用sizeof求一个...
  • 创建后状态能被修改...可变性的理解: 无论是Java语言规范还是Java存储模型都没有对可变性做出正式的定义。可变性并不是将域简单地等于将对象的所有变量都声明为final类型,所有域都是final类型的对象仍然可以
  • 如何正确的创建和销毁Java对象

    万次阅读 2017-06-15 18:24:16
    Java是一门强大的高级语言。在学习了其基础知识后,我们仍需要理解其深刻的内涵。接下来,我们会以《Effective Java》一书做为Java进阶学习的载体,对Java进行一个系统的、全新的认识。 第一章:创建和销毁对象
  • 深入理解Class对象 RRTI的概念以及Class对象作用 认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于...
  • 对IOC的相关理解总结

    万次阅读 多人点赞 2020-04-20 13:54:07
    目录 一、对IOC和DI的基本认识 (一)理解IoC,即“控制反转” (二)IoC具体做什么?...在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。理解好...
  • 从ATM机到机房收费中 我理解的面向对象

    千次阅读 热门讨论 2014-02-13 17:56:04
    以下是我目前的理解知道是否正确,请各位多多指点。 以第一遍敲机房收费系统的思路来画UML一定是不对的。vb的基于对象是指其对一些控件 按钮之类的这些界面对象的处理。比如,我们可以说,按钮,属性有(名称,...
  • 理解正确使用Java中的断言

    万次阅读 2016-09-21 19:41:33
     理解断言最重要的一点是必须依赖它们完成任何程序实际所需的行为。理由是正常发布的代码都是断言无效的,即正常发布的代码中断言语句都执行的(或起作用的),如果一小心,我们可以错误地使用断言,如: ...
  • 对象序列化原因的简单理解

    千次阅读 2014-06-11 20:39:42
    序列化和反序列化我们可能经常会听到,其实通俗一点的解释,序列化就是把一个对象保存到一个文件或数据库字段中去,其最终目的都是将内存中的对象持久化或者是在网络上传输。反序列化就是在适当的...尽管使用序列化
  • 深入理解Java并发之synchronized实现原理

    万次阅读 多人点赞 2017-06-04 17:44:44
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ... 出自【zejian的博客】...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深...
  • 我对面向过程,对象,接口,切面的理解 --模范青蛙   1.面向过程 面向过程是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想.可以说面向过程是一种基础的方法.它考虑的是实际地实现.一般的...
  • 深入理解Java类型信息(Class对象)

    千次阅读 多人点赞 2019-05-19 19:36:01
    RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息。这里分两种: ...
  • 说明提到面向对象,总是离开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述Python对面向对象的支持。在讨论Python的OOP...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 523,514
精华内容 209,405
关键字:

对象的理解不正确的是