九、JavaScript面向对象(2)

238 阅读18分钟

继承

面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。

大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现

image.png

image.png

image.png UML类图使用空心的箭头表示继承关系

子类指向父类

image.png

image.png

通过原型链实现继承

将父类的实例化对象 赋给 子类的prototype

Truck.prototype = new Vehicle();

image.png 解析:

该图与如下代码是对应的:

61a8253909dfcc6a09840797.png People是构造函数,它的原型对象prototype上有sayHello和sleep方法:

61a825c2096c41a609430327.png 对应下图:

61a825ec091cbe9914410308.png 继承时,方式是让Student的原型对象指向People的实例:

61a8264509c4dd7508990201.png 对应下图:

61a828cc093613c710880435.png Student的原型对象上有study和exam两个方法:

61a8295d092147df07030154.png

61a8297e094a680213620509.png hanmeimei是Student的实例对象,可以通过__proto__访问构造函数Student的原型对象:

61a829e009b7b82413360513.png 因此可以访问study、exam方法:

61a82a0409d61bbc12090468.png 而People的实例,也可以通过__proto__属性,访问People的原型对象:

61a82a6609f07a5e13800527.png 这样hanmeimei就可以通过原型链,访问People原型对象上的方法,实现继承:

61a82aca09fe970014460779.png

复写(override)父类的方法

也可以重写、复写父类的方法,改变原来父类的方法所执行的内容。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 父类
        function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        };
        People.prototype.sayHello = function () {
            console.log('我是' + this.name + ',我今年' + this.age);
        };
        People.prototype.sleep = function () {
            console.log(this.name + '开始睡觉');
        };

        // 子类 学生类
        function Student(name, age, sex, school, id) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.school = school;
            this.id = id;
        };
        // 实现继承(关键语句)
        Student.prototype = new People();    // new People()是实例化对象,将实例化对象添加到Student的原型上,那么就继承了People的内容。student实例的原型链上就会有people的内容,从而实现继承

        Student.prototype.study = function () {
            console.log('我正在学习');
        };
        Student.prototype.exam = function () {
            console.log('我正在考试');
        };
        // 复写(override)父类的sayHello方法
        Student.prototype.sayHello = function () {
            console.log('敬礼! ' + '我是' + this.name + ',我今年' + this.age)
        };
        // 实例化
        var hanmeimei = new Student('韩梅梅', 20, '女', '慕课小学', '12138');
        hanmeimei.study();
        hanmeimei.sleep();
        hanmeimei.sayHello();   // 敬礼! 我是韩梅梅,我今年20

        var laozhang = new People('老张', 60, '男');
        laozhang.sayHello();    // 我是老张,我今年60
    </script>
</body>

</html>

image.png 下图这俩添加的原型,是添加到了构造函数People身上

image.png 一个常见问题 如下

为什么这里的子类手动添加了父类的属性?

image.png 因为这里想要实现子类实例化时,每个实例拥有不同的name等属性值,所以在子类中再次书写了一遍。

算是复写、重写了父类的属性

面向对象案例-拓展

上升到面向对象 - 红绿灯小案例

思想上升到面向对象

image.png

image.png

image.png

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>红绿灯小案例</title>
    <style>
        #ele img {
            width: 70px;
        }
    </style>
</head>

<body>
    <div id="ele"></div>

    <script>
        // 红绿灯类
        function TrafficLight() {
            // 我们规定绿色1、黄色2.红色3
            this.color = 1;     // 表示当前颜色
            this.init();        // 初始化方法
            this.bindEvent();   // 绑定事件
        };

        // 初始化方法
        TrafficLight.prototype.init = function () {
            this.imgDom = document.createElement('img');
            ele.appendChild(this.imgDom);
            this.imgDom.src = './images/' + this.color + '.jpg';
        };

        // 绑定事件
        TrafficLight.prototype.bindEvent = function () {
            var self = this;    // 备份上下文(DOM元素节点不能调用方法,只有实例对象可以调用) 这里的this指的是TrafficLight的构造实例
            this.imgDom.onclick = function () {
                self.changeColor();
            };
        };

        // 改变颜色方法
        TrafficLight.prototype.changeColor = function () {
             // 改变自己的color属性,从而有一种“自治”的感觉,自己管理自己,不干扰别的红绿灯
            this.color++;
            if (this.color == 4) {
                this.color = 1;
            }
            this.imgDom.src = './images/' + this.color + '.jpg';
        };

        // 获取元素节点
        var ele = document.getElementById('ele');

        // 实例化100个红绿灯
        var count = 100;
        while (count--) {
            new TrafficLight();
        }
    </script>
</body>

</html>

动画.gif

案例的思路:

我们想生成100个红绿灯,每个红绿灯都能通过点击,切换灯的颜色,那么就可以通过遍历实例化100次trafficLight来实现:

62c7a58b29ef408906501000.png 每遍历一次TrafficLight,就会在页面中生成一个红绿灯,并有点击切换功能。在TrafficLight中会具体实现每个灯的功能。

实例化TrafficLight(new TrafficLight())时,代码就会从下图红框处开始执行:

62c7a5c629149d3a06501000.png

this.color就相当于声明变量color,只不过写法特殊,通过this.xxx的形式写。this.init()就是调用init方法,this.bindEvent()则是调用bindEvent方法。

调用init方法时,就会对应执行下图红框处位置:

62c7a624296e4b9e06501000.png 即创建图片,并给图片设置src属性;这样页面上,就会显示出图片了。

调用bindEvent方法时,则会执行如下代码:

62c7a65729c58c0006501000.png 此时就会给img图片绑定点击事件:

62c7a66629e1833606501000.png 点击图片的时候,则会执行changeColor方法,从而实现切换src(切换颜色):

62c7a69c29e2cf2d06501000.png 这样就逐步实现了功能。

常见问题:

实例自己是可以调用自己身上方法的 image.png

为何备份上下文?

给this.imgDom元素绑定事件,事件中的this会指向imgDom,而不是构造实例,imgDom无法调用changeColor方法。而这里需要调用TrafficLight下的changeColor方法。所以提前将this(指向实例)赋值给self,那么在点击事件中就可以调用changeColor方法了。

image.png

image.png

上升到面向对象 - 炫彩小球小案例

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: black;
        }

        .ball {
            position: absolute;
            border-radius: 50%;
        }
    </style>
</head>

<body>
    <script>
        function Ball(x, y) {
            //属性x、y表示的是圆心的坐标
            this.x = x;
            this.y = y;
            this.r = 20;   // 半径
            this.color = colorArr[parseInt(Math.random() * colorArr.length)]; // 背景颜色
            this.opacity = 1;   // 透明度
            this.init();        // 初始化
            // 这个小球的x增量和y的增量 使用do while语句,可以防止dX和dY都是零
            do {
                this.dX = parseInt(Math.random() * 20) - 10;
                this.dY = parseInt(Math.random() * 20) - 10;
            } while (this.dX == 0 && this.dY == 0)
            // 把自己推入数组,注意,这里的this不是类本身,而是实例对象
            ballArr.push(this);
        };

        // 初始化方法
        Ball.prototype.init = function () {
            this.dom = document.createElement('div');
            this.dom.className = 'ball';
            this.dom.style.width = this.r * 2 + 'px'
            this.dom.style.height = this.r * 2 + 'px'
            this.dom.style.left = this.x - this.r + 'px';
            this.dom.style.top = this.y - this.r + 'px';
            this.dom.style.backgroundColor = this.color;
            document.body.appendChild(this.dom);   // 上DOM树
        };

        // 更新方法
        Ball.prototype.update = function () {
            // 位置改变
            this.x += this.dX;
            this.y -= this.dY;
            // 半径改变
            this.r += 0.2;
            // 透明度改变
            this.opacity -= 0.01;
            this.dom.style.opacity = this.opacity;

            this.dom.style.width = this.r * 2 + 'px';
            this.dom.style.height = this.r * 2 + 'px';
            this.dom.style.left = this.x - this.r + 'px';
            this.dom.style.top = this.y - this.r + 'px';

            // 当透明度小于0的时候,就需要从数组中删除自己,DOM元素也要删掉自己
            if (this.opacity < 0) {
                //从数组中删除自己
                for (var i = 0; i < ballArr.length; i++) {
                    if (ballArr[i] == this) {
                        ballArr.splice(i, 1);
                    }
                }
                // 还要删除自己的dom
                document.body.removeChild(this.dom);
            }
        };

        // 把所有的小球实例都放到一个数组中
        var ballArr = [];

        // 初始颜色数组
        var colorArr = ['#4A89F3', '#D33428', '#11C8EE', '#F96A11', '#F6BACE'];

        // 定时器,负责更新所有的小球实例
        setInterval(function () {
            // 遍历数组,调用update方法
            for (var i = 0; i < ballArr.length; i++) {
                ballArr[i].update();
            }
        }, 20);

        //鼠标指针的监听
        document.onmousemove = function (e) {
            var x = e.clientX;
            var y = e.clientY;
            new Ball(x, y);
        };
    </script>
</body>

</html>

效果如下: 动画.gif 解析说明

确定圆心: 因为left、right值 的(0, 0) 点在左上角,所以为找到圆心就得用left、top值减去半径,正好是圆心

注意

  • 因为添加的到html结构中元素一直保留,可能影响浏览器的性能,造成页面卡顿,所以推荐将看不见的小球删除了

  • 删除的是dom节点。需要注意的是,删除dom节点以后,该dom节点所拥有的属性也不存在了。

  • 构造函数中的this就是指向实例对象。

内置对象

包装类

image.png 对象是 JavaScript 语言最主要的数据类型,三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”(wrapper)。

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的NumberStringBoolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

v1 === 123 // false
v2 === 'abc' // false
v3 === true // false

上面代码中,基于原始类型的值,生成了三个对应的包装对象。可以看到,v1v2v3都是对象,且与对应的简单类型值不相等。

包装对象的设计目的,首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值,整门语言有一个通用的数据模型,其次是使得原始类型的值也有办法调用自己的方法。

NumberStringBoolean这三个原生对象,如果不作为构造函数调用(即调用时不加new),而是作为普通函数调用,常常用于将任意类型的值转为数值、字符串和布尔值。

// 字符串转为数值
Number('123') // 123

// 数值转为字符串
String(123) // "123"

// 数值转为布尔值
Boolean(123) // true

总结一下,这三个对象作为构造函数使用(带有new)时,可以将原始类型的值转为对象;作为普通函数使用时(不带有new),可以将任意类型的值,转为原始类型的值。

demo

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var a = new Number(123);
        console.log(a);
        console.log(typeof a);

        var b = new String('hello world');
        console.log(b);
        console.log(typeof b);

        var c = new Boolean(true);
        console.log(c);
        console.log(typeof c);
    </script>
</body>

</html>

image.png 以a为例,a是通过构造函数Number实例化得来的实例,相当于把数字123包装成了对象,所以类型是对象object。当成固定知识点记住结论就行了。

用new包装类得到数字的结果的类型是object,它是一个对象

image.png 注意

PrimitiveValue是不可以自己打点调用的,它是一个内部属性值

606438d9080479a406610300.jpg 包装类是否能进行正常运算呢?(面试常考)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var a = new Number(123);
        var b = new String('hello world');
        var c = new Boolean(true);

        console.log(a + 7);
        console.log(b.slice(0, 5));
        console.log(c && true);
    </script>
</body>

</html>

image.png 是可以进行正常运算的

我们再来看下面这段代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var d = 456;
        console.log(d.__proto__ == Number.prototype);   // 可以假想d是Number的实例
        var e = 'hello';
        console.log(e.__proto__ == String.prototype);   // 可以认为e是String的实例
        console.log(String.prototype.hasOwnProperty('slice'));
        console.log(String.prototype.hasOwnProperty('substr'));
    </script>
</body>

</html>

我们结合下面这张图来看代码就十分容易了 image.png 即使这里没有写new,都可以被看作是被Number new出来的456,被String new出来的‘hello’

Number()是一个构造函数,d变量的值为数字,可以理解为实例化出来的一个对象,所以对象的__proto__指向构造函数的prototype属性

image.png 注意

  • Array就不能成为数组的包装类,因为包装类指的是对基本类型值的面向对象的封装,Array本身就不是基本类型值
  • undefined与null是没有包装类的
  • 所有的数据类型原型链最终都指向Object.prototype,不过,Object对象除外,他的原型链终点是指向null

image.png

原始类型与实例对象的自动转换

某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。这时,JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例。

比如,字符串可以调用length属性,返回字符串的长度。

'abc'.length // 3

上面代码中,abc是一个字符串,本身不是对象,不能调用length属性。JavaScript 引擎自动将其转为包装对象,在这个对象上调用length属性。调用结束后,这个临时对象就会被销毁。这就叫原始类型与实例对象的自动转换。

var str = 'abc';
str.length // 3

// 等同于
var strObj = new String(str)
// String {
//   0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
// }
strObj.length // 3

上面代码中,字符串abc的包装对象提供了多个属性,length只是其中之一。

自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。

var s = 'Hello World';
s.x = 123;
s.x // undefined

上面代码为字符串s添加了一个x属性,结果无效,总是返回undefined

另一方面,调用结束后,包装对象实例会自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个新生成的对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。如果要为字符串添加属性,只有在它的原型对象String.prototype上定义

内置函数

可以理解为,内置函数就是用构造函数编译出来的。内置函数通常有5个大类如下:

  1. 常规函数 如Bom事件。

  2. 数组函数 如new Array();

  3. 日期函数 如new Date()

  4. 数学函数 如Math

  5. 字符串函数 如substring()

以上就是内置函数的分类。

Math(数学)对象

image.png Math是 JavaScript 的原生对象,提供各种数学功能。该对象不是构造函数,不能生成实例,所有的属性和方法都必须在Math对象上调用。

与前面介绍的几个对象(例如 Number对象、String 对象、Array 对象等)不同,调用 Math 对象中的属性和方法无需预先使用 new 运算符来创建它,直接将 Math 作为对象调用即可

静态属性

Math对象的静态属性,提供以下一些数学常数。(了解即可)

  • Math.E:常数e
  • Math.LN2:2 的自然对数。
  • Math.LN10:10 的自然对数。
  • Math.LOG2E:以 2 为底的e的对数。
  • Math.LOG10E:以 10 为底的e的对数。
  • Math.PI:常数π
  • Math.SQRT1_2:0.5 的平方根。
  • Math.SQRT2:2 的平方根。
Math.E // 2.718281828459045
Math.LN2 // 0.6931471805599453
Math.LN10 // 2.302585092994046
Math.LOG2E // 1.4426950408889634
Math.LOG10E // 0.4342944819032518
Math.PI // 3.141592653589793
Math.SQRT1_2 // 0.7071067811865476
Math.SQRT2 // 1.4142135623730951

这些属性都是只读的,不能修改。

静态方法

Math对象提供以下一些静态方法。

  • Math.abs():绝对值
  • Math.ceil():向上取整
  • Math.floor():向下取整
  • Math.max():最大值
  • Math.min():最小值
  • Math.pow():幂运算
  • Math.sqrt():平方根
  • Math.log():自然对数
  • Math.exp()e的指数
  • Math.round():四舍五入
  • Math.random():随机数

Math.round( )

image.png Math.round方法用于四舍五入。

Math.round(0.1) // 0
Math.round(0.5) // 1
Math.round(0.6) // 1

注意,它对负数的处理(主要是对0.5的处理)。

Math.round(-1.1) // -1
Math.round(-1.5) // -1
Math.round(-1.6) // -2

四舍五入精确位数

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var num = 12.34567;
        console.log(Math.round(num *= 100) / 100);  // 保留两位小数
    </script>
</body>

</html>

image.png

Math.max() 和 Math.min()

Math.max方法返回参数之中最大的那个值,Math.min返回最小的那个值。如果参数为空, Math.min返回InfinityMath.max返回-Infinity

Math.max(2, -1, 5) // 5
Math.min(2, -1, 5) // -1
Math.min() // Infinity
Math.max() // -Infinity

image.png

使用Math.max()求数组中最大值

Math.max() 与 Math.min()的参数都是需要罗列零散的值的,正常来说是无法求数组中的最大/最小值的

但是我们可以使用apply()方法,就能够求数组中的最大/最小值了

image.png apply()方法第一个参数是对象,通过用于改变this指向,但是这里没有this,就可以写为null,空对象。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var arr = [4, 5, 6, 7];
        console.log(Math.max.apply(null, arr));
        console.log(Math.min.apply(null, arr));

        console.log(Math.max(...arr));      // es6语法
    </script>
</body>

</html>

image.png

注意

如果参数中包含不能转换成数字的非数字值,则返回NaN

Math.random()

image.png 该方法返回的是一个大于等于0并且小于1的随机数,不是整数。

Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1。

Math.random() // 0.7151307314634323

练习

单选题1

image.png

单选题2

image.png

求出数组中的最大值和最小值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var arr = [1, 3, 5, 7, 9, 2, 4, 6, 8];
        var max = arr[0];
        var min = arr[0];

        for (var i = 0; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i]
            } else if (arr[i] < min) {
                min = arr[i]
            }
        }

        console.log('最大值' + max + ',最小值' + min);
    </script>
</body>

</html>

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // es6语法
        var arr = [1, 3, 5, 7, 9, 2, 4, 6, 8];

        console.log('最大值' + Math.max(...arr), '最小值' + Math.min(...arr));
    </script>
</body>

</html>

Date(日期)对象

Date 对象是 JavaScript 内置的对象,通过它您可以访问计算机系统的时间,此外,Date 对象中还提供了多种用于管理、操作和格式化时间/日期的方法。

Date对象是 JavaScript 原生的时间库。它以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var d1 = new Date();    // 参数什么参数都不加,自动得到今天此时此刻的日期对象
        console.log(d1);
        console.log(typeof d1);

        var d2 = new Date(2023, 0, 2);     //不算时区
        console.log(d2);

        var d3 = new Date('2023-6-6');   // 算时区 中国是东8区 所以时间会有所不同 
        console.log(d3);
    </script>
</body>

</html>

注意

第二个参数的范围是0-11,0代表1月份,1代表2月份,以此类推,11代表12月。

这是new Date的特殊点。如果以字符串作参数,则不是。

日期对象的常见的方法

Date对象提供了一系列get*方法,用来获取实例对象某个方面的值。

  • getDate():返回实例对象对应每个月的几号(从1开始)。
  • getDay():返回星期几,星期日为0,星期一为1,以此类推。
  • getFullYear():返回四位的年份。
  • getMonth():返回月份(0表示1月,11表示12月)。
  • getHours():返回小时(0-23)。
  • getMinutes():返回分钟(0-59)。
  • getSeconds():返回秒(0-59)。

image.png

getDate()

getDay()

getMonth()

getFullYear()

getHours

getMinutes()

getSeconds()

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var d = new Date();
        
        console.log(d.getFullYear());
        console.log(d.getMonth() + 1);  // 月份
        console.log(d.getDate());   // 日期
        console.log(d.getDay());    // 星期
        console.log(d.getHours());
        console.log(d.getMinutes());
        console.log(d.getSeconds());
    </script>
</body>

</html>

时间戳

image.png time stamp 时间戳

getTime()方法

  • 精确到毫秒

Date.parse()函数

  • 精确到秒,也是毫秒,只不过最后三位数一定是000

将当前时间转为时间戳

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 显示时间戳的两种方式
        var time = new Date();
        console.log(time.getTime());    // 精确到毫秒
        console.log(Date.parse(time));  // 精确到秒,也是毫秒,只不过最后三位数一定000
    </script>
</body>

</html>

image.png

将时间戳转换为时间

只需将时间戳传入new Date() 中

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var time = new Date('1960-2-11');
        var timeStamp = Date.parse(time);   
        console.log(new Date(timeStamp));   // 将时间戳传入new Date()中
    </script>
</body>

</html>

image.png

小练习

多选题

image.png

单选题

image.png

单选题

image.png

获取星期几

使用date对象的方法,编写一段代码,获取今天是星期几

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var dayArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
        var currentDay = new Date().getDay();
        console.log(dayArr[currentDay]);
    </script>
</body>

</html>

倒计时小程序

编写一个高考倒计时

提示:日期对象 减 日期对象可以得到时间戳

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>距离2023年高考还有</h1>
    <h2 id="info"></h2>

    <script>
        var info = document.getElementById('info');
        setInterval(function () {
            var now = new Date();
            var target = new Date(2023, 5, 7);
            // 毫秒差
            var diff = target - now;    // 两个日期对象相减会得到时间戳
            // 获取天数     总相差的毫秒数 / 一天的毫秒数
            var day = parseInt(diff / (1000 * 60 * 60 * 24));   // 1秒*60秒*60分*24小时
            // 获取小时  不足一天的毫秒数 / 一个小时的毫秒数
            var hours = parseInt(diff % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
            // 获取分钟  不足一小时的毫秒数 / 一分钟的毫秒数
            var minutes = parseInt(diff % (1000 * 60 * 60 * 24) % (1000 * 60 * 60) / (1000 * 60));
            // var minutes = parseInt(diff % (1000 * 60 * 60) / (1000 * 60));  // 优化后
            // 获取秒
            var seconds = parseInt(diff % (1000 * 60 * 60 * 24) % (1000 * 60 * 60) % (1000 * 60) / 1000);

            info.innerHTML = day + '天' + hours + '小时' + minutes + '分钟' + seconds + '秒';
        }, 1000);

    </script>
</body>

</html>

动画.gif

日期格式转换

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var dayArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
        
        setInterval(function () {

            var time = new Date();

            document.body.innerHTML = (time + '<br>' + time.getFullYear() + '年' + time.getDate() + '月' + time.getDate() + '日,' + dayArr[time.getDay()] + ',时间是' + time.getHours() + '点' + time.getMinutes() + '分' + time.getSeconds() + '秒');
        }, 1000);
    </script>
</body>

</html>

动画.gif

继承与内置构造函数

什么是继承

子类的实例对象可以调用父类定义的方法和属性,子类拥有父类的所有方法和属性,并且在父类的基础上还扩展了一些属性和方法

image.png

image.png

image.png

image.png

image.png

通过原型链实现继承(1)

实现继承

image.png

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 超类、基类
        function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.introduce = function () {
                console.log('你好,我是' + this.name);
            };
        };

        // 派生类
        function Student(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        };

        // 借助原型链实现继承
        Student.prototype = new People();

        var xiaoming = new Student('xiaoming', 18, '男');
        xiaoming.introduce();   // 子类调用父类的方法
        console.log(xiaoming);
    </script>
</body>

</html>

image.png

通过原型链实现继承(2)

通过原型链实现继承的问题

image.png 问题1 示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function People() {
            this.arr = [2, 4, 6, 8, 10];    // 向父类添加一个引用类型值
        };

        function Student() {

        };

        // 借助原型链实现继承
        Student.prototype = new People();

        var zhangSan = new Student();
        var liSi = new Student();

        console.log(zhangSan.arr);      // 所有的子类都可以共享父类的引用类型值
        console.log(liSi.arr);

        // 如果其中一个子类修改其值,则都会收到影响
        zhangSan.arr.push(0, 1, 2);
        console.log(zhangSan.arr);
        console.log(liSi.arr);
    </script>
</body>

</html>

image.png

问题2 示例

image.png

借用构造函数

image.png

解决了两个问题

  1. 引用类型值共享的问题(使引用类型值都可以在每个实例上使用,不会因为修改其中一个而另一个被影响)

  2. 重复定义父类定义过的属性

经典继承、伪造对象

  • 在子类的构造函数内部使用call来调用父类

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.arr = [33, 44, 55];
        };

        function Student(name, age, sex) {
            People.call(this, name, age, sex);    // 借用构造函数(经典继承、伪造对象)
        };

        var xiaoming = new Student('xxx', 18, '男');
        var xiaohong = new Student('xxx', 19, '女');
        console.log(xiaoming);
        console.log(xiaohong);

        xiaoming.arr.push(66, 77);    // 小明向数组中推入,不会影响小红
    </script>
</body>

</html>

image.png

解析

因为要结合call方法改变People方法中的this指向,所以调用call方法的第一个参数传递的是Student函数中的this。可以结合如下分析理解:

通过new关键字实例化People,从输出结果可知,People函数内部的this默认指向People当前实例对象

61b58ddd097d7f3716520836.png 但是,由于在Student中通过call方法调用函数People,改变了People内部的this,让其指向了Student函数中的this

61b58edd0911725e24981300.png 所以在实例化Student,依次执行Student函数内部的代码,执行到People.call(this, name, sex, age);这句代码时,People函数的this等价于Student构造函数实例对象,那么People函数内部this.arr= [33,44,55]等给this添加属性的代码,相当于就是给Student构造函数实例对象添加属性了,从而实现了Student继承People的一些属性。

所以最终实例化的Student对象,该对象下有arr等属性,如下:

61b58ff30947c76f16580536.png

组合继承、伪经典继承

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 父类
        function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        };

        People.prototype.introduce = function () {
            console.log('hello I\'m ' + this.name);
        };

        // 子类
        function Student(name, age, sex, school, id) {
            // 借助构造函数实现继承
            People.call(this, name, age, sex);
            this.school = school;
            this.id = id;
        };
        
        // 通过原型链实现继承
        Student.prototype = new People();

        // 复写(override)父类的方法
        Student.prototype.introduce = function () {
            console.log('hello I\'m ' + this.name + '同学');
        };

        var xiaoMing = new Student('小明', 18, '男','xxx学校','93628761');
        
        console.log(xiaoMing);
        console.log(xiaoMing.introduce());
    </script>
</body>

</html>

image.png 组合继承也有缺点

image.png

image.png

原型式继承

Object.create( )

我们先来学习Object.create()方法

Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。

Object.create()是js语法中提供的一个方法,根据指定的对象为原型 创建出新对象。

该方法接收两个参数

第一个参数 表示指定的对象

第二个参数 表示为新创建的对象补充添加其它属性。

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var obj1 = {
            num1: 1,
            num2: 2,
            num3: 3,
        };

        var obj2 = Object.create(obj1);     // 以obj1为原型,创建出新对象obj2

        console.log(obj2.__proto__ === obj1);   // 原型是它说明就有原型链查找了

        console.log(obj2.num1);     // 这三个属性都是原型链上的属性(在某种意义上,我们实现了这两个对象的继承关系)
        console.log(obj2.num2);
        console.log(obj2.num3);
    </script>
</body>

</html>

image.png Object.create( )的第二个参数

第二个参数是个对象,用于为创建出的新对象添加属性名与属性值(写法有些特殊)

demo如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var obj1 = {
            num1: 1,
            num2: 2,
        };

        var obj2 = Object.create(obj1, {
            num1: {         //若有同名属性,则会覆盖掉原型上的属性
                value: 111
            },
            xxx: {
                value: 222
            }
        });

        obj2.num3 = 333;    // 可以单独为新创建的对象添加属性

        console.log(obj2);
    </script>
</body>

</html>

image.png

微信图片_20220922141624.png

image.png 这就是原型式继承

注意

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var obj1 = {
            a: 1,
            b: 2,
            c: 3,
            test: function () {
                console.log(this.a + this.c);   // 当obj2调用原型上方法的时候,此时函数的上下文就指向了obj2,调用方法时,如果方法中的属性自身没有
            }                                   // 则会上原型上(obj1)查找,若自身有这个属性且同名,则优先使用自身上的属性
        };

        var obj2 = Object.create(obj1, {
            d: {
                value: 4
            },
            e: {
                value: 5
            },
            c: {
                value: 88
            }
        });
        
        obj2.test();
    </script>
</body>

</html>

image.png

此时代码中是没有类的,没有写一个构造函数,找不到父类也找不到子类。

在某种意义上,我们实现了这两个对象的继承关系 ,它被称作原型式继承

面试考题

image.png道格拉斯·克罗克福德 在2006年写的一个函数。 非常巧妙,面试常考。

函数的功能就是以o为原型,创建新对象

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function objectCreate(o) {
            // 创建一个临时的构造函数
            function F() { };
            F.prototype = o;  // 让这个构造函数的prototype指向o,那么new F()的实例对象__proto__属性就(指向)等于o
            return new F();     // 返回构造函数 F的实例
        };

        var obj1 = {
            num1: 1,
            num2: 2,
        };

        var obj2 = objectCreate(obj1);

        console.log(obj2.__proto__ === obj1);

        console.log(obj2.num1);
        console.log(obj2.num2);
    </script>
</body>

</html>

image.png

image.png

寄生式继承

也是由道格拉斯·克罗克福德推广、发明的

image.png 寄生式继承是指编写一个工厂函数,这个工厂函数会接收一个参数o,然后在工厂函数内部,以o为原型创建一个新对象p,同时给p添加预置的新方法,最后并返回新对象p。在整个过程中,新对象p是以o为原型创建的,也就是寄生在o上的,所以称为寄生式继承。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var obj = {
            a: 1,
            b: 3,
            c: 5
        };

        function f(o) {
            var p = Object.create(o);   //这句代码是重点:以o为原型创建一个新对象p

            p.sum = function () {
                console.log(this.a + this.b);
            };

            p.sub = function () {
                console.log(this.c - this.b);
            };

            return p;   // 返回新对象p
        }

        var p = f(obj);     // 调用工厂函数,传递参数obj,会以obj为原型创建出新对象p

        p.sum();
        p.sub();

        console.log(p.sum() === p.sub());
    </script>
</body>

</html>

image.png

image.png

寄生式继承的缺点

因为是向新对象中添加的方法和属性,根本没有原型。

所以会对内存造成一定的损耗。

image.png

寄生组合式继承

image.png 为了解决上述问题,我们可以使用寄生组合式继承

image.png

image.png 寄生组合式继承是指通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,主要对应以下两段代码,如图

61b5c5fc097bf97b09620626.png 关于借用构造函数来继承属性,主要就是在子类构造函数的内部调用超类的构造函数,并使用call()绑定上下文,即People.call(this, name, sex, age),这是前面讲的借用构造函数来实现继承的内容

关于通过原型链的混成形式来继承方法,其基本思路就是下面的那段话,通俗点讲就是:创建一个超类的原型副本,也就是使用寄生式继承来创建超类的原型副本,并将原型副本指定给子类的原型,此时不必再像以前一样为了指定子类的原型而调用超类的构造函数,从而就能减少一次调用超类的构造函数,提高性能,如图

61b5cb3b0926bd3612470239.png 此处可以理解为:创建一个超类的原型副本,也就是使用寄生式继承来创建超类的原型副本,并将原型副本指定给子类的原型,此时不必再像以前一样为了指定子类的原型而调用超类的构造函数,从而就能减少一次调用超类的构造函数,提高性能,然后将其赋值给子类原型后,子类的原型就指向了父类的原型,实现了继承,参考如下图所示:

63280c3d094b0c6008380238.png 示例如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 这个函数接收两个参数,subType是子类的构造函数,superType是父类的构造函数
        function inheritPrototype(subType, superType) {
            // 寄生式继承
            var prototype = Object.create(superType.prototype);
            subType.prototype = prototype;
        };

        // 超类
        function People(name, age) {
            this.name = name;
            this.age = age;
        };

        People.prototype.introduce = function () {
            console.log('我是' + this.name + '今年' + this.age + '岁');
        };
        
        // 子类
        function Student(name, age, id) {
            // 借用构造函数继承父类属性
            People.call(this, name, age);
            this.id = id;
        };

        inheritPrototype(Student, People);

        Student.prototype.exam = function () {
            console.log(this.name + '在考试,id是' + this.id);
        };

        var xiaoming = new Student('小明', 18, '2352153');
        console.log(xiaoming);
        xiaoming.exam();
        xiaoming.introduce();
    </script>
</body>

</html>

image.png 解析

多写一个inheritPrototype函数就是为了解决组合式继承中调用两次超类的情况。inheritPrototype函数中将超类的原型创建出一个新的对象,子类的原型指向这个对象,就继承了超类的原型。这样只在call()的时候调用一次超类。

虽然调用了一次inheritPrototype函数,但是没有再调用一次超类。

实际开发中一般用寄生组合式最多,因为属性只用了一份,超类只调用了一次。

instanceof运算符

  • 用于检测某个实例对象是否在构造函数prorotype属性的原型链上

instance 示例、对象

image.png instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

通俗来讲:右侧是不是左侧的爸爸、爷爷、祖宗,只要左侧对象继承自右侧函数就为 true。

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function People() {

        };

        function Student() {

        };

        // 借助原型链实现继承
        Student.prototype = new People();

        // 实例化一个对象
        var xiaoming = new Student();

        console.log(xiaoming instanceof People);
        console.log(xiaoming instanceof Student);
        console.log(xiaoming instanceof Object);
    </script>
</body>

</html>

image.png

内置构造函数(1)

image.png 在JavaScript中常见的引用类型有:Object类型 、Array类型、Function类型、RegExp类型等。

比如:Array就是数组类型的构造函数,Function就是函数类型的构造函数,Object就是对象类型的构造函数,所以内置构造函数都是引用类型

image.png demo如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 数组的内置构造函数,任何的数组字面量都可以看做是Array的实例
        console.log([] instanceof Array);
        console.log([1, 3, 5] instanceof Array);
        console.log([1, 3, 5] instanceof Object);

        var arr1 = new Array();
        console.log(arr1 instanceof Array);
        console.log(arr1 instanceof Object);

        var arr2 = new Array(6, 7, 8);
        console.log(arr2 instanceof Array);
        console.log(arr2 instanceof Object);
    </script>
</body>

</html>

image.png

        //  函数的内置构造函数,任何的函数字面量都可以看做是Function的实例
        function fun() {

        };

        function add(num1, num2) {
            return num1 + num2;
        };

        console.log(fun instanceof Function);
        console.log(fun instanceof Object);
        console.log(add instanceof Function);
        console.log(add instanceof Object);

        // 使用new作出函数
        var sub = new Function('num1', 'num2', 'return num1 - num2');   // 所有参数罗列在最开始,函数体中的语句要写在最后
        console.log(sub(3, 2));

image.png

        // 对象的内置构造函数
        console.log({} instanceof Object);
        console.log({ a: 1, b: 2 } instanceof Object);

        var obj = new Object();
        obj.a = 1;
        obj.hello = function () {
            console.log('hello world');
        };

        console.log(obj);
        console.log(obj instanceof Object);

image.png

内置构造函数(2)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 拓展数组的方法,为数组添加求和方法
        Array.prototype.qiuhe = function () {
            // this表示调用qiuhe()方法的数组
            var arr = this;
            var sum = 0;
            for (var i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        };

        var arr = [1, 3, 5];
        console.log(arr.qiuhe());
    </script>
</body>

</html>

image.png 解析

this可以理解为谁调用的方法,this就指向谁,这样理解也可以。

在原型对象prototype上定义的方法中的this,一般指向实例。arr就是Array的一个实例,所以this会指向arr。

再看如下示例

参与运算的时候,它们看着是new出来的对象,但运算起来与基本类型值一样

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 参与运算的时候,它们看着是new出来的对象,但运算起来与基本类型值一样
        var num = 123;
        console.log(num);
        console.log(typeof num);

        var o = new Number(123);
        console.log(o);
        console.log(typeof o);

        console.log(o + num);

        var str = new String('abc');
        console.log(str);
        console.log(typeof str);

        console.log(String.prototype.hasOwnProperty('substring'));
        console.log(String.prototype.hasOwnProperty('substr'));
        console.log(String.prototype.hasOwnProperty('slice'));

        var b = new Boolean(true);
        console.log(b);
        console.log(typeof b);
    </script>
</body>

</html>

image.png

内置构造函数的关系

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 数组原型链的关系
        console.log([].__proto__ === Array.prototype);
        console.log([].__proto__.__proto__ === Object.prototype);

        console.log([] instanceof Array);
        console.log([] instanceof Object);

        console.log(Object.prototype.__proto__);
        console.log(null.prototype);    // null 是没有__proto__的
    </script>
</body>

</html>

image.png

Function既是构造函数也是实例对象

image.png 1、Function既是构造函数,又是实例对象。既Function可以实例化出Function。

作为实例的Function,它的__proto__就指向它的构造函数Function的prototype(黄色箭头)。

而作为构造函数的Function,可以通过prototype获取到原型对象(红色箭头),所以就有了下图:

image.png 2、而Object的原型对象是Object.prototype:

image.png Object也是函数的实例,函数可以new出来一个Object()函数,所以它的__proto__指向函数的原型对象:

image.png 函数的原型对象,最终会指向Object的原型对象:

image.png 所以有了下图:

image.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        console.log(Function.__proto__ === Function.prototype);
        console.log(Object.__proto__ === Function.prototype);

        console.log(Function instanceof Function);
        console.log(Function instanceof Object);
        console.log(Object instanceof Function);    
    </script>
</body>

</html>

image.png 可结合如下图示进行理解

image.png

image.png

知识图谱

625ab45309fbc28221906544.png

6060406d09fd421b15406356.png