JS(4) JS面向对象编程

创建对象四种方式:
   1.调用系统的构造函数创建对象
   var 变量名= new Object(); //Object 是系统的构造函数
   
        //实例化对象
        var obj = new Object();
        //添加属性
        obj.name = "调用系统构造函数创建的对象的属性";
        //添加方法
        obj.myFunction = function () {
          console.log("调用系统构造函数创建的对象的方法");
        };
        console.log(obj.name);//获取属性
        //方法的调用
        obj.myFunction();
        
   2.工厂模式创建对象
        //工厂模式创建对象
        function createObject(name) {
          var obj = new Object();//创建对象
          //添加属性
          obj.name = name;
          //添加方法
          obj.myFunction = function () {
            console.log("工厂模式创建对象,并调用方法,属性为:" + this.name);
          };
          return obj;//必须返回创建对象
        }
        //创建一个对象
        var per1 = createObject("工厂模式创建对象");
        per1.myFunction();

   3.自定义构造函数创建对象
       function Person(name,age) {
          this.name=name;
          this.age=age;
          this.sayHi=function () {
            console.log("我叫:"+this.name+",年龄是:"+this.age);
          };
        }

        //利用自定义构造函数创建实例对象
        var obj=new Person("小明",10);
        console.log(obj.name);
        console.log(obj.age);
        obj.sayHi();
    
   4.字面量的方式创建对象
    var obj={
      name:"小明",
      eat:function () {
        console.log("吃了");
      }
    };
    console.log(obj.name);
    obj.sayHi();
    
总结:
    到目前为止,如果要创建对象,最好的方式是用构造函数,他的封装性、代码的可重用性最高。
    解决存储空间浪费的问题,可通过原型来解决(数据共享,节省内存空间)
    
调用对象的方法和获取属性的方式:
    对象名.属性名;
    对象名.方法名();
    
    对象名[属性名];
    对象名[方法名]();
    
    例子:
    var obj={
      name:"小明",
      eat:function () {
        console.log("吃了");
      }
    };
    console.log(obj.name);
    obj.sayHi();
    
    console.log(obj[name]);
    obj[sayHi]();
    
基本类型和对象类型:
    基本类型(简单类型),值类型: number,string,boolean,undefined,null
    复杂类型(引用类型):object,自定义对象

    基本类型数据存储在栈中,值类型之间传递,传递的是值
    引用类型在栈中存储地址,指向堆内存的实际数据,引用类型之间传递,传递的是地址(引用)

    var num=10;//值类型,值在栈上
    var obj={};//复杂类型,对象在堆,地址在栈

上述四种创建对象的优缺点:
    系统构造函数创建对象(字面量方式创建对象)的缺点:创建对象和为对象添加属性方法时,代码不能很好的封装,且如果要创建多个拥有相同属性和方法的对象,重复代码太多
    自定义构造函数创建对象(工厂方法创建对象):代码有了较好的封装,创建多个对象时,也只需多new(调用工厂方法)几次而已,但和上面2种方法一样,有存储空间上的浪费,比如这个构造函数中有1个方法,那么这1个方法在实例化对象的时候,都会被分别存储到每个对象的堆内存中。
    
    在java中,对象变量在栈中保存引用,指向堆中的空间;而方法保存在方法区中,供所有对象进行调用,即对象能共享类的方法。JS中可通过原型来添加方法,解决数据共享的问题
    
原型:
    function Person(name,age) {
      this.name=name;
      this.age=age;
    }
    //通过原型来添加方法,解决数据共享,节省内存空间
    Person.prototype.eat=function () {
      console.log("吃凉菜");
    };
    var p1=new Person("小明",20);

    console.dir(p1);//控制台打印对象
    console.dir(Person);
    console.log(p1.__proto__==Person.prototype);//true
    
    实例对象p1中有__proto__这个属性,他就是一个原型对象,但这个属性是给浏览器使用,不是标准的属性
    构造函数中有prototype这个属性,他也是一个原型对象,这个属性是给程序员使用,是标准的属性。
    
    即实例对象中有一个属性叫__proto__,他是一个原型对象;构造函数中有一个属性叫prototype,他也是一个原型对象;构造函数同时也是对象(函数对象),因此构造函数也有一个叫__proto__的原型对象属性。
    
    实例对象、构造函数、原型对象三者的关系:
    * 构造函数中有一个属性叫prototype,指向的是构造函数的原型对象
    * 原型对象中有一个constructor构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数(即原型对象和构造函数是相互引用的)
    * 实例对象中有一个叫__proto__的属性,指向的是该构造函数执行的原型对象,即实例对象的原型和构造函数的原型指向的是同一个原型对象。
    * 构造函数的prototype属性指向的原型对象中的方法是可以被实例对象直接访问的
    
    什么样的数据是需要写在构造方法中?什么样的数据是需要写在原型中?
    不需要共享的数据写在构造函数中,需要共享的数据写在原型中。原型中的方法,可以相互调用,也可以通过this调用构造函数中的属性或方法
    
    原型示例:
    //构造函数
    function Student(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    //为类型指定原型属性
    Student.prototype = {
      //手动修改构造器的指向 如果没有这个,则构造方法和原型对象就不是相互引用的关系了
      constructor:Student,
      height: "188",
      weight: "55kg",
      study: function () {
        console.log("学习好开心啊");
      }
    };
    //为原型添加方法 一般建议将所有原型方法都写在上面的原型属性对象中
    Student.prototype.eat = function () {
        console.log("我要吃好吃的");
    };
    
    var stu=new Student("test1",20,"男");
    stu.eat();
    stu.study();
    console.dir(Student);
    console.dir(stu);
    
    实例对象查找名字时,优先在实例对象中查找属性或者方法,找到了则直接使用,找不到则,去实例对象的__proto__指向的原型对象prototype中找,找到了则使用,找不到则报错
    
    利用实例对象,我们可以为JS内置对象添加原型方法
    String.prototype.sayHi=function () {
      console.log(this+" hello world!");
    };

    //字符串就有了打招呼的方法
    var str2="dcz";
    str2.sayHi();//dcz hello world!
    
如何将局部变量转换成全局变量:
    把局部变量给window就可以了,例子:
    var myFunction=function(){
        var test="hello";
        window.myTest=test;
    }
    myFunction();//必须要调用方法,否则输出的是undefined
    console.log(myTest);
    
原型链
    原型链是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的。
    如果存在继承关系,那么子类实例对象的__proto__会指向父类的原型对象,而父类的原型对象的__proto__会指向性爷爷类的原型对象,一直到Object对象的原型。这在继承中会再次说到。


原型的指向是否可以改变?
    原型指向可以改变。实例对象的原型__proto__指向的是该对象所在的构造函数的原型对象,构造函数的原型对象(prototype)指向如果改变了,实例对象的原型(__proto__)指向也会发生改变。
    
    //人的构造函数
    function Person(age) {
      this.age=10;
    }
    //人的原型对象方法
    Person.prototype.eat=function () {
      console.log("人在吃东西");
    };
    //学生的构造函数
    function Student() {

    }
    Student.prototype.sayHi=function () {
      console.log("学生之间打招呼的方式可能比较特殊");
    };
    //学生的原型,指向了一个人的实例对象
    Student.prototype=new Person(10);
    var stu=new Student();
    stu.eat();
    stu.sayHi();//报错,说stu对象没有sayHi这个方法。但如果包sayHi的方法移动到修改原型指向代码的后面,则不会报错了
    原因:因为sayHi是学生构造函数原型对象中的方法,而学生构造函数的原型发生了改变,指向了Person的原型对象了,因此整个原型链中是找不到这个方法,所以就报错了,而在这个原型链中,可以找到eat的方法,所以调用eat方法是可以的(改变原型指向,感觉就像java中的继承了,Student成了Person的子类)。
    
    在这个例子中:
    stu.__proto__--->Person.prototype--->Person.prototype.__proto__--->Object.prototype-->Object.prototype没有__proto__,所以,Object.prototype中的__proto__是null
    可以看成Student是Person的子类,Person是Object的子类,Object没有父类了。
    
继承:
    继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承。
    继承也是为了数据共享,js中的继承也是为了实现数据共享。    
    原型作用之一:数据共享,节省内存空间
    原型作用之二:为了实现继承
    
    通过改变原型指向实现的继承:
    function Person(name,age,sex,weight) {
         this.name=name;
         this.age=age;
         this.sex=sex;
         this.weight=weight;
    }
    Person.prototype.sayHi=function () {
      console.log("您好:"+this.name);
    };
    function Student(score) {
      this.score=score;
    }
    //把student的原型指向了Person实例,实现了继承,但同时也直接初始化了父类的属性,继承过来的属性的值都是一样的了,所以,这就是问题
    Student.prototype=new Person("小明",10,"男","50kg");
    
    var stu1=new Student("100");
    console.log(stu1.name,stu1.age,stu1.sex,stu1.weight,stu1.score);
    stu1.sayHi();
    
    var stu2=new Student("120");
    stu2.name="张三";
    stu2.age=20;
    stu2.sex="女";
    console.log(stu2.name,stu2.age,stu2.sex,stu2.weight,stu2.score);
    stu2.sayHi();
    var stu3=new Student("130");
    console.log(stu3.name,stu3.age,stu3.sex,stu3.weight,stu3.score);
    stu3.sayHi();
    
    借用构造函数实现继承:
    function Person(name, age, sex, weight) {
      this.name = name;
      this.age = age;
      this.sex = sex;
      this.weight = weight;
    }
    Person.prototype.sayHi = function () {
      console.log("您好");
    };
    function Student(name,age,sex,weight,score) {
      //借用构造函数,类似java中的super(name,age,sex,weight)
      Person.call(this,name,age,sex,weight);
      this.score = score;
    }
    var stu1 = new Student("小明",10,"男","10kg","100");
    console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);

    var stu2 = new Student("小红",20,"女","20kg","120");
    console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);

    var stu3 = new Student("小丽",30,"妖","30kg","130");
    console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
    
    这种方式解决了属性继承,并且值不重复的问题,但父级中的方法没有被继承。
    
    组合继承:即用上面2种方式进行结合,实现继承
    function Person(name, age, sex, weight) {
      this.name = name;
      this.age = age;
      this.sex = sex;
      this.weight = weight;
    }
    Person.prototype.sayHi = function () {
      console.log("人类的打招呼的方法,您好:"+this.name);
    };
    function Student(name,age,sex,weight,score) {
      //借用构造函数,类似java中的super(name,age,sex,weight)
      Person.call(this,name,age,sex,weight);
      this.score = score;
    }
    //修改student的原型指向person实例
    Student.prototype=new Person();//不传值
    Student.prototype.display=function(){
        console.log("学生类的显示数据的方法",this.name, this.age, this.sex, this.weight, this.score);
    }
    var stu1 = new Student("小明",10,"男","10kg","100");
    stu1.display();
    stu1.sayHi();

    var stu2 = new Student("小红",20,"女","20kg","120");
    stu2.display();
    stu2.sayHi();

    var stu3 = new Student("小丽",30,"妖","30kg","130");
    stu3.display();
    stu3.sayHi();
    
    这种方式既能实现属性的继承,又能实现方法的继承。
    
如何通过代码找出对象间的继承关系:
    1、看构造函数中是否有 父类构造函数名.call(this,其他变量列表)或者父类构造函数名.apply(this,[其他变量列表]);如Person.call(this,name,age,sex,weight);
    2、看构造函数的原型指向是否改变,子类构造函数名.prototype=new 父类构造函数();如Student.prototype=new Person();//不传值
    
函数声明和函数表达式的区别:
    示例代码1:
    //函数声明
    if(true){
     function f1() {
       console.log("第一个");
     }
    }else{
     function f1() {
       console.log("第二个");
     }
    }
    f1();
    //在谷歌和火狐中输出的是 第一个,但IE8中是 第二个 原因:函数声明提升后,后面的重名函数会覆盖前面声明的函数。
    
    //函数表达式
    var ff;
    if(true){
      ff=function () {
        console.log("第一个");
      };
    }else{
      ff=function () {
        console.log("第二个");
      };
    }
    ff();//输出的都是第一个
    
    实例代码2:
    //函数声明
    f1();//正常输出
    function f1() {
      console.log("函数声明");
    }
   
    //函数表达式
    ff();//报错,说ff不是一个方法
    var ff;
    ff=function () {
        console.log("函数表达式");
    };
    原因:函数提升问题,函数声明提升不提升没有影响,但函数表达式提升后,只是一个变量,因此报错说他不是一个方法
    
函数中this指向的问题:
    普通函数调用    中的this是谁?-----window,因为页面中的所有对象都属于window
    定时器方法调用  中的this是谁?----window,因为页面中的所有对象都属于window
    
    对象.方法调用   中的this是谁?----实例对象,即谁调用方法this就是谁,如 p.sayHi();那么this就是p
    构造函数        中的this是谁?-----实例对象,new操作结果赋值给谁就是谁,如 var p=new Person();那么this就是p
    原型对象方法    中的this是谁?---实例对象,即谁调用方法this就是谁,如 p.sayHi();那么this就是p
    
函数调用的方式:
    函数分为普通函数、函数表达式、构造函数、对象的方法
    //普通函数
    function f1() {
      console.log("我是普通函数");
    }
    f1();//普通函数的调用
    //函数表达式
    var ff=function(){
        console.log("我是函数表达式");
    }
    ff();

    //构造函数
    function F1() {
      console.log("我是构造函数");
    }
    //通过new 来调用,创建对象实例
    var f=new F1();

    //对象的方法
    function Person() {
      this.play=function () {
        console.log("我是对象的方法");
      };
    }
    var per=new Person();
    per.play();//对象的方法,实例.方法();
    
函数也是一种数据类型:
    既然函数可以作为参数也可以作为返回值,还可以将函数保存到数组中,那么可以确定函数也是一种数据类型,函数同时也是实例对象。
    function f1() {
      console.log("我是函数");
    }
    var array=[f1];
    var num=10;
    console.log(typeof num);//number类型
    console.log(typeof f1);//function类型
    console.dir(f1);//f1中有prototype属性,说明f1函数也是构造函数,f1中也有__proto__,说明f1也是一个实例对象
    
apply、call、bind
    作用:改变方法内部this的指向。不管是普通函数、构造方法、对象的方法,内部都可以使用this关键字
    对于普通方法,this为window,页面中的所有对象都是window的。
    构造函数,this为new出来的实例对象。 
    对象方法,this为调用对象方法的实例对象
    
    只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply、call或者bind的方法改变this的指向
    
    函数名字.apply(对象,[参数1,参数2,...]);返回值为函数对应的返回值
    方法名字.apply(对象,[参数1,参数2,...]);返回值为函数对应的返回值
    call的使用语法
    函数名字.call(对象,参数1,参数2,...);返回值为函数对应的返回值
    方法名字.call(对象,参数1,参数2,...);返回值为函数对应的返回值
    bind的使用语法
    函数名字.bind(对象,参数1,参数2,...);返回值是复制之后的这个函数
    方法名字.bind(对象,参数1,参数2,...);返回值是复制之后的这个方法
    
    例子:
    function f1(x, y) {
      console.log((x + y) + ":=====>" + this.age);
    }
    function Person(age) {
      this.age=age;
    }
    Person.prototype.play=function () {
      console.log(this+"====>"+this.age);
    };
    function Student(age) {
      this.age=age;
    }
    var per=new Person(10);
    var stu=new Student(20);
    var ff1=f1.apply(per,[10,20]);//相当于执行 f1(10,20);只是f1方法内部的this不再是window而是per实例对象 输出30===>10
    var ff2=per.play.apply(stu);//相当于执行per.play();只是per.play方法内部的this不再是per实例对象而是stu实例对象 输出stu==>20
    var ff3=f1.call(per,100,200);//相当于执行 f1(10,20);只是f1方法内部的this不再是window而是per实例对象  输出30===>10
    var ff4=per.play.call(stu);//相当于执行per.play();只是per.play方法内部的this不再是per实例对象而是stu实例对象  输出stu==>20
    
    //复制了一份
    var ff1=f1.bind(per,1000,2000);//var ff1=f1.bind(per);也可以写成这样,那么在调用ff1时就需要传实参了,即ff1(1000,2000);
    ff1();//输出3000:=====> 10(per实例对象的年龄)
    //复制了一份
    var ff2=per.play.bind(stu);
    ff2();//输出20,stu实例对象的年龄
    
函数中的几个成员:
    函数中有一个name属性----->函数的名字,name属性是只读的,不能修改
    函数中有一个arguments属性--->实参伪数组
    函数中有一个length属性---->函数形参的个数
    函数中有一个caller属性---->调用者(f1函数在f2函数中调用的,所以,此时调用者就是f2)
    
    
    
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值