前端学习笔记之JS对象(八)

面向对象

认识对象

认识对象

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

在这里插入图片描述

对象的语法

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

在这里插入图片描述

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

在这里插入图片描述

  • 可以用“点语法”访问对象中指定键的值
xiaoming.name; // '小明'
xiaoming.age; // 12
xiaoming.hobbys; // ['足球', '游泳', '编程']
// 对象打点访问它的属性
  • 如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问
// 不符合标识符命名规范,就必须用方括号访问属性
xiaoming['favorite-book']; // '舒克和贝塔'
  • 如果属性名以变量形式存储,则必须使用方括号形式
var obj = {
    a: 1,
    b: 2,
    c: 3
};
//属性名用变量存储
var key = 'b';
console.log(obj.key); //undefined
console.log(obj[key]);// 2
console.log(obj.b); //2
  • 直接使用赋值运算符重新对某属性赋值即可更改属性
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;

对象的方法

  • 如果某个属性值是函数,则它也被称为对象的“方法”
var xiaoming = {
    name: '小明',
    age: 12,
    sex: '男',
    hobbys: ['足球', '游泳', '编程'],
    'favorite-book': '舒克和贝塔',
    sayHello: function () {
        console.log('你好,我是小明,今年12岁,我是个男生');
    }
};
  • 使用“点语法”可以调用对象的方法
xiaoming.sayHello();
  • 方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用
  • 在正式学习了什么是“方法”之后,就能深入理解之前我们学习的一些函数的书写形式了,比如:
console.log();
Math.ceil();

对象的遍历

  • 和遍历数组类似,对象也可以被遍历,遍历对象需要使用for…in…循环
  • 使用for…in…循环可以遍历对象的每个键
  • 在后续的ES6相关课程中,还会学习新的对象遍历的方式

在这里插入图片描述

对象的深浅克隆

  • 基本类型和引用类型

在这里插入图片描述

  • 对象是引用类型值,这意味着:
    • 不能用var obj2 = obj1这样的语法克隆一个对象
    • 使用==或者===进行对象的比较时,比较的是它们是否为内存中的同一个对象,而不是比较值是否相同
对象的浅克隆
  • 复习什么是浅克隆:只克隆对象的“表层”,如果对象的某些属性值又是引用类型值,则不进一步克隆它们,只是传递它们的引用。
  • 使用for…in…循环即可实现对象的浅克隆
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];
}

// 为什么叫浅克隆呢?比如c属性的值是引用类型值,那么本质上obj1和obj2的c属性是内存中的同一个数组,并没有被克隆分开。
obj1.c.push(77);
console.log(obj2);                  // obj2的c属性这个数组也会被增加77数组
console.log(obj1.c == obj2.c);      // true,true就证明了数组是同一个对象
对象的深克隆
  • 复习什么是深克隆:克隆对象的全貌,不论对象的属性值是否又是引用类型值,都能将它们实现克隆
  • 和数组的深克隆类似,对象的深克隆需要使用递归
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不变的,因为没有“藕断丝连”的现象

认识上下文

函数的上下文

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

函数中的this

var xiaoming = {
    nickname : '小明',
    age : 12,
    sayHello : function () {
    	console.log('我是' + this.nickname + ',我' + this.age + '岁了');
    }
};
xiaoming.sayHello(); //我是小明,我12岁了

在这里插入图片描述

函数的上下文由调用方式决定
  • 同一个函数,用不同的形式调用它,则函数的上下文不同

    • 情形1:对象打点调用函数,函数中的this指代这个打点的对象

      • xiaoming.sayHello();
    • 情形2:圆括号直接调用函数,函数中的this指代window对象

      • var sayHello = xiaoming.sayHello;
        sayHello();
        
  • 函数未调用无法判断this的指代

  • 函数只有被调用,它的上下文才能被确定

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

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

var fn = obj.fn;
fn(); //NaN

上下文规则

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

  • 函数的上下文(this关键字)由调用函数的方式决定,function是“运行时上下文”策略
  • 函数如果不调用,则不能确定函数的上下文
上下文规则1
  • 规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
对象.方法()
//Case 1
function fn() {
	console.log(this.a + this.b);
}
var obj = {
    a: 66,
    b: 33,
    fn: fn
};
obj.fn();  //99

//案例2
var obj1 = {
    a: 1,
    b: 2,
    fn: function () {
    	console.log(this.a + this.b);
    }
};
var obj2 = {
    a: 3,
    b: 4,
    fn: obj1.fn
};
obj2.fn(); // 7

//Case 3
function outer() {
    var a = 11;
    var b = 22;
    return {
        a: 33,
        b: 44,
        fn: function () {
        	console.log(this.a + this.b);
        }
    };
}
outer().fn();  //77

//Case 4
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].c(); //7
上下文规则2
  • 规则2:圆括号直接调用函数,则函数的上下文是window对象
函数()
//Case1
var obj1 = {
    a: 1,
    b: 2,
    fn: function () {
    	console.log(this.a + this.b);
    }
};
var a = 3;
var b = 4;
var fn = obj1.fn;
fn(); // 7

//Case2:
function fun() {
	return this.a + this.b;
}
var a = 1;
var b = 2;
var obj = {
    a: 3,
    b: fun(), //适用规则2
    fun: fun
};
var result = obj.fun(); //适用规则1
console.log(result);
上下文规则3
  • 规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
  • 什么是类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象
  • arguments对象是最常见的类数组对象,它是函数的实参列表
数组[下标]()
//case1:
var arr = ['A', 'B', 'C', function () {
	console.log(this[0]);
}];
arr[3](); //"A"

//Case2
function fun() {
	arguments[3]();
}
fun('A', 'B', 'C', function () {
	console.log(this[1]);
}); //"B"

上下文规则4
  • 规则4:IIFE中的函数,上下文是window对象
(function() {})();
//Case1
var a = 1;
var obj = {
    a: 2,
    fun: (function () {
        var a = this.a; //1
        return function () {
       		console.log(a + this.a); //this.a=2
    }
    })()
};
obj.fun(); //3
上下文规则5

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

setInterval(函数, 时间);
setTimeout(函数, 时间);
var obj = {
    a: 1,
    b: 2,
    fun: function () {
    	console.log(this.a + this.b);
    }
}
var a = 3;
var b = 4;
setTimeout(obj.fun, 2000); //7

//Case2
var obj = {
    a: 1,
    b: 2,
    fun: function () {
    	console.log(this.a + this.b); //适用规则1
    }
}
var a = 3;
var b = 4;

setTimeout(function() {
	obj.fun();
}, 2000); //3,注意这里和case1不同的是这里的fun是由obj直接调用,而非延时器调用
上下文规则6

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

DOM元素.onclick = function () {};
  • 请实现效果:点击哪个盒子,哪个盒子就变红,要求使用同一个事件处理函数实现
<!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>
       #box1{
           border: 1px solid #000;
           width: 100px; height: 100px;
           float: left;
           margin-left: 20px;
       } 
       #box2{
           border: 1px solid #000;
           width: 100px; height: 100px;
           float: left;
           margin-left: 20px;
       } 
       #box3{
           border: 1px solid #000;
           width: 100px; height: 100px;
           float: left;
           margin-left: 20px;
       } 

       #box4{
           border: 1px solid #000;
           width: 100px; height: 100px;
           float: left;
           margin-left: 20px;
       } 
    </style>
</head>
<body>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    <div id="box4"></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");
        var box4 = document.getElementById("box4");

        box1.onclick = setColorToRed;
        box2.onclick = setColorToRed;
        box3.onclick = setColorToRed;

        function setColorToRed1(o){
            o.style.backgroundColor = 'red';
        }
        box4.onclick = function(){
            setColorToRed1(box4);
        }

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

var a = 4;
var b = 9;

obj.fn(); //3 False [this===obj] true
var fn = obj.fn;
fn(); //13 True

call和apply

call和apply能指定上下文

function sum() {
	alert(this.chinese + this.math + this.english);
}
var xiaoming = {
    chinese: 80,
    math: 95,
    english: 93
};
sum.all(xiaoming);
sum.apply(xiaoming);

call和apply的区别

function sum(b1, b2) {
	alert(this.c + this.m + this.e + b1 + b2);
}
var xiaoming = {
    c: 80,
    m: 95,
    e: 93
};
sum.call(xiaoming, 5, 3);
sum.apply(xiaoming, [5, 3]);
function sum(){
    alert(this.a+this.b+this.c);
}
function sum1(b1,b2){
    alert(this.a+this.b+this.c+b1+b2);
}

var tom = {
    a:100,
    b:90,
    c:95
}
var jack = {
    a:100,
    b:90,
    c:95,
    sum:sum,
}
jack.sum();
sum.call(tom);
sum.apply(tom);
sum1.call(tom,90,80);   //call接受参数的时候 
sum1.apply(tom,[90,98]);

function fun1(){
    fun2.apply(this,arguments); 
}
function fun2(a,b){
    alert(a+b);
}
fun1(34,43); //77 这里直接调用的话this会指向windows对象

上下文规则总结

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

构造函数

用new调用函数四步走

  • 现在,我们学习一种新的函数调用方式: new 函数()

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

  • JS规定,使用new操作符调用函数会进行“四步走”

    • 函数体内会自动创建出一个空白对象
    • 函数的上下文(this)会指向这个对象
    • 函数体内的语句会执行
    • 函数会自动返回上下文对象,即使函数没有return语句
  • 四步走详解

function fun() {
    this.a = 3;
    this.b = 5;
}
var obj = new fun();
console.log(obj);
四步走详解 - 第1步
  • 第1步:函数体内会自动创建出一个空白对象

在这里插入图片描述

四步走详解 - 第2步
  • 第2步:函数的上下文(this)会指向这个对象

在这里插入图片描述

四步走详解 - 第3步

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

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

构造函数

什么是构造函数
  • 我们将之前书写的函数进行一下小改进:

在这里插入图片描述

在这里插入图片描述

  • 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它。
  • 顾名思义,构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化。
  • 构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写。
构造函数名称首字母约定大写

在这里插入图片描述

  • 一个函数名称首字母大写了,它不一定是构造函数
  • 一定要记住:一个函数是不是构造函数,要看它是否用new调用,而至于名称首字母大写,完全是开发者的习惯约定
  • 如果不用new调用构造函数,例如
function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
//这时适用于上下文规则1,函数直接调用this代表window,故此时全局对象的name和age和sex一直在变
People('小明', 12, '男');
People('小红', 10, '女');
People('小刚', 13, '男');
  • 构造函数中的this不是函数本身,而是由构造函数所秘密创建的对象。
function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var xiaoming = new People('小明', 12, '男'); //{} => xiaoming
var xiaohong = new People('小红', 10, '女');
var xiaogang = new People('小刚', 13, '男');
  • 为对象添加方法

在这里插入图片描述

function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.sayHello = function(){
        console.log("我是"+this.name+", 我"+this.age+"岁了,我是一个"+this.sex+"生。");
    }
    this.sleep = function(){
        console.log(this.name + "正在💤");
    }
}

var tom = new People("tom",12,"男"); 
var jack = new People("jack",13,"男");
var mary  = new People("mary",14,"女");

console.log(tom); //People {name: "tom", age: 12, sex: "男", sayHello: ƒ, sleep: ƒ}
console.log(jack); //People {name: "jack", age: 13, sex: "男", sayHello: ƒ, sleep: ƒ}
console.log(mary); //People {name: "mary", age: 14, sex: "女", sayHello: ƒ, sleep: ƒ}
tom.sayHello();
tom.sleep(); 
jack.sayHello();

类和实例

在这里插入图片描述

  • 如同“蓝图”一样,类只描述对象会拥有哪些属性和方法,但是并不具体指明属性的值
  • 实例是具体的一个对象,而非一类对象
  • “狗”是类,“史努比”是实例、“小白”是实例

在这里插入图片描述

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

原型和原型链

prototype和原型链查找

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

在这里插入图片描述

function sum(a,b){
    return a+b;
}

console.log(sum.prototype); //{constructor: ƒ}
console.log(typeof sum.prototype);  //object
console.log(sum.prototype.constructor === sum); //true

function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var tom = new People("tom",12,'男');
console.log(tom.__proto__ === People.prototype);
  • 普通函数来说的prototype属性没有任何用处,而构造函数的prototype属性非常有用
  • 构造函数的prototype属性是它的实例的原型

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • hasOwnProperty方法可以检查对象是否真正“自己拥有”某属性或者方法
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('age'); // true
xiaoming.hasOwnProperty('sex'); // true
xiaoming.hasOwnProperty('nationality'); // false
  • in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 往原型上添加nationality属性
People.prototype.nationality = "中国";
//实例化Tom
var Tom = new People("Tom",12,"男");
console.log(Tom.nationality);
//实例化Jack
var Jack = new People("Jack",10,'男');
Jack.nationality = "美国";
console.log(Jack.nationality); //美国,
console.log(Tom.hasOwnProperty("age"));         //true
console.log(Tom.hasOwnProperty("nationality")); //false
console.log(Jack.hasOwnProperty("nationality"));//true
console.log('name' in Tom); 					//true
console.log('nationality' in Tom); 				//true

在prototype上添加方法

  • 在之前的课程中,我们把方法都是直接添加到实例身上:
解决办法:将方法写到prototype上function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.sayHello = function () {   //方法直接添加到实例身上
    	console.log('我是' + this.name);
	};
}
  • 把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费

在这里插入图片描述

  • 解决办法:将方法写到prototype上

在这里插入图片描述

function People(){
    this.sayHello = function(){
    };
}

var a = new People();
var b = new People();
var c = new People();
console.log(a.sayHello === b.sayHello); //false,每个实例的函数不在一个地址有点浪费内存

//改进
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 Jack = new People("Jack",10,'男');
var Tom = new People("Tom",12,"男");

console.log(Jack.sayHello === Tom.sayHello); //true
Jack.sayHello(); //我是Jack, 我今年10岁了!
Tom.sayHello(); //我是Tom, 我今年12岁了!
Jack.growUp();
Jack.sayHello(); //我是Jack, 我今年11岁了

原型链的终点

在这里插入图片描述

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

var xiaoming = new People("xiaoming",12,'male');
console.log(xiaoming.__proto__.__proto__  === Object.prototype);      //true
console.log(Object.prototype.__proto__);                         //null
console.log(Object.prototype.hasOwnProperty("hasOwnProperty"));  //true
console.log(People.prototype.hasOwnProperty("hasOwnProperty"));  //false
console.log(Object.prototype.hasOwnProperty("toString"));       //true

关于数组的原型链

在这里插入图片描述

var arr = [1,2,3];
// 以下结果均为true
console.log(arr.__proto__ === Array.prototype);
console.log(arr.__proto__.__proto__ === Object.prototype);
console.log(Array.prototype.hasOwnProperty("push"))
console.log(Array.prototype.hasOwnProperty("splice"))
console.log(Array.prototype.hasOwnProperty("slice"))
console.log(Array.prototype.hasOwnProperty("sort"))

继承

在这里插入图片描述

在这里插入图片描述

  • People类拥有的属性和方法Student类都有,Student类还扩展了一些属性和方法;
  • Student “是一种“ People,两类之间是 ”is a kind of“的关系
  • 这就是”继承“关系:Student类继承自People类
什么是继承
  • 继承描述了两个类之间的 ”is a kind of“ 的关系,比如学生是一种人,所以人类和学术类之间就构成继承关系
  • People是父类(或超类、基类);Student是子类(或派生类)
  • 子类丰富了父类,让类描述得更加具体、更细化

在这里插入图片描述

在这里插入图片描述

JavaScript中如何实现继承
  • 实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法
  • 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
  • ES6中会有新的继承方法
通过原型链实现继承

在这里插入图片描述

在这里插入图片描述

// 父类,人类
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, school, studentNumber) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.school = school;
    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, '女', '慕课小学', 100556);

hanmeimei.study();
hanmeimei.sayHello();
hanmeimei.sleep();

var laozhang = new People('老张', 66, '男');
laozhang.sayHello();
laozhang.study(); //Error

上升到面向对象

上升到面向对象小案例1

  • 面向对象的本质:定义不同的类,让类的实例工作
  • 面向对象的优点:程序编写更清晰、代码结构更严密、使代码更健壮更利于维护
  • 面向对象经常用到的场合:需要封装和复用性的场合(组件思维)

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box img{
            width: 80px;
        }
    </style>
</head>
<body>
    <div id="box"></div>

    <script>
        // 定义红绿灯类
        function TrafficLight() {
            // 颜色属性,一开始都是红色
            // 红色1、黄色2、绿色3
            this.color = 1;
            // 调用自己的初始化方法
            this.init();
            // 绑定监听
            this.bindEvent();
        }
        // 初始化方法
        TrafficLight.prototype.init = function() {
            // 创建自己的DOM
            this.dom = document.createElement('img');
            // 设置src属性
            this.dom.src = 'images/' + this.color + '.jpg';
            box.appendChild(this.dom);
        };
        // 绑定监听
        TrafficLight.prototype.bindEvent = function() {
            // 备份上下文,这里的this指的是JS的实例
            var self = this;
            // 当自己的dom被点击的时候
            this.dom.onclick = function () {
                // 当被点击的时候,调用自己的changeColor方法
                self.changeColor();
            };
        }
        // 改变颜色
        TrafficLight.prototype.changeColor = function () {
            // 改变自己的color属性,从而有一种“自治”的感觉,自己管理自己,不干扰别的红绿灯
            this.color ++;
            if (this.color == 4) {
                this.color = 1;
            }
            // 光color属性变化没有用,还要更改自己的dom的src属性
            this.dom.src = 'images/' + this.color + '.jpg';
        };

        // 得到盒子
        var box = document.getElementById('box');


        // 实例化100个
        var count = 100;

        while(count--){
            new TrafficLight();
        }
         
    </script>

</body>
</html>
  • 使用面向对象编程,就能以“组件化”的思维解决大量红绿灯互相冲突的问题
  • 面向对象编程,最重要的就是编写类

上升到面向对象小案例2

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

在这里插入图片描述

在这里插入图片描述

如何实现多个小球动画

  • 把每个小球实例都放到同一个数组中

{{小球实例},{小球实例},{小球实例},{小球实例}}

  • 只需要使用1个定时器,在每一帧遍历每个小球,调用它们的update方法
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        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.opacity = 1;
            // 小球背景颜色,从颜色数组中随机选择一个颜色
            this.color = colorArr[parseInt(Math.random() * colorArr.length)];
            // 这个小球的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.init();
            // 把自己推入数组,注意,这里的this不是类本身,而是实例
            ballArr.push(this);
        }
        // 初始化方法
        Ball.prototype.init = function () {
            // 创建自己的dom
            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);
        };
        // 更新
        Ball.prototype.update = function () {
            // 位置改变
            this.x += this.dX;
            this.y -= this.dY;
            // 半径改变
            this.r += 0.2;
            // 透明度改变
            this.opacity -= 0.01;
            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.opacity = this.opacity;

            // 当透明度小于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 = ['#66CCCC', '#CCFF66', '#FF99CC', '#FF6666', 
    '#CC3399', '#FF6600'];

        // 定时器,负责更新所有的小球实例
        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>

JS的内置对象

包装类

什么是包装类

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

举例

var a = new Number(123);
var b = new String('慕课网');
var c = new Boolean(true);
  • a、b、c是基本类型值么?它们和普通的数字、字符串、布尔值有什么区别么?
var a = new Number(123);
var b = new String('慕课网');
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)); // '慕课'
console.log(c && true);     // true

var d = 123;
console.log(d.__proto__ == Number.prototype);       // true

var e = '慕课网';
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

总结

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

Math对象

  • 幂和开方:Math.pow()、Math.sqrt()
  • 向上取整和向下取整:Math.ceil()、Math.floor()
  • Math.round()可以将一个数字四舍五入为整数
console.log(Math.round(3.4)); // 3
console.log(Math.round(3.5)); // 4
console.log(Math.round(3.98)); // 4
console.log(Math.round(3.49)); // 3
  • 如何才能实现“四舍五入到小数点后某位”呢?

在这里插入图片描述

var a = 12.3567 //四舍五入至百分位
console.log(Math.round(a*100)/100); //12.36
  • 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()可以得到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’)这样的写法

在这里插入图片描述

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

在页面上实时显示距离2021年8月23日9点30分还有多少天、多少时、多少分、多少秒

<!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>距离开学日期仅剩下:</h1>
    <h2 id='info'></h2>
    
    <script>
        function countDays(){
            var nowDate = new Date();
            var targetDate = new Date(2021,7,23,9,30);  //2021-8-23-9:30
            var diff = targetDate-nowDate;
            var days = parseInt(diff/(1000*60*60*24));  
            var hours = parseInt(diff%(1000*60*60*24)/(1000*60*60));
            var minutes = parseInt(diff%(1000*60*60)/(1000*60));
            var seconds = parseInt(diff%(1000*60)/1000);
            document.getElementById('info').innerText = days+"天"+hours+"小时"+minutes+'分钟'+seconds+'秒';
        }
        setInterval(countDays,1000);
    </script>
</body>
</html>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值