JS高级之面向对象篇(包含this判定规则、深 / 浅拷贝、原型/原型链等的详解)

一、认识对象

1. 是什么

  • 对象(object)是键值对(k:v)的集合,表示属性和值得映射关系
    在这里插入图片描述
  • 数组中要考虑顺序,对象不考虑顺序,而是看映射关系

2. 对象的语法

  • k和v之间用冒号分隔,每组k:v之间用逗号分隔,最后一个k:v对之后可以不加逗号
 const obj = {
            name: 'dudu',
            age:18,
            sex:'女'
        }
  • 如果属性的键名不符合js表示命名规范,则这个键名必须用引号包裹
 const obj = {
            'test-key':'测试'
        }

3. 访问属性

  • 通过“点语法”访问,类似于数组中的下标
obj.name // 'dudu'
obj.age // 18
  • 对于不符合js命名规范的,则必须使用方括号[]的写法访问,方括号内部要加单引号
obj['test-key'] // 测试
  • 如果属性名是通过变量的形式存储,则必须通过方括号形式访问,此时方括号内部不加引号
 const obj = {
            name: 'dudu',
            age:18,
            sex:'女',
            'test-key':'测试'
        }
        var key = 'name'

        obj.key // undefined
        obj[key] // dudu

4. 更改属性

  • 直接使用赋值运算符=重新赋值即可
obj.age = 20

5. 创建属性

  • 若对象本身没有某个属性,在使用点语法赋值时,这个属性就会自动被创建
obj.tset = '创建'

6. 删除属性 delete

  • 通过delete操作符来删除对象的某个属性
delete obj.test

二、对象的方法和对象的遍历

  • 某个属性值是函数,则其也被称为对象的方法
  • 对象的方法也是通过点语法调用
const obj = {
            name:'dudu',
            sayHello() {
                console.log("hello")
            }
        }
        obj.sayHello() // hello
  • 对象需要通过 for...in...循环 进行遍历键
for(let k in obj) {
            console.log('属性'+k+'的值是'+obj[k])
        }

三、 对象的深浅克隆

1. 基本类型和引用类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

        var a = 3
        var b = a
        a++
        console.log(a) // 4
        console.log(b) //3

        var arr1 = [1,2,3,4]
        var arr2 = arr1
        arr1.push(5)
        console.log(arr1) // [1,2,3,4,5]
        console.log(arr2) // [1,2,3,4,5]
  • 基本类型:number、string、Boolean、undefined、null
  • 引用类型:object、array、function、regexp等

2. 对象是引用类型

  • 不能用 var obj2 = obj2的语法克隆一个对象
  • 使用 == 或 === 进行对象的比较时,他们比较的是是否指向内存中的同一个对象,而不是比较值是否相同
    在这里插入图片描述
var obj1 = {
        a:1,
        b:2,
        c:3
    }
    var obj2 = {
        a:1,
        b:2,
        c:3
    }
    // obj1和obj2是两个不同的对象
    console.log( obj1 == obj2) //false
    console.log(obj1 === obj2) //false

3. 对象的浅克隆

  • 只克隆对象的“表层”,如果对象的某些属性值又是引用类型,则不克隆他们,只是传递他们的引用
var obj1 = {
        a:1,
        b:2,
        c:[12,23,34]
    }
    var obj2 = obj1; //不能实现克隆,obj1和obj2指向同一个栈,属于同一个对象
    
    /**浅克隆*/
    var obj3 = {}
    for(var k in obj1) {
        // 每遍历一个K属性,就给obj3添加一个同名的K属性,并且值和obj1的值相同
        // a、b是基本类型,在赋值时会进行值克隆,所以两个对象中的a、b互不相关
        // c是引用类型,在赋值时不会进行克隆,而是让两个对象中的c指向同一个栈
        obj3[k] = obj1[k]
    }

在这里插入图片描述

4. 对象的深克隆

  • 克隆对象的全貌,不管是基本类型还是引用类型,都可以进行克隆
  • 对象的深克隆也会使用递归
function deepClone(obj) {
        // 数组和对象都是引用类型,但两者遍历方式不同,数组通过for遍历、对象通过 for...in遍历
        // typeof不能判断出对象和数组,两个都会返回object
        // Array.isArray()可以判断一个对象是否是数组,所以可以先判断数组,不是数组再判断是否是对象
        if(Array.isArray(obj)) {
            var result = []
            // 遍历数组中的每一项,然后添加到新数组中
            for(var i=0;i<obj.length;i++) {
                // 再添加前也需要调用一次函数判断类型(递归)
                result.push(deepClone(obj[i]))
            }
        }else if(typeof obj == 'object') {
            var result = {}
            // 遍历对象,然后添加到新对象中
            for(var k in obj) {
                // 再添加前也需要调用一次函数判断类型(递归)
                result[k] = deepClone(obj[k])
            }
        }else {
            // 基本类型,直接进行赋值即可
            var result = obj
        }
        // 最后要返回最终结果
        return result
   }

   var obj1 = [22,33,{
       name:'dudu',
       age:18,
       hobbies:['run']
   }]
   var obj2 = deepClone(obj1)

5. 递归

5.1 是什么

  • 函数内部可以调用函数自身,从而发起对函数的迭代,即函数自己调用自己

5.2 递归的要素

  • 边界条件:确定递归何时终止,也称为递归出口
  • 递归模式:大问题是如何分解为小问题的,也称为递归体
 /** 递归
     *  求4的阶层
     **/
    // 求阶层
    function factorial(n) {
        // 递归的出口:如果n为1直接返回1
        if(n == 1) return 1
        // 否则继续呈上n-1的阶乘
        return n * factorial(n-1)
    }
    var res = factorial(4)
    console.log(res) //24

5.3 斐波那契数列

  • 形如:1、1、2、3、5、8、13、21的数列
// 返回斐波那契数列中下标为n的项对应的值
    function sequence(n) {
        if(n == 0 || n ==1 ) return 1
        return sequence(n-1) + sequence(n-2)
    }
    console.log(sequence(7)) // 21

四、函数的上下文 this

  • this关键字表示函数的上下文对象
  • this的具体指代要通过调用函数时的“前言后语”来判断

1. 函数的上下文this是由函数的调用方式决定的

  • 以函数的形式调用,this指向window
  • 以对象打点的形式调用,this指向调用的那个对象
var person = {
    name:'dudu',
    age:18,
    sayHello() {
        console.log(`我是${this.name},今年${this.age}岁了`)
    }
   }
   person.sayHello() // 此时this指向person对象
   var sayHello = person.sayHello 
   sayHello() // 此时this指向window
  • 函数只有被调用时,才可以确定上下文

2. 上下文规则

  1. 规则一:对象打点调用函数时(对象.方法),上下文是打点的对象
var obj1 = {
       a:1,
       b:2,
       fn() {
           console.log(this.a+this.b)
       }
   }
   var obj2 = {
       a:3,
       b:4,
       fn:obj1.fn
   }
   obj2.fn() // obj2调用的,所以this是obj2。然后函数也是引用类型,进行赋值的时候会指向同一个堆,所以会打印出 7
function outer() {
       var a = 20
       var b = 12
       return {
           a:33,
           b:44,
           fn() {
               console.log(this.a+this.b)
           }
       }
   }
   outer().fn() // outer()返回一个对象,所以最后也是构成了对象.方法的调用,this就会指向返回的对象,所以会打印出77
  1. 规则二:圆括号直接调用函数( 函数() ),则上下万是window对象
var obj1 = {
       a:1,
       b:2,
       fn() {
           console.log(this.a+this.b)
       }
   }
   var a = 3
   var b = 4
   var fn = obj1.fn
   fn() //通过函数直接调用,this指向window对象,则结果会返回7
function fn() {
       return this.a + this.b
   }
   var a = 1
   var b =2
   var obj = {
       a:3,
       b:fn(),
       fn:fn
   }
   var result = obj.fn() //通过对象的形式调用,this指向obj对象,a=3,b:fn()相当于全局调用了fn方法,b中的this指向window,所以b=3
   console.log(result) // 6
  1. 规则三:数组(类数组对象)枚举出函数进行调用:数组[下标]() ,上下文是这个数组(类数组对象)
  • 类数组对象:所有的键名为自然数序列(从0开始)且有length属性的对象
  • arguments对象是最常见的类数组对象,它是函数的实参列表
function fun() {
       arguments[3]()
   }
    fun('A','B','C',function() {
        console.log(this[1]) // this指向arguments类数组对象'A','B','C',function(),结果输出B
    })
  1. 规则四:IIFE中的函数,上下文是window对象

(function() {

})()

var a = 1
    var obj = {
        a:2,
        fun:(function(){
            var a = this.a; //IIFE中的this指向window,则a=1
            return function() {
                // a 在函数体内部,则a=1
                // return 返回了一个函数
                console.log(a + this.a) // 3
            }
        })()
    }
    obj.fun() //点的方式调用函数,则this指向obj对象,则this.a=2
  1. 规则五:定时器、延时器调用函数,上下文是window对象

setInterval(函数,时间)
setTimeout(函数,时间)

var obj = {
        a:1,
        b:2,
        fun() {
            console.log(this.a + this.b) 
        }
    }
    var a = 3
    var b = 4
    setTimeout(obj.fun, 2000); //this指向window,则this.a=3,this.b=4
    setTimeout(() => {
        obj.fun() //对象点方法的形式调用函数,则this指向obj对象,this.a=1、this.b=2
    }, 2000);
  1. 规则六:事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function() {
//当函数内部有其他函数,比如定时器时,要考虑this的指向
// 必要时需要备份this,即 var _this = this,这时在内部的函数中就可以用_this指向需要的DOM
 }

3. call和apply

3.1 call和apply能指定函数的上下文

函数.call(上下文)
函数.apply(上下文)

function sum() {
        console.log(this.chinese + this.math + this.english)
    }
    const person = {
        chinese:98,
        math:100,
        english:95
    }
    sum.call(person)
    sum.apply(person)

3.2 call和apply的区别

  1. 在传递参数时,call要用逗号分隔参数,apply是用数组存放参数
  2. 第一个参数为上下文,第二个参数为要传的参数值
function sum(x,y) {
        console.log(this.chinese + this.math + this.english + x +y)
    }
    const person = {
        chinese:98,
        math:100,
        english:95
    }
    sum.call(person,4,5)
    sum.apply(person,[4,5])

五、构造函数

5.1 用new调用函数的四步骤

new 函数()

  1. 函数体内部自动创建一个空白对象
  2. 函数的上下文(this)会指向这个对象
  3. 函数体内的语句会执行
  4. 函数会自动返回上下文对象,即使函数没有return语句
    在这里插入图片描述
    在这里插入图片描述

5.2 构造函数

  • new 调用的函数,就是构造函数
  • 构造函数命名时首字母要大写
  • 构造函数的this不是函数本身

5.3 类与实例

  • ‘萨摩耶’是类,嘟嘟是实例
// Person是类
    class Perosn(name,age,sex) {
        this.name = name
        this.age = age
        this.sex = sex
    }
    // dudu是实例
    var dudu = new Perosn('嘟嘟',18,'女')
    // heihei是实例
    var heihei = new Perosn('黑黑',19,'男')

六、原型和原型链

6.1 原型对象

  • 在创建函数时,解析器会自动为函数添加一个属性prototype(原型),这个属性对应着一个对象称为原型对象
  • 任何函数对象都有prototype属性
  • 如果函数作为普通函数调用,则prototype无意义
  • 若作为构造函数调用,它所创建的对象(实例)中都会有一个隐含的属性,指向该构造函数的原型对象,可以通过 __ proto__访问该隐含属性
console.log(dudu.__proto__ === Perosn.prototype) //true
  • 构造函数的prototype是其实例的原型
    在这里插入图片描述

6.2 原型链查找

  • JavaScript规定:实例可以打点访问它的原型的属性和方法,这被称为“原型链查找
  • 原型对象相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象。所以可以将对象中公共的内容,统一设置到原型对象中
  • 当访问对象的一个属性或方法时,它会优先在对象自身寻找,如果没有则会去原型对象找
// 向原型对象中添加一个属性hobby
    Perosn.prototype.hobby = 'run'
    console.log(dudu.hobby) // run
    // 向heihei对象添加属性hobby
	heihei.hobby = 'eat'
    console.log(heihei.hobby) // eat

在这里插入图片描述

6.3 hasOwnProperty()

  • 使用 in检测对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log('hobby' in dudu) // true
  • 使用 hasOwnProperty()检测对象自身是否含有某属性,只有对象自身含有该属性才会返回true
console.log(dudu.hasOwnProperty('hobby')) // false

6.4 原型链的终点

  • 原型对象也是对象,所以也有原型
  • 当使用一个对象的属性或方法时,首先会先在对象自身寻找
    • 当自身没有,就会在原型对象中寻找
    • 如果原型对象中没有,则会寻找原型的原型中,直到找到Object对象的原型(一般只会查找两层)
    • Object对象的原型没有原型,如果在Object中仍然没有找到,则返回undefined
// 检测hasOwnProperty所在的位置
    console.log(dudu.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) // true
    console.log(Object.prototype.hasOwnProperty('hasOwnProperty')) //true
 console.log(dudu.__proto__.__proto__ === Object.prototype) // true
    console.log(Object.prototype.__proto__) // null
  • prototype属性值是对象,它默认拥有constructor属性指向函数
function sum(a,b) {
        return a+b
    }
    console.log(sum.prototype.constructor === sum) //true

6.5 在prototype上添加方法

  • 这样可以避免内存的浪费
Perosn.prototype.sayHello = function() {
        console.log(`hello!我是${this.name}~`)
    }
    console.log(dudu.sayHello === heihei.sayHello) // true
    dudu.sayHello() // hello!我是嘟嘟~

6.6 继承

  • 实现继承的关键:子类拥有父类的全部属性和方法,同时子类还可以定义自己特有的属性和方法
  • 可以通过JavaScript特有的原型链特性实现继承
 // 父类
    function Perosn(name,age,sex) {
        this.name = name
        this.age = age
        this.sex = sex
    }
    Perosn.prototype.sayHello = function() {
        console.log(`hello!我是${this.name},我今年${this.age}岁啦~~`)
    }
    Perosn.prototype.sleep = function() {
        console.log(this.name+'已经开始睡觉啦zzzzzzzz')
    }
    // 子类
    function Student(name,age,sex,school,studentId) {
        this.name = name
        this.age = age
        this.sex = sex
        this.school = school
        this.studentId = studentId
    }
    
    // 实现继承的关键语句
    Student.prototype = new Perosn()

    Student.prototype.study = function() {
        console.log(this.name+'要准备学习了~~~~~')
    }
    Student.prototype.exam = function() {
        console.log(this.name+'正在积极备考!!!!!!!!')
    }

    // 实例化
    var dudu = new Student('嘟嘟',19,'女','汪汪汪大学',201802)

    dudu.sayHello()
    dudu.study()
    dudu.sleep()
    dudu.exam()
    console.log(dudu.studentId)
  • 子类可以重写父类的方法

七、其他

7.1 包装类

  • Number()、String()、Boolean()分别是数字、字符串、布尔值的包装类
  • 包装类是为了让基本类型值可以通过他们构造函数的prototype上获得方法
  • Number()、String()、Boolean()的实例都是object类型
  • new出来的基本类型值可以正常参与运算
 var a = new Number(123)
        var b = new String('慕课网')
        var c  = new Boolean(true)
        console.log(a)
        console.log(typeof a)
        console.log(b)
        console.log(typeof b)
        console.log(c)
        console.log(typeof c)
        console.log('--------------------------------')
        console.log(5 + a)
        console.log(b.slice(0,2))
        console.log(c && true)

在这里插入图片描述

7.2 Math(数学)对象

  • 幂和开方:Math.pow()、Math.sqrt()
  • 向上取整和向下取整:Math.ceil()、Math.floor()

7.2.1 Math.round()

  • 将一个数字四舍五入为整数
  • 四舍五入到小数点后的某位
var x = 9.345667
        // 将x四舍五入到小数点后3位
        console.log(Math.round(x * 1000) / 1000)

7.2.2 Math.max() 和 Math.min()

  • Math.max():参数列表中的最大值
  • Math.min():参数列表的最小值
  • 利用Math.max()求数组最大值——借助apply
    • Math.max()要求参数是通过逗号分隔,不能是数组
    • 所以借助apply方法,不指定上下文,传入数组
var arr = [2,12,34,546,7]
        var max = Math.max.apply(null,arr)
        console.log(max) // 546

7.2.3 Math.random()

  • 可以得到 0 ~ 1 之间的小数
  • 获取 [a,b] 区间内的整数:parseInt(Math.random() * (b - a + 1)) + a
//    生成[3,8]之间的整数
    console.log(parseInt(Math.random() * 6) + 3)

7.3 Date(日期)对象

  • 通过 new Date()即可获得当前时间的日期对象,他是Object类型值
  • 通过new Date(yyyy,mm,d)可以指定日期的时间对象,但月份是从0开始,即11代表12月,这种写法不算时区
  • new Date('yyyy-mm-dd')字符串的写法中月份是从1开始算,并且月份和日期都要保持两位,不足补0,这种写法会算时区

7.3.1 时间对象常见的方法

在这里插入图片描述

7.3.2 时间戳

  • 时间戳表示1970年1月1日零点距离某时刻的毫秒数
  • 通过getTime()或者 Date.parse() 可以将日期对象变为时间戳
  • 通过new Date(时间戳)可以将时间戳变为日期对象
var d = new Date()
    var timestamp1 = d.getTime() // 精确到毫秒
    var timestamp2 = Date.parse(d) // 精确到秒
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值