美文网首页
JavaScript面向对象

JavaScript面向对象

作者: 风暴java之灵 | 来源:发表于2019-04-24 09:24 被阅读0次

    创建默认对象

    1. JavaScript中提供了一个默认的类Object,我们可以通过这个类创建对象
    2. 由于是用系统默认的类创建的对象,系统不知道我们需要给对象添加什么属性和方式,所以需要手动的添加我们需要的属性和方法
    3. 如何给一个对象添加属性
      对象名.属性名 = 值 ;
    4. 如何给一个对象添加方法
      对象名.方法名 = function (){
      代码;
      }
    • 创建对象的方法
      第一种方法
    let obj = Object(); //创建一个名为obj的对象
    obj.name = "wj";//给obj对象添加一个name的属性
    obj.say = function(){
          console.log("我是对象");
    }
    obj.say();// 结果  我是对象
    

    第二种方法

    let obj = {}; //创建一个名为obj的对象
    obj.name = "wj";//给obj对象添加一个name的属性
    obj.say = function(){
          console.log("我是对象");
    }
    obj.say();// 结果  我是对象
    

    第三种方法
    注意: 属性名称和取值之间用冒号隔开, 属性和属性之间用逗号隔开

    let obj = {; //创建一个名为obj的对象
    name : “wj” ,//给obj对象添加一个name的属性
    say : function(){
          console.log("我是对象");
    }
    }
    obj.say();// 结果  我是对象
    

    函数和方法的区别

    函数: 没有和其他类绑定在一起的
    方法: 和其他类绑定在一起的
    函数可以直接调用,方法只能通过对象调用

    • 函数和方法中都有一个叫 this 的东西
      谁调用了当前的方法或者函数,this就是谁
    let obj = { //创建一个名为obj的对象
    name : "wj" ,
    say : function(){
       console.log(this);
    }
    }
    obj.say();  //结果为  obj对象
    

    工厂函数

    专门用于创建对象的函数被称为工厂函数.

    function createPerson(myName,myAge) { //创建工厂函数
           let obj = new Object();
           obj.name = myName;
           obj.age = myAge;
           obj.say = function(){
          console.log("我是工厂函数")   
    }
    return obj; //返回对象
    }
    let obj = createPerson("wj",18);
    let obj2 = createPerson("李四",22);
    console.log(obj);
    console.log(obj2);
    

    构造函数

    1. 构造函数也是用来创建对象的
      构造函数本质是工厂函数的简写
    2. 构造函数和工厂函数的区别
      构造函数的函数名称首字母必须大写
      构造函数只能通过new来调用
    function Person (myName,myAge) {
    //系统会自动添加  let obj = new Object();  let this = obj;
           this.name = myName;
           this.age = myAge;
           this. say = function (){
                  console.log("我是构造函数");
    }
    //系统自动添加  return this;
    }
    let obj = new Person("文件",18);
    let obj1 = new Person("wj",21);
    console.log(obj);
    console.log(obj1);
    
    • 构造函数的优化
      每个构造函数中都有一个叫prototype的东西
      为了解决性能问题,我们可以将被创建的对象共有的属性和方法放到prototype中
    function Person (myName,myAge) {
           this.name = myName;
           this.age = myAge;
    }
    Person.prototype = {
          this. say = function (){
                  console.log("我是构造函数");
    }
    }
    
    let obj = new Person("文件",18);
    let obj1 = new Person("wj",21);
        console.log(obj.say===obj1.say);//结果为 true
    obj.say();  //结果为  我是构造函数
    

    prototype的特点

    1. 存储在prototype中的方法和属性可以被对应构造函数创建的对象共享
      2.如果prototype中的存储的方法或者属性和构造函数的同名,那么访问到的是构造函数中的方法或者属性
      prototype的应用场景:主要用来存放所有对象都相同的一些属性以及方法
      如果是对象特有的属性或者方法,一般放到构造函数中

    函数对象关系

    • Function函数狮所有函数的祖先函数,所有对象都是函数
    • 所有构造函数都有一个prototype属性,并指向自己的原型对象
    • 所有原型对象都有一个constructor属性,constructor指向自己的构造函数
    • 所有函数都有proto属性
      普通对象的proto属性指向创建它的构造函数的原型对象
      所有对象的proto属性最终都会指向"Object原型对象"
      Object原型对象proto属性指向"null"
      原型.png

    原型链

    对象中由proto组成的链条,我们称为原型链
    对象在查找属性和方法时,会优先会在自身找,如果没有就会去上一级的原型对象中查找,如果找到Object的原型对象中还没有,就会报错。
    注意:在给prototype赋值的时候,为了不破坏对象中原有的关系,我们需要手动指定constructor指向谁

      function Person (myName,myAge) {
                this.name = myName;
             this.age = myAge;
            }
    
            Person.prototype = {
                constructor:Person,
                say :function () {
                    console.log("我是原型对象");
                }
            }
    let obj = new Person("wj",21);
    console.log(obj.__proto__ === Person.prototype);//结果为 true 
    ##属性注意点
    在JavaScript对象中,如果要给一个对象不存在的属性或者方法设定的值得时候,他不会去原型对象中去查找,而是会给这个对象添加这个不存在的方法或者属性
    ```javaScript  
     function Person (myName,myAge) {
                this.name = myName;
             this.age = myAge;
            }
    
            Person.prototype = {
                constructor:Person,
                 sex: 2,
                say :function () {
                    console.log("我是原型对象");
                }
            }
    let obj = new Person("wj",21);
    console.log(obj.sex);//结果 2
    console.log(obj.__proto__.sex);//结果 2
    obj.sex=1;
    console.log(obj.sex);//结果 1
    console.log(obj.__proto__.sex);//结果 2
    

    三大特性之封装性

    • 什么是封装? 就是隐藏实现的细节,仅对外公开接口
    • 为什么要封装?
    1. 当你的属性暴露在外部时,封装可以防止别人随意的修改你的数据
    2. 封装是将数据隐藏起来,只有用特定的方法才可以设置或者调用数据,不能被外界随意修改,这样降低了数据被误用的可能
      function Person (myName,myAge) {
                this.name = myName;
                let age = 45;
                this.setAge = function (myAge) {
                    if(myAge>0&&myAge<150)
                        age = myAge;
                }
                this.getAge = function () {
                    return age;
                }
    
            }
    
            Person.prototype = {
                constructor:Person,
                say :function () {
                    console.log("我是构造函数");
                }
            }
             let obj = new  Person("wj",21);
            console.log(obj.getAge()); // 结果 21
            obj.setAge(86);
            console.log(obj.getAge());// 结果 86
    
    • 私有属性注意点:
      在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
      由于私有属性的本质就是一个局部变量, 并不是真正的属性, 所以如果通过 对象.xxx的方式是找不到私有属性的, 所以会给当前对象新增一个不存在的属性
      function Person (myName,myAge) {
                this.name = myName;
                let age = 45;
                this.setAge = function (myAge) {
                    if(myAge>0&&myAge<150)
                        age = myAge;
                }
                this.getAge = function () {
                    return age;
                }
    
            }
    
            Person.prototype = {
                constructor:Person,
                say :function () {
                    console.log("我是构造函数");
                }
            }
             let obj = new  Person("wj",21);
            console.log(obj.getAge()); // 结果 21
            obj.setAge(86);
            console.log(obj.getAge());// 结果 86
          obj.age= -3;  //当前对象新增一个不存在的属性age
    console.log(obj.age); //结果为 -3
    

    属性方法分类

    通过实例对象调用的方法或者属性,被称为实例方法或者属性
    通过构造函数调用的方法或属性,被称为静态方法或者属性

    三大特性之继承

    在企业开发中如果构造函数和构造函数之间的关系是is a关系, 那么就可以使用继承来优化代码, 来减少代码的冗余度

    • 继承第一代
      在子类构造函数之后手动设置子类构造函数的prototype属性指向父类的实例对象,为了维持他们之间的关系,也需要设定父类实例对象的constructor指向子类构造函数————
    1. 子构造函数名.prototype = 父类实例对象
    2. 父类实例对象.constructor = 子类构造函数名
      function Person (myName,myAge) {
                this.name = myName;
             this.age = myAge;
            }
    
            Person.prototype = {
                constructor:Person,
                say :function () {
                    console.log("我是原型对象");
                }
            }
    let obj = new Person("wj",21);
     function Student (myName,myAge,myScore) {
               this.score = myScore;
              this.say = function() {
          console.log("我是子类");
            }
    Student.prototype = new Person();
    Student.prototype.constructor = Student;
    let  stu = new Student("w",12,88); //在Student构造函数中找不到myName,myAge对应的属性  
    所以该方法有个弊端  不能在创建Student对象的同时给继承父类属性赋值
    
    • 继承第二代
      this是什么? this是谁调用了函数或者方法,this就指向谁
    • bind方法
      通过bind方法可以改变this的指向 并会新生成一个函数并返回给我们
      格式:需要改变this指向的函数名.bind(新指向的对象)
    function test(){
       name : 12;
    }
    function fn(){
    console.log(this);
    }
    fn();//返回window
    let obj = fn.bind(test);
    obj(); //返回 test
    

    bind方法改变this指向的同时也可以设定参数,只要将设定的参数写在新指向的对象名后面,用逗号隔开

    function test(){
       name : 12;
    }
    function fn(a,b){
    console.log(a,b);
    console.log(this);
    }
    fn(1,10);//返回1 10  window
    let obj = fn.bind(test,2,9);
    obj(); //返回 2 , 9  test
    
    • call方法
      通过call方法改变this的指向 会立即调用修改之后的函数
      格式:需要改变this指向的函数名.call(新指向的对象)
    function test(){
       name : 12;
    }
    function fn(){
    console.log(this);
    }
    fn();//返回window
     fn.call(test);//返回 test
    

    call方法改变this指向的同时也可以设定参数,只要将设定的参数写在新指向的对象名后面,用逗号隔开

    function test(){
       name : 12;
    }
    function fn(a,b){
    console.log(a,b);
    console.log(this);
    }
    fn(1,10);//返回1 10  window
     fn.call(test,2,9); //返回 2 , 9  test
    
    • apply方法
      通过apply方法改变this的指向 会立即调用修改之后的函数
      格式:需要改变this指向的函数名.apply(新指向的对象)
    function test(){
       name : 12;
    }
    function fn(){
    console.log(this);
    }
    fn();//返回window
     fn.apply(test);//返回 test
    

    call方法改变apply指向的同时也可以设定参数,只不过需要用数组来传递参数

    function test(){
       name : 12;
    }
    function fn(a,b){
    console.log(a,b);
    console.log(this);
    }
    fn(1,10);//返回1 10  window
     fn.apply(test,[2,9]); //返回 2 , 9  test
    

    继承方法二 用call方法在子类构建函数中改变父类的this指向,这种方法解决了不能在创建Student对象的同时给继承父类属性赋值

    //建议去温习一下工厂函数和构造函数
      function Person (myName,myAge) {
                //let obj = new Object();
                //let this = obj;
               // this = stu ;
                this.name = myName;
             this.age = myAge;
          // return this;
            }
    let obj = new Person("wj",21);
     function Student (myName,myAge,myScore) {
               //let  stu = new Object();
               //let this = stu;
               Person.call(this,myName,myAge); //Person.call(stu,myName,myAge);
               this.score = myScore;
              this.say = function() {
          console.log("我是子类");
            }
    }
    let stu = new Student("ww",21,99);
     console.log(stu);
    

    弊端:父类构造函数的原型对象中的东西无法获取

    • 继承第三代
      改变子类构造函数的prototype属性指向父类的原型对象,解决了不能获取父类原型对象中的数据的弊端
      function Person (myName,myAge) {
                this.name = myName;
             this.age = myAge;   
            }
      Person.prototype.speak= function () {
                console.log("我是父类的");
            }
     function Student (myName,myAge,myScore) {    
               Person.call(this,myName,myAge); 
               this.score = myScore;
              this.say = function() {
          console.log("我是子类");
            }
    }
    Student.prototype = Person.prototype ;
    Student.prototype.constructor  = Student;
    let stu = new Student("ww",21,99);
     console.log(stu);
    stu.speak();//结果 我是父类的
    

    弊端:破坏了原有对象的三角恋关系,由于父类和子类的原型对象是同一个,如果给子类添加方法或者属性,父类也会同步添加这个方法或者属性

    • 继承第四代终极方案
      方法:
    1. 在子类的构造函数中通过call借助父类的构造函数
    2. 将子类的原型对象修改为父类的实例对象
    该方式解决了继承第三代的弊端
     function Person (myName,myAge) {
                this.name = myName;
             this.age = myAge;   
            }
      Person.prototype.speak= function () {
                console.log("我是父类的");
            }
     function Student (myName,myAge,myScore) {    
               Person.call(this,myName,myAge); 
               this.score = myScore;
              this.say = function() {
          console.log("我是子类");
            }
    }
    Student.prototype =  new Person() ;
    Student.prototype.constructor  = Student;
    let stu = new Student("ww",21,99);
    Student.prototype.run = function(){
                console.log("run");
            }
    let per = new Person();
    per.run(); //报错   解决了继承第三代的弊端
    

    三大特性之多态

    1.什么是强类型语言, 什么是是弱类型语言
    1.1什么是强类型语言:
    一般编译型语言都是强类型语言,
    强类型语言,要求变量的使用要严格符合定义
    例如定义 int num; 那么num中将来就只能够存储整型数据
    1.2什么是弱类型语言:
    一般解释型语言都是弱类型语言,
    弱类型语言, 不会要求变量的使用要严格符合定义
    例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等
    1.3由于js语言是弱类型的语言, 所以我们不用关注多态
    2.什么是多态?
    多态是指事物的多种状态
    例如:
    按下 F1 键这个动作,
    如果当前在 webstorm 界面下弹出的就是 webstorm 的帮助文档;
    如果当前在 Word 下弹出的就是 Word 帮助;
    同一个事件发生在不同的对象上会产生不同的结果。
    3.多态在编程语言中的体现
    父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同

    ES6类和对象

    从ES6开始系统提供了一个名称叫做class的关键字, 这个关键字就是专门用于定义类的
    格式 :
    class 类名 {
    实例属性;
    实例方法;
    静态属性;
    静态方法;
    }
    如果想在创建对象的时候动态的设置属性或者方法,可以在constructor构造函数中设定

    class Person {
    //当创建对象的时候系统会自动调用constructor构造函数
      constructor(myAge) {
                     this.age = myAge;
           }
    name = "wj"; //实例属性
    say(){
    console.log("我是实例方法");
    }
    static sex = 2 ; //静态属性
    static test(){
        console.log("我是静态方法");//静态方法
    }
    }
    let per = new Person(21);
    console.log(per);
    

    但是以上实例属性和静态属性的写法并不是ES6的标准写法,因此大部分的浏览器都不支持
    标准写法是将实例属性写到constructor()里面。
    在ES6的标准中,static只能用来定义静态方法,不能定义静态属性,静态属性只能动态的添加

    class Person {
    //当创建对象的时候系统会自动调用constructor构造函数
      constructor(myAge) {// constructor中的东西相当于ES6之前构造函数中的内容
                     this.age = myAge;
                     this.name = "wj"; //实例属性
                     this.say = function() {
                      console.log("我是实例方法");
                                                               }
           }
    speak(){ //在constructor中的定义的方法会直接存放到原型对象中
    console.log("我在原型对象中");
    }
    static test(){
        console.log("我是静态方法");//静态方法
    }
    }
    Person.sex = 2 ; //静态属性(只能通过这种方法定义静态属性)
    let per = new Person(21);
    console.log(per);
    

    如果通过class定义类, 那么不能自定义这个类的原型对象
    如果想将属性和方法保存到原型中, 只能动态给原型对象添加属性和方法

    class Person {
                constructor(myAge) {
                    this.age = myAge;
                    this.name = "wj"; //实例属性
                    this.say = function(){
                        console.log("我是实例方法");
                    }
                }
                static test(){
                    console.log("我是静态方法");//静态方法
                }
            }
                Person.sex = 2 ;//定义静态变量
        /*Person.prototype.speak = function() {
                   console.log("我是动态添加的");
    }
          let per = new Person(21);
             console.log(per);
            per.speak(); //  结果 我是动态添加的
    */
            let obj ={//定义这个类的原型对象
                constructor: Person ,
                name : 12,
                speak:function() {
                    console.log("我是定义的原型对象");
                }
            }
            Person.prototype = obj ; 
        let per = new Person(21);
            console.log(per);
            per.speak(); //报错
    

    ES6继承

    ES6中有专门的继承方法:通过extends继承
    继承格式:class 子类名 extends 父类名 {}
    子类在constructor函数中可以通过super调用父类的构造函数

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
    speak(){
                console.log(this.age);
            }
        }
        class Student extends Person {//继承
            constructor(myAge,myScore){
                super(myAge);//调用父类的构造函数(相当于Person.call(Student,myAge,myScore))
                this.score = myScore;
            }
            study(){
                console.log("我是学生");
            }
        }
        let stu = new Student(21,99);
      stu.speak();//结果 21
    

    获取对象类型

    如果想知道某个对象是由谁创建的可以通过console.log(对象名.constructor.name);来获取

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
        }
    let obj =  new Person();
        console.log(obj.constructor.name); //结果为  Person
    

    instanceof关键字

    instanceof关键字用于判断对象是否是谁的实例 如果是返回true 否则返回false

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
        }
    let obj =  new Person();
        console.log(obj instanceof Person); //结果为  true
    

    注意 如果判断的构造函数的原型在实例对象的原型链上 也会返回true

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
        }
    class Student extends Person{
               constructor(myAge){
                    super(myAge);
               }
     }
    let obj = new Student(12);
    console.log(obj instanceof Student); //结果为  true
    console.log(obj instanceof Person); //结果为  true
    

    isPrototypeOf

    isPrototypeOf ()用于判断一个对象是否是另一个对象的原型

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
        }
    let obj =  new Person();
        console.log(Person.prototype.isPrototypeOf(obj)); //结果为  true
    

    注意 只要调用者在传入对象的原型链上就会返回true

     class Person {
            constructor(myAge){
                this.age = myAge;
                this.say = function () {
                    console.log("我是父类");
                }
            }
        }
    class Student extends Person{
               constructor(myAge){
                    super(myAge);
               }
     }
    let obj = new Student(12);
    console.log(Person.prototype.isPrototypeOf(obj)); //结果为  true
    console.log(Student.prototype.isPrototypeOf(obj)); //结果为  true
    

    判断对象属性是否存在

    格式: "属性名" in 对象名 :
    如果在对象的类里或者原型对象里有就返回true,否则返回false

    class Person {
      constructor(){
    this.name = "wj";
    }
    }
    Person.prototype.age = 18;
    let obj = new Person();
    console.log("name" in obj);//true
    console.log("age" in obj);// true
    

    格式 对象名.hasOwnProperty("属性名");
    该方法值会在类本身查找,不会去原型对象中查找

    class Person {
      constructor(){
    this.name = "wj";
    }
    }
    Person.prototype.age = 18;
    let obj = new Person();
    console.log(obj.hasOwnProperty("name"));//true
    console.log(obj.hasOwnProperty("age"));// false
    

    对象的增删改查

    增添改操作
    有两种格式:

    1. 对象名.属性/方法名 = 值;
    2. 对象名["属性/方法名"] = 值;
      删除操作
      两种格式:
    3. delete 对象名.属性/方法名;
    4. delete 对象名["属性/方法名"];
    class Person{
    }
    let obj = new Person();
    obj["name"] = "wj"; //添加属性
    obj["say"] = function (){ //添加方法
          console.log("我是添加的")
    }
    delete  obj["name"]; //删除属性
    

    对象遍历

    通过高级for循环可以拿到对象中所有的属性和方法名称
    格式 for(let key in 对象名){//会将对象中所有的方法和属性取出来依次赋值给key

    }

    ```javaScript
     class Person {
            constructor(myAge,myName){
                this.age = myAge;
                this.name = myName;
                this.say = function () {
                    console.log("我是父类");
                }
            }
    speak(){ // 该方法存放在原型对象中
       console.log("我在原型对象中");
    }
        }
    let obj = new Person(21,"wj");
    for(let key in obj) {
        //console.log(obj.key); //结果为undefined(p.key意思是obj中名为
    key的属性,obj中并没有,所以结果为未定义)
       console.log(obj[key]);//相当于obj["key"]  = obj["age"] 取出的才是属性和方法
    }//结果中没有speak方法是因为高级for循环不会载原型对象中查找
    
    //如果不想取出对象中的方法可以通过如下方法来实现
     class Person {
            constructor(myAge,myName){
                this.age = myAge;
                this.name = myName;
                this.say = function () {
                    console.log("我是父类");
                }
            }
    speak(){ // 该方法存放在原型对象中
       console.log("我在原型对象中");
    }
        }
    let obj = new Person(21,"wj");
    for(let key in obj) {
    if(obj[key] instanceof Function){
    continue;
    }
       console.log(obj[key]);
    }
    

    对象解构

    对象解构和数组一样
    不同在于:
    1.符号不同,数组用[], 对象用{};

    1. 对象解构中左边的变量名称必须和右边的一致,否则解构不出来
     class Person {
            constructor(myAge,myName){
                this.age = myAge;
                this.name = myName;
                this.say = function () {
                    console.log("我是父类");
                }
            }
    }
    let obj = new Person(21,"wj");
    let {name,age} = obj ;
     console.log(age,name);//  21 "wj"
    

    应用场景 数组或者对象赋值的时候简化代码

    class Person {
            constructor(myAge,myName){
                this.age = myAge;
                this.name = myName;
                this.say = function () {
                    console.log("我是父类");
                }
            }
    }
    let obj = new Person(21,"wj");
    function speak({name,age}) {
    console.log(name,age);
    }
    speak(obj); //  "wj"   21
    

    深拷贝和浅拷贝

    深拷贝是指给给新变量赋值时,不会影响原先变量的值
    默认情况下基础类型的都是深拷贝
    浅拷贝是指给给新变量赋值时,也会改变原先变量的值
    默认情况下引用类型的都是浅拷贝

    let num1 = 123;           //深拷贝
            let num2 = num1;
            num2 = 666; // 修改形变量的值
            console.log(num1); // 123
            console.log(num2);  //666
    class Person{     //浅拷贝
                name = "lnj";
                age = 34;
            }
            let p1 = new Person();
            let p2 = p1;
            p2.name = "zs"; // 修改变量的值
            console.log(p1.name);// "zs"
            console.log(p2.name);  "zs"
    

    对象深拷贝

    如果对象中只有基础类型的属性的话可以通过assign方法实现
    assign可以将第二个参数的对象的属性拷贝到第一个参数的对象中

     class Person {
           constructor(myAge, myName) {
               this.age = myAge;
               this.name = myName;
               this.say = function () {
                   console.log("我是父类");
               }
           }
       }
       class Student {
    
       }
           let obj = new Person(21,"wj");
            let p2 = new Object();
            Object.assign(p2,obj);//深拷贝
             console.log(p2);
    

    如果对象中含有方法的话,用上面的办法就能完全的深拷贝
    这个时候就需要我们自定义一个方法来实现深拷贝

    function deepCopy(a,b) {//自定义方法实现对象深拷贝
               for(let key in b){// 1.通过遍历拿到source中所有的属性
                 let bValue = b[key];// 2.取出当前遍历到的属性对应的取值
               if(b[key] instanceof Object){// 3.判断当前的取值是否是引用数据类型
                     a[key] = new bValue.constructor;
                         deepCopy(a[]key],bValue);//运用递归拷贝对象
                         }
    
                 else {
                           a[key] = bValue; 
                        }   
    }
    }
    
    

    相关文章

      网友评论

          本文标题:JavaScript面向对象

          本文链接:https://www.haomeiwen.com/subject/iepbgqtx.html