JavaScript学习笔记 08、面向对象(上)

举报
长路 发表于 2022/11/28 20:49:42 2022/11/28
【摘要】 文章目录前言一、认识对象1.1、对象的定义1.2、访问对象值(两种形式)1.3、修改、创建、删除对象值1.4、方法的创建与使用1.5、遍历对象(for...in...)1.6、对象的深浅克隆(针对于object)二、认识函数的上下文2.1、this关键字(函数上下文)2.2、上下文规则规则1:`对象.方法()`,则这个函数的上下文就是打点的对象规则2:`函数()`,则这个函数的上下文就是windo

@[toc]

前言

本篇博客是关于javascript中面向对象知识点,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

一、认识对象

1.1、对象的定义

语法

//注意点1:js中使用{}来表示对象,每组k:v之间使用逗号相隔
//注意点2:其中k一般不用引号,对于不符合命名规范的时候''包裹;v可以使用任意类型(基本类型,引用类型)
var obj = {
    k: v,
    k: v,
    k: v,
    k: v   //注意点3:最后一个键值对可以不写逗号
}

示例

<script>
    var changlu = {
        name: "cl",
        age: 18,
        sex: '男',
        hobbys: ['编程','java','运动'],
        'favourite-boot': '刻意练习'  //属性名(key)中若是有不符合命名规范的要用''包裹
    };
    console.log(changlu);
</script>

image-20210614120559784



1.2、访问对象值(两种形式)

两种形式

  1. 使用obj.属性方式直接访问原本在对象中key没有’'包裹的值。
  2. 使用obj['属性名']来访问key有’’(或无’'也是可以的)包裹的值。

我们就拿上面的示例进行举例:

<script>
    var changlu = {
        name: "cl",
        age: 18,
        sex: '男',
        hobbys: ['编程', 'java', '运动'],
        'favourite-boot': '刻意练习'  //属性名(key)中若是有不符合命名规范的要用''包裹
    };
    console.log(changlu);

    //方式一:直接使用.属性方式
    console.log(changlu.name);
    console.log(changlu.age);
    console.log(changlu.sex);
    console.log(changlu.hobbys);

    //方式二:使用[]的形式来访问值
    //针对于获取键使用''包裹的值。
    console.log(changlu['favourite-boot']);
    //访问正常的key值使用这类方式也是ok的
    //console.log(changlu['name']);
    //访问存储到变量名key对应的值
    var searchName = "age";
    console.log(changlu[searchName]);
</script>

image-20210614121334612

小总结:对于正确命名规范的key,我们可以直接使用.属性的方式进行访问;使用['属性名']是能够访问到指定对象中的所有属性的!



1.3、修改、创建、删除对象值

介绍

修改:直接访问对象值进行修改即可。

创建:直接.属性即可创建。

删除:delete obj.属性delete obj['属性']


示例

image-20210614134445958

<script>
    var changlu = {
        name: "cl",
        age: 18,
        sex: '男',
        hobbys: ['编程', 'java', '运动'],
        'favourite-boot': '刻意练习'  //属性名(key)中若是有不符合命名规范的要用''包裹
    };

    //修改
    changlu.age = 20;//直接修改即可
    console.log(changlu.age);

    //创建一个身高height
    changlu.height = 180;//直接赋值就好
    console.log(changlu.height);

    //删除身高height以及key被''包裹的键值对
    delete changlu.height;
    delete changlu['favourite-boot'];
    console.log(changlu);
</script>


1.4、方法的创建与使用

对于对象方法的创建你可以直接声明在在内部,或者在外部进行调用对象.新函数= function(){}

示例

<body>

    <script>
        var changlu = {
            name: "cl",
            age: 18,
            sex: '男',
            info: function () {//创建函数方式(方式一)
                //在对象的函数中你不能直接调用该对象的属性,只能使用this.属性或者对象名.属性获取。
                console.log("My name is " + this.name + "," + "age:" + changlu.age);
            }
        };

        //调用内部函数
        changlu.info();

        //创建函数方式(方式二)
        changlu.sleep = function () {
            console.log("I will be sleep!");
        }
        //调用函数
        changlu.sleep();
        console.log(changlu);
    </script>

</body>

image-20210614140234716



1.5、遍历对象(for…in…)

语法for(var key in 对象),该key是对象的键,其是一个字符串。

示例

<script>
    var changlu = {
        name: "cl",
        age: 18,
        sex: '男',
        info: function () {//创建函数方式(方式一)
            //在对象的函数中你不能直接调用该对象的属性,只能使用this.属性或者对象名.属性获取。
            console.log("My name is " + this.name + "," + "age:" + changlu.age);
        }
    };

    //遍历对象
    for (const key in changlu) {
        console.log(typeof key);//该key变量为string类型
        console.log("key:" + key + ",value:" + changlu[key])
    }
</script>

image-20210614141041892



1.6、对象的深浅克隆(针对于object)

浅克隆

使用for ... in ...来实现对象的浅克隆:对于对象中的引用类型实际上仅仅只是引用赋值

<script>
    var obj = {
        name: "cl",
        age: 18,
        t: [0, 1, 2, 3, {
            a: 1,
            b: 2,
            c: [11, 22, 33]
        }]
    };

    //浅克隆:for...in...
    var newObj = {};
    for (var key in obj) {
        newObj[key] = obj[key];
    }
    //测试
    console.log(newObj);
    //证明是浅克隆:第三个属性是引用类型t,若是两个值相同则表示是没有成功
    console.log(newObj.t == obj.t);
</script>

image-20210614145808718


深克隆

对于深克隆需要通过使用递归的形式进行:

<script>
    var obj = {
        name: "cl",
        age: 18,
        t: [0, 1, 2, 3, {
            a: 1,
            b: 2,
            c: [11, 22, 33]
        }]
    };

    //深克隆(数组、对象、其他类型)
    function deepClone(o) {
        //判断是否是数组(数组类型实际就是object,所有需要提前进行判断)
        if (Array.isArray(o)) {
            var result = [];
            //数组
            for (let i = 0; i < o.length; i++) {
                result.push(deepClone(o[i]));//进行递归调用
            }
        } else if (typeof o == "object") {
            //需要克隆的是对象
            var result = {};
            for (let key in o) {
                result[key] = deepClone(o[key]);//进行递归调用
            }
        } else {
            //其他类型
            var result = o;
        }
        return result;
    }

    //调用函数进行深克隆
    var newobj = deepClone(obj);
    console.log(newobj);

    //测试一:obj.t
    console.log(obj.t == newobj.t);

    //测试二:obj.t[4]
    console.log(obj.t[4] == newobj.t[4]);

    //测试三:obj.t[4].c
    console.log(obj.t[4].c == newobj.t[4].c);

</script>

image-20210614150602352



二、认识函数的上下文

2.1、this关键字(函数上下文)

重点:函数的上下文(this)是由调用方式决定的,并不是由本身决定的。

注意:对于一个对象的函数中使用到了this,我们不要一定认为该函数就是使用的本对象的属性或其他类型,要根据函数的调用方式以及那些形式调用的相关!!!

可见下面两个函数调用的时机与情况

<script>
    var obj = {
        a: 1,
        b: 2,
        printAB: function () {
            console.log("a:" + this.a + ",b:" + this.b);
        }
    }

    //情况一:直接调用(此时this指代的是obj对象的上下文)
    obj.printAB();

    //情况二:将obj对象的函数赋值给变量,此时再调用方法,log中的this就指代window对象
    var printAB = obj.printAB;//此时赋值到变量的printAB的this表示window对象上下文
    printAB();//此时的this.a => window.a ,this.b => window.b,由于window对象没有全局变量a,b,所以就是undefined
</script>

image-20210614153055490



2.2、上下文规则

规则1:对象.方法(),则这个函数的上下文就是打点的对象

规则1:对象打点调用它的函数方法(对象.方法()),则函数的上下文就是这个打点的对象

案例1:

<script>
    function fn() {
        console.log(this.a + this.b);
    }

    var obj = {
        a: 66,
        b: 33,
        fn: fn
    };

    //是这个obj对象调用的,那么this上下文指的就是obj
    obj.fn();//99
</script>

案例2:最具有迷惑性

<script>
    var obj = {
        a: 1,
        b: 2,
        fn: function () {
            console.log(this.a + this.b);
        }
    };

    var obj2 = {
        a: 3,
        b: 4,
        fn: obj.fn   //千万不要被迷惑成了调用obj2.fn是obj.fn调动执行的,这里仅仅只是赋值函数过来
    };

    //obj2调用的,那么上下对象依旧是obj2
    obj2.fn();//7
</script>

案例3:

<script>
    function outer() {
        var a = 11;
        var b = 22;
        return {  //匿名对象
            a: 33,
            b: 44,
            fn: function () {
                console.log(this.a + this.b);
            }
        };
    }

    //outer()返回的是指定的匿名对象,那么就是匿名对象.fn()
    //上下文对象就是这个匿名对象,this.a与this.b就依次是33、44
    outer().fn();//77
</script>

案例4:

<script>
    function fun() {
        console.log(this.a + this.b);
    }

    var obj = {
        a: 1,
        b: 2,
        c: [{
            a: 3,
            b: 4,
            c: fun
        }]
    };

    var a = 5;
    //obj.c[0]的对象是指定数组中下标为0的对象,那么此时函数的上下文就是这个下标为0的对象
    obj.c[0].c();//7
</script>


规则2:函数(),则这个函数的上下文就是window对象

规则2函数(),则这个函数的上下文就是window对象。

案例1:

<script>
    var obj1 = {
        a: 1,
        b: 2,
        fn: function () {
            console.log(this.a + this.b);
        }
    };

    var a = 3;
    var b = 4;

    var fn = obj1.fn;
    //规则2:直接是函数(),那么上下文就是window,3+4=7
    fn();//7
</script>

案例2:这个案例十分good

<script>
    function fun() {
        return this.a + this.b;
    }

    var a = 1;
    var b = 2;
    var obj = {
        a: 3,
        b: fun(),  //规则2:直接函数(),上下文为window,则为1+2=3
        fun: fun   //仅仅只是引用传递
    };

    var result = obj.fun();//规则1:上下文为obj,则为3+3=6
    console.log(result);
</script>


规则3:数组[下标](),则这个函数上下文就是这个数组

规则3:数组(==类数组对象==,数组[下标]())枚举出函数进行声明,则这个函数上下文就是这个数组。

  • 类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象。例如arguments对象是最常见的类数组对象,是函数的实参列表。

案例1:

<script>
    var arr = ['A', 'B', 'C', function () {
        console.log(this[0]);
    }];

    //规则3:上下文对象是arr数组,那么this指的就是这个数组,this[0]='A'
    arr[3]();//'A'
</script>

案例2:

<script>
    function fun() {
        //规则3:arguments就是类数组,这里指代的就是多个参数
        arguments[3]();//arguments类数组就是上下文,使用this就是指的是arguments类数组
    }

    //调用函数
    fun('A', 'B', 'C', function () {
        console.log(this[1]);//'B'
    });
</script>


规则4:IIFE中的函数(function(){})(),上下文是window对象

规则四:IIFE中的函数(function(){})(),上下文是window对象。

案例1:其中包含规则1、4,闭包概念,解题的关键点一定要从上至下细细查看!!!

<script>
    var a = 1;
    var obj = {
        a: 2,
        fun: (function () {
            //IIFE函数中返回值为函数,形成闭包,a会存储起来值
            var a = this.a;//1、上下文对象是window,所以为1
            return function () {
                console.log(a + this.a);
            };//2、此时fun = function(){ console.log(a + this.a);  }。其中a为闭包值1,this.a由于没有调用还不确定
        })()  //规则4:上下文对象是window
    };
    //规则1:上下文对象为obj,所以this.a = 2
    obj.fun();//最终执行函数:1+2 = 3
</script>

image-20210614163311611



规则5:定时器延时器调用函数,上下文是window对象

规则5:定时器延时器(setInterval()setTimeout())调用函数,上下文是window对象。

对于定时器或延时器中调用函数有两种情况

①直接传入对应的函数

<script>
    var obj = {
        a: 1,
        b: 2,
        fun: function () {
            console.log(this.a + this.b);
        }
    };

    var a = 3;
    var b = 4;
    //首先将obj的对象传给回调函数,此时2s后由setTimeout进行调用该函数
    //规则6:定时器或延时器调用的函数上下文对象是window
    setTimeout(obj.fun, 1000);//7  | window.a+window.b = 7
</script>

②在延时器的回调函数中去打点调用对象函数

<script>
    var obj = {
        a: 1,
        b: 2,
        fun: function () {
            console.log(this.a + this.b);
        }
    };

    var a = 3;
    var b = 4;
    //规则5:此时setTimeout的回调函数指的是其中的匿名函数,确实该函数的作用域是window
    //规则1:注意在匿名函数中调用的函数方法是以对象.方法形式进行调用函数,上下文为obj
    setTimeout(function(){
        obj.fun();//3  | 该上下文对象是obj,所以this.a+this.b=1+2=3
    }, 1000);
</script>


规则6:事件处理函数的上下文是绑定事件的dom元素

DOM元素.onclick = function(){}:该事件处理函数的上下文就是这个DOM元素。

案例1:点击哪个盒子,哪个盒子就变红,使用同一个事件处理函数实现。(利用规则5)

<style>
    * {
        margin: 0;
        padding: 0;
    }

    div {
        float: left;
        width: 100px;
        height: 100px;
        border: 1px solid #000;
        margin-left: 10px;
    }
</style>

<body>
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>

    <script>
        //点击变红
        function clickToColor() {
            //规则6:DOM元素调用时,上下文对象为DOM元素
            this.style.backgroundColor = "red";
        }

        //绑定单击事件,使用同一个函数
        var divlists = document.getElementsByTagName("div");
        divlists[0].onclick = clickToColor;
        divlists[1].onclick = clickToColor;
        divlists[2].onclick = clickToColor;
    </script>
</body>

GIF

案例2:与案例2大致相同,不同的是,点击盒子要延迟2s才变红。

思路:在函数中使用延时器,但需要注意的是延时器中的回调函数上下文是window对象,所以我们需要先在函数中保存一份DOM元素的上下文,之后使用DOM元素上下文进行调用。

<script>
	//对应html、css样式实际与案例1相同,这里就不再重复使用
    function clickToColor() {
        //由于setTimeout中的回调函数的上下文是window对象,若是直接使用this就是指代window对象
        //所以我们需要在函数中保存对应DOM元素的上下文对象,即this
        var self = this;
        setTimeout(function () {
            self.style.backgroundColor = "red";
        }, 2000);
    }

    //绑定单击事件,使用同一个函数
    var divlists = document.getElementsByTagName("div");
    divlists[0].onclick = clickToColor;
    divlists[1].onclick = clickToColor;
    divlists[2].onclick = clickToColor;

</script>

GIF



规则7:call和apply方法能够自由指定上下文

引出为什么要自由指定上下文?

首先我们通过一个示例来进行说明,对于下面该案例,我们想要输出stu对象的总成绩,有什么办法能够让我们直接使用该函数打印出总成绩吗?

<script>
    function sum() {
        console.log("总成绩为:" + this.chinese + this.english + this.math);
    }

    var stu = {
        chinese: 99,
        english: 100,
        math: 66
    }
</script>

有下面两种方式进行:都是通过在内部添加或者外部添加方式接着进行调用才可以

<script>
    function sum() {
        console.log("总成绩为:" + (this.chinese + this.english + this.math));
    }

    var stu = {
        chinese: 99,
        english: 100,
        math: 98,
        //方式一:直接在对象内部添加函数
        //sum: sum
    }

    //方式二:在外部手动为stu添加一个键值对
    stu.sum = sum;

    //执行方法
    stu.sum();//297
</script>

image-20210614172638315

为了更加方便不需要在原本对象中进行添加,就可以使用call()或apply()方法!!!



call与apply的语法

语法

  • call方法:函数.call(上下文对象)
  • apply方法:函数.apply(上下文对象)

我们接着通过使用call、apply来解决上面案例问题:

<script>
    function sum() {
        console.log("总成绩为:" + (this.chinese + this.english + this.math));
    }

    var stu = {
        chinese: 99,
        english: 100,
        math: 98,
    }

    //call()方法
    sum.call(stu);//总成绩为:297

    //apply()方法
    sum.apply(stu);//总成绩为:297
</script>

说明:这两个方法都是能够去指定上下文对象,效果都是一致,唯一不同的是两种方式第二个参数传值不同。



call与apply不同点

介绍不同点

使用call()apply()都可以进行传值给指定调用的函数作为函数的形参,其中不同点:

  • call():函数的形参需要一个个传入。
  • apply():函数形参需要直接传入一个数组。
    • 若是两种传值与规定不符就会出现异常或者读取到的值有误!!!

image-20210614173102032


apply()使用情境

对于apply()中需要传入数组,我们在什么情况下可以进行使用呢?

  • 内部函数需要获取到外部的所有形参值时,可以直接将arguments传入!!!

示例

<script>
    function fun1() {
        //若是函数(外)中的函数(内)需要获取到函数(外)传入的参数值的话,就可以直接将arguments传入
        fun2.apply(this, arguments);
    }

    function fun2(a, b) {
        alert(a + b);
    }

    fun1(33, 44);
</script>

image-20210614173703228



2.3、七个规则总结

规则 上下文
对象.函数() 对象
函数() window
数组[下标]() 数组
IIFE window
定时器 window
DOM事件处理函数 绑定DOM的元素
call和apply 可任意指定


三、构造函数

3.1、new 函数()来创建对象(四步骤)

语法new 函数(),返回一个对象。

根据JS规定,使用new操作符来调用函数会进行四个步骤

  1. 执行函数体之前会自动创建一个空白对象。===> var obj = {};
  2. 此时函数的上下文(this)会指向这个对象。 ===> this = obj;
  3. 函数体内的语句会一一执行。 ===> 就会使用this指向的这个空白对象进行一系列操作。
  4. 最终函数会自动返回一个上下文对象this(无论函数是否有没有return,都会返回一个对象)。

示例

<script>
    function fun() {//1、var obj = {}  2、this = obj
        this.a = 5;//3.1、obj.a = 5  | 相当于新创建一个属性a
        this.b = 6;//3.2、obj.b = 6  | 相当于新创建一个属性b
        var m = 123;
        //4、返回一个对象this(即上下文)
    }

    var myfun = new fun();
    console.log(myfun);
</script>

image-20210614180052463



3.2、认识构造函数

构造函数:使用new调用一个函数即可,任何函数都可以使用构造函数,只需要用new调用它。

  • 通常我们约定对于构造函数的函数首字母开头要大写!(并不是说首字母大写的才是构造函数,而是根据是否使用new 函数()方式急性调用)

示例

<script>
    //业界约定好首字母开头大写的是构造函数
    function People(name, sex, age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.info = function () {
            console.log("name:" + name + ",sex=" + sex + ",age=" + age);
        }
    }

    //new 函数()来创建一个对象
    var changlu = new People("changlu", '男', 20);
    var liner = new People("liner", '女', 23);
    console.log(changlu);
    console.log(liner);
    changlu.info();
    liner.info();
</script>

image-20210614182448930



3.3、js中的"类"与实例(js没有纯粹类的概念)

JavaC++等是"面向对象"(oo,object-oriented)语言。

JavaScript是"基于对象"(ob,object-based)语言。

  • js中的构造函数实际上就可以类比于OO语言中的"类"。js中没有特意书写类的格式,只能通过函数形式来模拟出类,在ES6之中提出了class类这个概念。js中没有纯粹的类概念,只有构造函数这个概念。

重点

  1. js中使用构造函数来模拟"类"。(es6提出class概念,但是一定要记住js中没有纯粹的类概念,只有构造函数这个概念)
  2. 创建实例,就是通过new 函数()来返回一个上下文对象,也可以称为是实例。


四、原型与原型链

4.1、认识prototype属性

任何函数都有prototype属性(意思是原型),该属性值实际上是一个对象{},这个对象中默认拥有constructor属性指回函数。

作用:普通函数的prototype属性没有任何用户,而对于构造函数的prototype属性非常有用!

重点:构造函数的prototype属性是它的实例的原型。

<script>
    //构造函数(其实就是一个函数)
    function People(name, sex, age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    //new 函数()来创建一个对象
    var changlu = new People("changlu", '男', 20);
    console.log(People.prototype);//输出原型
    console.log(typeof People.prototype);//该函数的原型是一个对象
    console.log(People === People.prototype.constructor);//这个原型对象中的constructor(函数)与我们自定义的函数相同
</script>

image-20210614204405964



4.2、__proto__与prototype属性

__proto__:其是每个对象都有的属性。可以理解为构造器的原型

  • 简而言之将这个对象的__proto__属性看做是构造函数名.prototype(也就是原型对象)

prototype:其每个函数都有的属性。指的是原型,原型是一个对象,在这个对象中包含一个构造器属性指回函数。

<script>
    //构造函数(其实就是一个函数)
    function People(name, sex, age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    //new 函数()来创建一个对象
    var changlu = new People("changlu", '男', 20);
    //对象的__proto__(指的就是其构造函数的原型)与构造函数原型比较:true
    console.log(changlu.__proto__ == People.prototype);
    //构造函数原型的构造函数属性与构造函数比较:true
    console.log(People.prototype.constructor == People);
</script>

image-20210614205620434



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。