JavaScript对象进阶

170 阅读12分钟

一 面向对象思想:

概念:

  • 面向对象简单的说,就是对面向过程的一个封装,就是注重结果
  • 例如:我们做一个轮播图,面向过程(缺点:代码冗余,开发效率慢),我们要一步一步的去分析他的思路,然后一步步的实现。这就是面向过程的思想,而面向对象(缺点:看不到源码,不易维护)的思想就是找框架,或者库,直接拿来用,实现功能

面向对象三种特征:

  • 1:封装:把代码放入对象的方法中
  • 2:继承:一个对象拥有另一个对象的所以成员
  • 3:多态:不同的情况,状态展现也不同

二 内置对象以及方法

数组

  1. length 属性 动态获取数组长度
  2. join() 将一个数组转成字符串。返回一个字符串。
  3. reverse() 将数组中各元素颠倒顺序
  4. delete 运算符 只能删除数组元素的值,而所占空间还在,总长度没变(arr.length)。
  5. shift() 删除数组中第一个元素,返回删除的那个值,并将长度减 1。
  6. pop() 删除数组中最后一个元素,返回删除的那个值,并将长度减 1。
  7. unshift() 往数组前面添加一个或多个数组元素,长度要改变。
  8. push() 往数组结尾添加一个或多个数组元素,长度要改变。
  9. concat( ) 连接数组
  10. slice( ) 返回数组的一部分
  11. sort( ) 对数组元素进行排序
  12. splice( ) 插入、删除或替换数组的元素
  13. toLocaleString( ) 把数组转换成局部字符串
  14. toString( ) 将数组转换成一个字符串
  15. forEach 遍历所有元素

ES6

  1. forEach() 遍历数组的所有元素
  2. every() 判断所有元素是否都符合条件
  3. sort() 数组排序
  4. map () 对元素重新组装,生成新数组
  5. filter() 过滤符合条件的元素

Object 基础对象

  1. Object 含有所有 JavaScript 对象的特性的超类
  2. Object.constructor 对象的构造函数
  3. Object.hasOwnProperty( ) 检查属性是否被继承
  4. Object.isPrototypeOf( ) 一个对象是否是另一个对象的原型
  5. Object.propertyIsEnumerable( ) 是否可以通过 for/in 循环看到属性
  6. Object.toLocaleString( ) 返回对象的本地字符串表示
  7. Object.toString( ) 定义一个对象的字符串表示
  8. Object.valueOf( ) 指定对象的原始值

String 字符串对象

  1. Length 获取字符串的长度。如:var len = strObj.length
  2. toLowerCase() 将字符串中的字母转成全小写。如:strObj.toLowerCase()
  3. toUpperCase() 将字符串中的字母转成全大写。如:strObj.toUpperCase()
  4. charAt(index) 返回指定下标位置的一个字符。如果没有找到,则返回空字符串
  5. substr() 在原始字符串,返回一个子字符串
  6. substring() 在原始字符串,返回一个子字符串
区别:'''

“abcdefgh”.substring(2,3) = “c”

“abcdefgh”.substr(2,3) = “cde”
  1. split() 将一个字符串转成数组
  2. charCodeAt( ) 返回字符串中的第 n 个字符的代码
  3. concat( ) 连接字符串
  4. fromCharCode( ) 从字符编码创建—个字符串
  5. indexOf( ) 返回一个子字符串在原始字符串中的索引值(查找顺序从左往右查找)。如果没有找到,则返回-1
  6. lastIndexOf( ) 从后向前检索一个字符串
  7. localeCompare( ) 用本地特定的顺序来比较两个字符串
  8. match( ) 找到一个或多个正则表达式的匹配
  9. replace( ) 替换一个与正则表达式匹配的子串
  10. search( ) 检索与正则表达式相匹配的子串
  11. slice( ) 抽取一个子串
  12. toLocaleLowerCase( ) 把字符串转换小写
  13. toLocaleUpperCase( ) 将字符串转换成大写
  14. toLowerCase( ) 将字符串转换成小写
  15. toString( ) 返回字符串
  16. toUpperCase( ) 将字符串转换成大写
  17. valueOf( )

Boolean 布尔对象

  1. Boolean.toString( ) 将布尔值转换成字符串
  2. Boolean.valueOf( ) Boolean 对象的布尔值

Date 日期时间

  1. var today = new Date();创建 Date 对象
  2. Date.getDate( ) 返回一个月中的某一天
  3. Date.getDay( ) 返回一周中的某一天
  4. Date.getFullYear( ) 返回 Date 对象的年份字段
  5. Date.getHours( ) 返回 Date 对象的小时字段
  6. Date.getMilliseconds( ) 返回 Date 对象的毫秒字段
  7. Date.getMinutes( ) 返回 Date 对象的分钟字段
  8. Date.getMonth( ) 返回 Date 对象的月份字段
  9. Date.getSeconds( ) 返回 Date 对象的秒字段
  10. Date.getTime( ) 返回 Date 对象的毫秒表示

Error 异常对象

  1. Error.message 可以读取的错误消息
  2. Error.name 错误的类型

Math 数学对象

  1. Math.abs() 绝对值
  2. Math.ceil() 向上取整(整数加 1,小数去掉)
  3. Math.floor() 向下取整(直接去掉小数)
  4. Math.round() 四舍五入

Number 数值对象

  1. Number.MAX_VALUE 最大数值
  2. Number.MIN_VALUE 最小数值

RegExp 正则表达式对象

  1. RegExp.exec( ) 通用的匹配模式
  2. RegExp.global 正则表达式是否全局匹配
  3. RegExp.ignoreCase 正则表达式是否区分大小写
  4. RegExp.lastIndex 下次匹配的起始位置
  5. RegExp.source 正则表达式的文本
  6. RegExp.test( ) 检测一个字符串是否匹配某个模式
  7. RegExp.toString( ) 把正则表达式转换成字符串

三 工厂函数与构造函数

01:场景:创建多个对象 let obj1 let obj2 let obj3,那我创建100个对象呢

    // 我要创建多个对象,代码冗余
    let  obj1={
    name:'张三',
      age:19,
      sex:'男'
    }
    let obj2={
    name :'李四',
     age:17,
     sex:'女'
    }
    let obj3={
   name :',王五',
    age:17,
    sex:'男'
    }
    .......100个对象

工厂函数(用于创建对象的函数)

02:面对这种场景,代码的冗余,其实可以用函数来解决,像这种函数呢我们就叫做工厂函数。像工厂流水线一样,批量创建对象

    function person(name,age,sex){
        let p={}
        // 对象赋值
        p.name=name,
        p.age=age,
        p.sex=sex
        return p
    }
    // 使用函数解决,如果添加,添加形参就行了。大大提高了灵活性和减少了代码冗余
 let person1=person('张三',30,'男')
 let person2=person('李四',40,'女')
 let person3=person('王五',60,'男')
 console.log(person1,person2,person3);

03:但是上面的工厂函数还是有问题代码还是冗余,而且变量声明过多会造成变量污染,接下来我们用构造函数来实现对象的创建。进行对比

构造函数(使用new关键字调用的函数)

function person(name,age,sex){
        // 对象赋值
        this.name=name,
        this.age=age,
        this.sex=sex
    }
    // 使用构造函数解决
 let person1= new person('张三',30,'男')
 let person2= new person('李四',40,'女')
 let person3= new person('王五',60,'男')
 console.log(person1,person2,person3);;

接下来就来分析new关键字干了什么,加了new有什么用

我创建一个空的普通函数和构造函数进行对比

 function fn(){
  this.a='小明'
 }
 let f1=fn()
 let f2= new fn()

 console.log(f1,"f1普通函数");
 console.log(f2,"f2构造函数");

从输出输出结果来:构造函数创建了一个对象,并赋值,然后返回了这个对象。

image.png

总结 new 关键字做了4件事

1 创建了一个空对象

2 this指向这个对象, this={}

3 给这个对象赋值

4 返回这个对象 return this=()

在构造函数中returen 还可以用吗,接下来我们一起看?

        function fn(){
            this.a='小明'
            return 333333
 }
         let f2= new fn()
         console.log(f2,"我return了33333的结果"); 

看输出得知,没有用,依旧输出的是new创建的对象,

image.png

接下我们来看return引用类型可不可以

        function fn(){
            this.a='小明'
            return [1,2,3,4,5,6]
 }
 
        let f2= new fn()
        console.log(f2,"return了这个数组");  

最后发现是可以return的

image.png

总结:

1:在构造函数中使用return 发现return值类型无效,返回的还是new创建的对象

2:return引用类型,有效,会覆盖new创建的对象

四 原型对象

接下来探究一下原型对象的前世今生

原型对象的前世

原型对象的出现是为了解决构造函数的内存浪费

首先看构造函数是怎么出现内存的浪费的,引发构造函数内存浪费的不是构造函数本身,而是构造函数里面定义的方法。

    function Person(name,age){
            this.name=name,
            this.age=age
            this.hook=function(){
             console.log("我的爱好是打球");
             }
           } 
//     2:这里的p1和p2是就是实例对象,调用构造函数之后返回的那个对象。
        let p1= new Person('张三',30)
        let p2= new Person('李四',30)
       // p1和p2是同一个函数吗
       console.log(p1.hook===p2.hook);
console.log(p1.hook===p2.hook);//false

从打印出来看,p1和p2并不是同一个函数,这也就是构造函数里面的方法造成内存浪费的最直接的原因,其原因就是每调用一次构造函数,构造函数中的方法hook,都会重新开辟一个内存空间,但里面保存的值是一样的,但是这两个方法已经不是同一个内存地址了所以也就不是同一个函数了,所以这就造成了内存浪费 )

image.png

补充点

  • 1:构造函数我们有一个约定俗成的规范,就是构造函数首字母大写
  • 2:这里的p1和p2是就是实例对象,调用构造函数之后返回的那个对象

原型对象的由来

前面我们分析构造函数里面的方法,每调用一次方法就会执行一次同时就会开辟一个空间,我希望的是我当调用100次这个构造函数,这个构造函数的方法始终只调用一次。

    function fn(){
             console.log("我的爱好是打球");
             }
      
        function person(name,age){
            this.name=name,
            this.age=age
            // 
             this.hook=fn
             
           } 
    
        let p1= new person('张三',30)
        let p2= new person('李四',30)
       console.log(p1,p2);
       // p1和p2是同一个函数吗
       console.log(p1.hook===p2.hook);
  console.log(p1.hook===p2.hook); //true 

现在p1就等于p2,原因是我们把方法拿到外面声明成了全局函数,现在我在调用构造函数,构造函数里面的hook方法并没有重新执行,而是取出全局函数的fn的内存地址进行赋值,所以p1和p2的hook方法都是指向的是同一个

  • fn不加括号,拷贝的是他的内存地址
  • fn()加括号,拷贝的是他的值

全局函数产生的变量污染问题

  • 上面我们解决了构造函数的内存浪费问题,但是现在产生了一个新的问题,那就是全局函数产生的变量污染问题,假如我的构造函数中方法有100个,那我要声明100个全局函数,这样就造成了变了的污染的问题。

  • 解决办法:我们可以用对象将全局函数包裹起来,让他变成局部函数

    <script>
let  objList={       
  fn:function (){
             console.log("我的爱好是打球");
             },
   fn1:function (){
             console.log("我的爱好是唱歌");
             },
   fn2:function(){
             console.log("我的爱好是跳舞");
             }
}
  function person(name,age){
            this.name=name,
            this.age=age
             this.hook=objList.fn
             this.hook1=objList.fn1
             this.hook2=objList.fn2
           } 
    
        let p1= new person('张三',30)
        let p2= new person('李四',30)
      
    </script>
  • ****,
  • 所以对象的包裹的方式:可以解决内存浪费的,可以解决变量的污染,但是现在又有一个新的问题,那就是这个对象objList全局变量本身自己也变成了变量污染,到这里我们已经没有办法来解决了,所以JavaScript的作者就想到了这个漏洞,为了解决这个办法于是原型对象就诞生了。

原型对象的今生

原型对象的作用:

  • 解决内存浪费和变量污染

原型的对象的使用

  • 属性1prototype:是构造函数的属性,指向原型对象
    
  • prototype的作用:解决变量污染和内存浪费
    
  •  属性2,_proto_:是实例对象的属性,指向原型
    
  • _proto_他是隐藏的,实例成员访问原型
    

首先搞清楚构造函数,实例对象,原型对象他们之间的的关系

1111.png

    <script>
    // 构造函数
           function Person(name,age){
            this.name=name,
            this.age=age
           
           } 
    // 原型对象:
    //王原型身上注入方法,同过访问原型的对象方法,就可以解决变量污染和内存浪费的问题  
    person.prototype.hook=function(){
        console.log("我的爱好是打球");
    }

    // 实例对象
        let p1= new Person('张三',30)
        let p2= new Person('李四',30)
        console.log(p1,p2);
    </script>

image.png

总结:这也就是为什么对象能用.方式来访问原型身上的方法,是因为他隐藏了_proto_这个属性,完整写法应该是p1._proto_.hook,那为什么要隐藏这个属性呢,并且开发中不能使用,是因为防止通过这种属性去修改原型对象的值。

四 继承----原型继承

继承的概念:一个对象(子对象)拥有另一个对象(父对象)所以的成员

 <script>
        let father={
            car:{
                brand:"奔驰",
                price:200000
            },
            house:{
                brand:"汤臣一品",
                price:200000000
            },
            money:{
                buck:100000000
            }
        }

     function Son(name,age){
         this.name=name
         this.age=age
     }

     Son.prototype=father
     let p1= new Son('张三',18)
     let p2= new Son('李四',30)
     console.log(p1);
     console.log(p2);
    </script>

image.png

五 原型链

  • 1原型链:每一个实例对象都有自己的原型,而原型也是对象,也有自己的原型,依次类推形成链式结构,称之为原型链
  • 2:对象访问原型链中成员规则:就近原则
  • 自己有先访问自己的,没有就找原型的,原型没有就找原型的原型,依次类推,直到原型链终点null,如果还没有属性则获取undefined,或者报错 XXX is not function
  • 3:原型链的作用:面对对象的继承
 <script>
         function Person(name,age){
            this.name=name,
            this.age=age
          
           } 
        Person.prototype.list=function(){
            console.log("我的爱好是睡觉");
        }
           Person.prototype.sex="人妖"

        let p1= new Person('张三',30)
    
        console.log(p1.age);
        console.log(p1.name);
        console.log(p1.sex);
        console.log(p1.list);
        console.log(p1.toString(11111));
    </script>

image.png

注:数组,内置的对象,dom都有他的原型类型。

六 calss类函数

ES5的写法

// ES5的写法
        // 构造函数
        function Person(name,age){
            this.name=name
            this.age=age
        }
    //原型对象
    Person.prototype.eat=function(){
        console.log("我的爱好是跑步");
    }
    // 实例对象
    let p1=new Person('张三',40)
    console.log(p1);
    console.log(p1.eat);

ES6 class的写法

class类型函数相当于是构造函数的语法糖(本质上还是原型继承)

他把构造函数和原型对象( constructor)用大括号放在一起,增加了阅读性,class类函数必须用new关键字来调用,不然会报错。

 <script> 
    // class类函数的写法
    class Person1{
        // 构造函数
        constructor(name,age){
            this.name=name
            this.age=age

        }
        // 原型对象的方法
       eat(){
           console.log("我的爱好是听歌");
       }
    }
    let p2=new Person1("李四",50)
    console.log(p2);
    console.log(p2.eat);
    </script>

class类函数的extends和supter关键字

extends原型继承:class子类extends父类

1 :extends关键字:语法格式: class 子类 extends 父类 { //子类自己方法}

2: supter关键字:子类中调用父类的方法

用supter调用父类的方法和添加自己的方法 image.png

/* 
 1  :extends关键字:原型继承 
     class 子类 extends 父类  {   //子类自己方法}

 2: supter关键字:子类中调用父类的方法
    class 子类  extends  父类{
//  子类的constructor一定要调用super
            super(name,age,sex)
            super.eat()
            this.score=score
  }
 
 */
                    // class类函数的写法
    class Father{
        // 构造函数
        
     constructor(name,age,sex){
         this.name=name
         this.age=age
         this.sex=sex
        }
    // 原型对象的方法
       eat(){
           console.log("我的爱好是听歌");
       }
    }
    // extends原型继承   语法: class 子类extends 父类
    class JCson extends Father{
        constructor(name,age,sex,score){
            // 子类中调用父类的方法
            super(name,age,sex)
            super.eat()
            this.score=score
        }
        // 子类自己的方法
        stude(){
            console.log("我爱学习");
        }
        set(){
             console.log("我爱看电影");
       }

    }
    let p3=new JCson('张三',19,'男','500')
    console.log(p3,"我是p3");

image.png