整体介绍

认识对象 1)认识对象 2)对象的方法 3)对象的遍历 4)对象的深浅克隆 认识函数的上下文 1)上下文规则1 2)上下文规则2 3)上下文规则3 4)上下文规则4 4)上下文规则5 5)上下文规则6 7)call和apply 构造函数 1)用new调用函数的四步走 2)构造函数 3)类和实例 原型和原型链 1)prototype和原型链查找 2)在prototype上添加方法 3)原型链的终点 4)继承 上升到面向对象 1)上升到面向对象小案例1 2)上升到面向对象小案例2 JS的内置对象 1)包装类 2)Math对象 3)Date对象

认识对象

对象(object)是”键值对”的集合,表示属性和值的映射关系

对象的语法

k和V之间用冒号分隔,每组k:V之间用逗号分隔,最后一个k:v对后可以不书写逗号

属性是否加引号

如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹

属性的访问

可以用“点语法”访问对象中指定键的值

如果属性名不符合]S标识符命名规范,则必须用方括号的写法来访问

如果属性名以变量形式存储,则必须使用方括号形式

属性的更改

直接使用赋值运算符重新对某属性赋值即可更改属性

var obj = {
  a:10
};
obj.a=30;
obj.a++;

属性的创建

如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来

var obj = {
  a:10
};
obj.b=40;

属性的删除

如果要删除某个对象的属性,需要使用delete操作符

var obj= {
a:1,
b:2
};
delete obj.a;

对象的方法

如果某个属性值是函数,则它也被称为对象的“方法”

方法的调用

◆使用“点语法”可以调用对象的方法 xiaoming.sayHello();

方法和函数

方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用

对象的遍历

和遍历数组类似,对象也可以被遍历,遍历对象需要使用for…in…循环

使用for…in…循环可以遍历对象的每个键

for…in..循环

对象的深浅克隆

对象是引用类型值

对象是引用类型值,这意味着: 不能用var obj2=obj1这样的语法克隆一个对象,把obj2指向与obj1同样的堆内存区域 使用或者=进行对象的比较时,比较的是它们是否为内存中的同一个对象,而不是比较值是否相同

<script>
        // 例子1
        var obj1 = {
            a: 1,
            b: 2,
            c: 3
        };
        var obj2 = {
            a: 1,
            b: 2,
            c: 3
        };
        console.log(obj1 == obj2);      // false
        console.log(obj1 === obj2);     // false
 
        console.log({} == {});          // false
        console.log({} === {});         // false
 
        // 例子2
        var obj3 = {
            a: 10
        };
        var obj4 = obj3;
        obj3.a ++;
        console.log(obj4);      // {a: 11}
 
    </script>

在例子三中,obj4不会克隆obj3这个对象,而是指向obj3,obj3.a++,obj4也会跟着改变,所以obj3和obj4压根是同一个对象

对象的浅克隆

复习什么是浅克隆:只克隆对象的“表层”,如果对象的某些属性值又是引用类型值,则不进一步克隆它们,只是传递它们的引用

使用for…in..循环即可实现对象的浅克隆

<script>
        var obj1 = {
            a: 1,
            b: 2,
            c: [44, 55, 66]
        };
 
        // 实现浅克隆
        var obj2 = {};
        for (var k in obj1) {
            // 每遍历一个k属性,就给obj2也添加一个同名的k属性
            // 值和obj1的k属性值相同
            obj2[k] = obj1[k];
        }
        //更改a和b互不干扰,更改c就会干扰
        // 为什么叫浅克隆呢?比如c属性的值是引用类型值,那么本质上obj1和obj2的c属性是内存中的同一个数组,并没有被克隆分开。
        obj1.c.push(77);
        console.log(obj2);                  // obj2的c属性这个数组也会被增加77数组
        console.log(obj1.c == obj2.c);      // true,true就证明了数组是同一个对象
    </script>

对象里有数组,数组是引用类型值,需要使用递归将c这个数组里的克隆出来,而for循环无能为力,所以叫浅克隆

对象的深克隆

复习什么是深克隆:克隆对象的全貌,不论对象的属性值是否又是引用类型值,都能将它们实现克隆

和数组的深克隆类似,对象的深克隆需要使用递归 面试时经常会考察深克隆算法,必须掌握

<script>
        var obj1 = {
            a: 1,
            b: 2,
            c: [33, 44, {
                m: 55,
                n: 66,
                p: [77, 88]
            }]
        };
 
        // 深克隆
        function deepClone(o) {
            // 要判断o是对象还是数组
            if (Array.isArray(o)) {
                // 数组
                var result = [];
                for (var i = 0; i < o.length; i++) {
                    result.push(deepClone(o[i]));
                }
            } else if (typeof o == 'object') {
                // 对象
                var result = {};
                for (var k in o) {
                    result[k] = deepClone(o[k]);
                }
            } else {
                // 基本类型值
                var result = o;
            }
            return result;
        }
 
 
        var obj2 = deepClone(obj1);
        console.log(obj2);
 
        console.log(obj1.c == obj2.c);      // false 内存中不同对象 值相同,地址不同
 
        obj1.c.push(99);
        console.log(obj2);                  // obj2不变的,因为没有“藕断丝连”的现象
 
        obj1.c[2].p.push(999);
        console.log(obj2);                  // obj2不变的,因为没有“藕断丝连”的现象
 
    </script>

认识上下文

函数上下文

函数中可以使用this关键字,它表示函数的上下文 与中文中“这”类似,函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断

函数的上下文由调用方式决定

函数的上下文(this关键字)由调用函数的方式决定,function是”运行时上下文”策略

◆函数如果不调用,则不能确定函数的上下文

同一个函数,用不同的形式调用它,则函数的上下文不同 情形1:对象打点调用函数,函数中的this指代这个打点的对象 xiaoming.sayHello(); 情形2:圆括号直接调用函数,函数中的this指代window对象 var sayHello = xiaoming.sayHello; sayHello();

this不知道,函数只有被调用。它的上下文才能被确定 如果只有obj.fn();,答案就为3,如果是var fn = obj.fn; fn(),答案就为NaN

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

此时答案为13,因为此处this指代window对象,windows对象是全局变量,即定义的var a和var b

上下文规则1

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

上下文规则2

规则2:圆括号直接调用函数,则函数的上下文是window对象 函数()

上下文规则3

规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象) 数组[下标]()

类数组对象

什么是类数组对象:所有键名为自然数序列(从开始),且有length,属性的对象 ◆arguments对象是最常见的类数组对象,它是函数的实参列表

上下文规则4

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

上下文规则5

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

上下文规则6

规则6:事件处理函数的上下文是绑定事件的DOM元素 DOM元素.onclick=function(){}

<style>
        div{
            width: 200px;
            height: 200px;
            float: left;
            border: 1px solid #000;
            margin-right: 10px;
        }
    </style>
</head>
<body>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
 
    <script>
        function setColorToRed() {
            // 备份上下文
            var self = this;
            setTimeout(function() {
                self.style.backgroundColor = 'red';
            }, 2000);
        }
 
        var box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');
        var box3 = document.getElementById('box3');
 
        box1.onclick = setColorToRed;
        box2.onclick = setColorToRed;
        box3.onclick = setColorToRed;
    </script>
</body>

call和apply

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

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

<script>
        function sum() {
            alert(this.c + this.m + this.e + b1 + b2);
        };
 
        var xiaoming = {
            c: 100,
            m: 90,
            e: 80
        };
 
        sum.call(xiaoming,); //270
        sum.apply(xiaoming);//270
    </script>

cal和applyl的区别

到底使用call还是apply?

arguments是伪数组

但就是77

上下文规则总结

用new调用函数的四步走

用new操作符调用函数

现在,我们学习一种新的函数调用方式: new 函数() 你可能知道new操作符和“面向对象”息息相关,但是现在,我们先不探讨它的”面向对象”意义,而是先把用new调用函数的执行步骤和它上下文弄清楚

JS规定,使用new操作符调用函数会进行”四步走”: 1)函数体内会自动创建出一个空白对象 2)函数的上下文(this)会指向这个对象 3)函数体内的语句会执行 4)函数会自动返回上下文对象,即使函数没有return语句

四步走详解

第1步:函数体内会自动创建出一个空白对象

第2步:函数的上下文(this)会指向这个对象

第3步:执行函数体中的语句

第4步:函数会自动返回上下文对象,即使函数没有return语句

上下文规则继续总结:

构造函数

什么是构造函数

我们将之前书写的函数进行一下小改进:

用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它

顾名思义,构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化

构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写

构造函数名称首字母约定大写

构造函数中的this不是函数本身

此处的this是新对象,将来会成为xiaoming,xiaohong,xiaogang

尝试为对象添加方法

类与实例

类好比是“蓝图”

如同“蓝图”一样,类只描述对象会拥有哪些属性和方法,但是并不具体指明属性的值

构造函数和“类”

Java、C++等是”面向对象”(object-oriented)语言 JavaScript是“基于对象”(object-based)语言 JavaScript中的构造函数可以类比于OO语言中的”类” 写法的确类似,但和真正OO语言还是有本质不同,在后续课程还将看见JS和其他00语言完全不同的、特有的原型特性

JS使用构造函数充当类

prototype和原型链查找

什么是prototype

任何函数都有prototype属性,prototype是英语”原型”的意思 prototype,属性值是个对象,它默认拥有constructor属性指回函数

<script>
        function sum(a, b){
            return a + b;
        }
 
        console.log(sum.prototype);
        console.log(typeof sum.prototype);
        console.log(sum.prototype.constructor === sum);
    </script>

普通函数来说的prototype属性没有任何用处,而构造函数的prototype,属性非常有用构造函数的prototype属性是它的实例的原型

原型链查找

◆JavaScript规定:实例可以打点访问它的原型的属性和方法,这被称为“原型链查找”

原型的遮蔽效应: 小明这个实例找不到国籍这个属性,就会在原型上去找,如果能在自身找到,就不会去寻找原型上的属性

hasOwnProperty

hasOwnProperty方法可以检查对象是否真正”自己拥有’某属性或者方法

in

in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法

在prototype上添加方法

把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费解决办法:将方法写到prototype.上

方法要写到prototype上

<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.growup = function () {
            this.age ++;
        }
 
        var xiaoming = new People('小明', 12, '男');
        var xiaohong = new People('小红', 11, '女');
 
        console.log(xiaoming.sayHello === xiaohong.sayHello);
 
        xiaoming.sayHello();
        xiaohong.sayHello();
 
        xiaoming.growup();
        xiaoming.growup();
        xiaoming.growup();
        xiaoming.growup();
        xiaoming.growup();
 
        xiaoming.sayHello();
        xiaohong.sayHello();
    </script>

原型链的终点

<script>
        function People(name, age) {
            this.name = name;
            this.age = age;
        }
        var xiaoming = new People('小明', 12);
 
        console.log(xiaoming.__proto__.__proto__ === Object.prototype);     // true
        console.log(Object.prototype.__proto__);                            // null
 
        console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));     // true
        console.log(Object.prototype.hasOwnProperty('toString'));           // true
    </script>

关于数组的原型链

<script>
        var arr = [344, 45, 34, 23];
 
        console.log(arr.__proto__ === Array.prototype);                 // true
        console.log(arr.__proto__.__proto__ === Object.prototype);      // true
        console.log(Array.prototype.hasOwnProperty('push'));            // true
        console.log(Array.prototype.hasOwnProperty('splice'));          // true
    </script>

继承

继承描述了两个类之间的”is a kind of”关系,比如学生“是一种”人,所以人类和学生类之间就构成继承关系 ◆People是”父类”(或”超类”、“基类”);Student是“子类”(或“派生类”) ◆子类丰富了父类,让类描述得更具体、更细化

JavaScript中如何实现继承

实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法 ◆使用JavaScript特有的原型链特性来实现继承,是普遍的做法 在今后学习ES6时,将介绍新的实现继承的方法

通过原型链实现继承

<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 + '开始睡觉,zzzzz');
        };
 
        // 子类,学生类
        function Student(name, age, sex, scholl, studentNumber) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.scholl = scholl;
            this.studentNumber = studentNumber;
        }
        // 关键语句,实现继承
        Student.prototype = new People();
 
        Student.prototype.study = function () {
            console.log(this.name + '正在学习');
        }
        Student.prototype.exam = function () {
            console.log(this.name + '正在考试,加油!');
        }
        // 重写、复写(override)父类的sayHello
        Student.prototype.sayHello = function () {
            console.log('敬礼!我是' + this.name + '我今年' + this.age + '岁了');
        }
 
        // 实例化
        var hanmeimei = new Student('韩梅梅', 9, '女', 'MK小学', 100556);
 
        hanmeimei.study();
        hanmeimei.sayHello();
        hanmeimei.sleep();
 
        var laozhang = new People('老张', 66, '男');
        laozhang.sayHello();
    </script

包装类

Number()、String()和Boolean()分别是数字、字符串、布尔值的“包装类” 很多编程语言都有“包装类”的设计,包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法

<script>
        var a = new Number(123);
        var b = new String('MK网');
        var c = new Boolean(true);
 
        console.log(a);
        console.log(typeof a);      // object
        console.log(b);
        console.log(typeof b);      // object
        console.log(c);
        console.log(typeof c);      // object
 
        console.log(5 + a);         // 128
        console.log(b.slice(0, 2)); // 'MK'
        console.log(c && true);     // true
 
        var d = 123;
        console.log(d.__proto__ == Number.prototype);       // true
 
        var e = 'MK网';
        console.log(e.__proto__ == String.prototype);       // true
        console.log(String.prototype.hasOwnProperty('toLowerCase'));    // true
        console.log(String.prototype.hasOwnProperty('slice'));          // true
        console.log(String.prototype.hasOwnProperty('substr'));         // true
        console.log(String.prototype.hasOwnProperty('substring'));      // true
    </script>

包装类知识总结

Number()、String()和Boolean()的实例都是object类型,它们的PrimitiveValue属性存储它们的本身值 new出来的基本类型值可以正常参与运算 包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法

Math(数学)对象

已经学习过的Math对象的方法

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

四舍五入Math.round()

Math.round()可以将一个数字四舍五入为整数

四舍五入到小数点后某位

如何才能实现“四舍五入到小数点后某位”呢?

Math.max()和Math.min()

◆Math.max()可以得到参数列表的最大值 ◆Math.min()可以得到参数列表的最小值

console.log(Math.max(6,2,9,4)); //9 console.log(Math.min(6,2,9,4)); //2

如何利用Math.max()求数组最大值

Math.max()要求参数必须是“罗列出来”,而不能是数组 还记得apply方法么?它可以指定函数的上下文,并且以数组的形式传入“零散值”当做函数的参数

var arr=[3,6,9,2];
var max Math.max.apply(null,arr);
console.log(max);//9

随机数Math.random()

◆Math.random()可以得到0~1之间的小数 ◆为了得到[a,b]区间内的整数,可以使用这个公式: parseInt(Math.random()*(b-a +1))+a

Date(日期)对象

◆使用new Date()即可得到当前时间的日期对象,它是object类型值 ◆使用new Date(2020,11,1)即可得到指定日期的日期对象,注意第二个参数表示月份,从0开始算,11表示12月 ◆也可以是new Date(‘2020-12-01’)这样的写法

<script>
        // 什么参数都不加,自动得到今天此时此刻的日期对象
        var d1 = new Date();
        console.log(d1);
        console.log(typeof d1);
 
        // 得到六月一日
        var d2 = new Date(2020, 5, 1);      // 不算时区
        var d3 = new Date('2020-06-01');    // 算时区,8点
 
        console.log(d2);
        console.log(d3);
    </script>

日期对象的常见的方法

<script>
        var d = new Date();
 
        console.log('日期', d.getDate());
        console.log('星期', d.getDay());
        console.log('年份', d.getFullYear());
        console.log('月份', d.getMonth() + 1);
        console.log('小时', d.getHours());
        console.log('分钟', d.getMinutes());
        console.log('秒数', d.getSeconds());
    </script>

时间戳

◆时间戳表示1970年1月1日零点整距离某时刻的毫秒数 ◆通过getTime()方法或者Date.parse()函数可以将日期对象变 为时间戳 ◆通过new Date(时间戳)的写法,可以将时间戳变为日期对象

<script>
        // 日期对象
        var d = new Date();
 
        // 显示时间戳的两种方法。时间戳表示1970年1月1日距离此时的毫秒数
        var timestamp1 = d.getTime();       // 精确到毫秒
        var timestamp2 = Date.parse(d);     // 精确到秒,也是毫秒数,只不过最后三位一定是000
 
        console.log(timestamp1);
        console.log(timestamp2);
 
        // 如何把时间戳,比如1601536565000变回日期对象呢?
        var dd = new Date(1601536565000);
        console.log(dd);
        console.log(dd.getFullYear());
    </script>