# 面向对象
# 对象简介
对象( object)是“键值对”的集合,表示属性和值的映射关系
let obj = {
//属性名(键名,key) 属性值(value) k : v对
name: "云牧",
age: 18,
sex: "男",
hobbies: ["打篮球","编程"]
}
//JS中,大括号表示对象
2
3
4
5
6
7
8
k和v之间用冒号分隔,每组k : v之间用逗号分隔,最后一个k :v对后可以不书写逗号
属性是否加引号
如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹
let obj = {
"na-me": "云牧",
age: 18,
sex: "男",
hobbies: ["打篮球","编程"]
}
2
3
4
5
6
属性的访问
可以用“点语法”访问对象中指定键的值
let obj = {
name: "云牧",
age: 18,
sex: "男",
hobbies: ["打篮球","编程"]
}
console.log(obj.name); //云牧
console.log(obj.age); //18
console.log(obj.hobbies); //["打篮球", "编程"]
2
3
4
5
6
7
8
9
10
如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问
let obj = {
"na-me": "云牧",
};
console.log(obj["na-me"]);
2
3
4
5
如果属性名以变量形式存储,则必须使用方括号形式
let obj = {
a: 1,
b: 2,
c: 3
};
let key = 'b';
console.log(obj.key); //undefined
console.log(obj[key]); //2
2
3
4
5
6
7
8
属性的更改
直接使用赋值运算符重新对某属性赋值即可更改属性
let obj = {
a: 10
};
obj.a = 30;
obj.a++;
2
3
4
5
属性的创建
如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
let obj = {
a: 10
};
obj.b = 40;
2
3
4
属性的删除
如果要删除某个对象的属性,需要使用delete操作符
let obj = {
a: 1,
b: 2
}
delete obj.a;
2
3
4
5
# 对象的方法
如果某个属性值是函数,则他被称为对象的方法
let obj1 = {
name: "云牧",
age: 18,
sex: "男",
sayHello: function(){
console.log("大家好 我是练习时长一年半的练习生坤坤")
}
}
let obj1 = {
name: "夕颜",
age: 18,
sex: "男",
sayHello: function(){
console.log("大家好 我是练习时长一年半的练习生坤坤")
}
}
//sayHello方法
//使用“点语法”可以调用对象的方法
xiaoming.sayHello() ;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用
# 对象的遍历
和遍历数组类似,对象也可以被遍历,遍历对象需要使用for. . .in...循环
使用for. . .in...循环可以遍历对象的每个键
for (var k in obj) { //循环变量k它会一次成为对象的每一个键
console.log("属性"+ k + "的值是"+ obj[k]); //obj代表要遍历的对象
}
2
3
# 对象的深浅克隆
对象是引用类型值,这意味着:
不能用 let obj2 = obj1这样的语法克隆一个对象
使用==或者===进行对象的比较时,比较的是它们是否为内存中的同一个对象,而不是比较值是否相同
// 例子1
let obj1 = {
a: 1,
b: 2,
c: 3
};
let obj2 = {
a: 1,
b: 2,
c: 3
};
console.log(obj1 == obj2); // false
console.log(obj1 === obj2); // false
console.log({} == {}); // false
console.log({} === {}); // false
// 例子2
let obj3 = {
a: 10
};
var obj4 = obj3;
obj3.a ++;
console.log(obj4); // {a: 11}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 浅克隆
只克隆对象的“表层”,如果对象的某些属性值又是引用类型值,则不进一步克隆它们,只是传递它们的引用
let obj1 = {
a: 1,
b: 2,
c: [44, 55, 66]
};
// 实现浅克隆
let obj2 = {};
for (let 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就证明了数组是同一个对象
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 深克隆
克隆对象的全貌,不论对象的属性值是否又是引用类型值,都能将它们实现克隆
<script>
var obj1 = {
a: 1,
b: 2,
c: [33, 44, {
m: 55,
n: 66,
p: [77, 88]
}]
};
// 深克隆
function deepClone(o) {
let result;
if (Array.isArray(o)) {
// 要判断o是对象还是数组
//数组
result = [];
for (let i = 0; i < o.length; i++) {
result.push(deepClone(o[i]));
}
} else if (typeof o == "object") {
// 对象
result = {};
for (let k in o) {
result[k] = deepClone(o[k]);
}
} else {
// 基本类型值
result = o;
}
return result;
}
let 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 上下文对象this
我们先来看一句话
这是非常好的习惯,值得表扬 (这字我们一脸懵逼 不知道指代什么,需要有前言后语)
比如
垃圾分类,这是非常好的习惯,值得表扬
随手关灯,这是非常好的习惯,值得表扬
遛狗栓绳,这是非常好的习惯,值得表扬
课后复习,这是非常好的习惯,值得表扬
早睡早起,这是非常好的习惯,值得表扬
# 函数的上下文
函数中可以使用this关键字,它表示函数的上下文
与中文中“这”类似,函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断
举例
let yunmu = {
nickname: "云牧",
age: 12,
sayHello: function() {
console.log("我是" + this.nickname + ",我" + this.age + "岁了");
}
}
//对象打点调用这个函数
yunmu.sayHello(); //此时的this指的是yunmu对象本身
//再看这个
let say = yunmu.sayHello; //将函数提出来 单独存储为变量
say(); //直接圆括号调用这个函数而不是对象打点调用了
2
3
4
5
6
7
8
9
10
11
12
13
14
15
调用方式不同也导致输出结果不一样
得出一个结论
# 函数的上下文由调用方式决定
同一个函数,用不同的形式调用它,则函数的上下文不同
情形1:对象打点调用函数,函数中的this指代这个打点的对象
yunmu.sayHello();
情形2:圆括号直接调用函数,函数中的this指代window对象
let say = yunmu.sayHello;
say();
2
题目
let obj = {
a: 1,
b: 2,
fn: function(){
console.log(this.a + this.b);
}
}
//这个函数的this指代什么?
//不知道 因为它的上下文只有当被调用上下文才确定
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let obj = {
a: 1,
b: 2,
fn: function(){
console.log(this.a + this.b);
console.log(this === obj);
}
}
obj.fn(); //3 true
var a = 10;
var b = 2;
let newFn = obj.fn;
newFn(); // 12 false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
函数的上下文(this关键字)由调用函数的方式决定,function是“运行时上下文”策略
函数如果不调用,则不能确定函数的上下文
# 规则1
规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
对象.方法();
# 题目1
<script>
function add(){
console.log(this.a + this.b);
}
let obj = {
a: 66,
b: 33,
fn:add
};
obj.fn(); //构成对象.方法()的形式,适用规则1
</script>
2
3
4
5
6
7
8
9
10
11
12
13
运行结果是
99
# 题目2
<script>
let obj1 = {
a: 1,
b: 2,
fn: function(){
console.log(this.a + this.b);
}
}
let obj2 = {
a: 3,
b: 4,
fn: obj1.fn
}
obj2.fn() //构成对象.方法()的形式,适用规则1
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
运行结果
7
# 题目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(); //构成对象.方法()的形式,适用规则1
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果
77
# 题目4
<script>
function fn(){
console.log(this.a + this.b);
}
let obj = {
a: 1,
b: 2,
c: [{
a: 3,
b: 4,
c : fn
}]
}
var a = 5;
obj.c[0].c(); //构成对象.方法()的形式,适用规则1
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
运行结果
7
# 规则2
规则2:圆括号直接调用函数,则函数的上下文是window对象
函数();
# 题目1
<script>
//圆括号直接调用 函数上下文就是window对象
let obj = {
a: 1,
b: 2,
fn: function(){
console.log(this.a + this.b);
}
}
var a = 3;
var b = 4;
let fn = obj.fn; //构成函数()的形式,适用规则2
fn();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
运行结果
7
# 题目2
<script>
function fun() {
return this.a + this.b;
}
var a = 1;
var b = 2;
let obj = {
a: 3,
b: fun(), //构成函数()的形式,适用规则2
fun: fun,
};
let result = obj.fun(); //构成对象.方法()的形式,适用规则1
console.log(result);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
运行结果
6
# 规则3
规则3︰数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
数据[下标]();
# 题目1
<script>
let arr = [
"A",
"B",
"C",
function () {
console.log(this[0]);
},
];
arr[3](); //适用规则3
</script>
2
3
4
5
6
7
8
9
10
11
12
运行结果
A
# 题目2
<script>
function fn() {
arguments[3](); //适用规则3
}
fn("A", "B", "C", function () {
console.log(this[1]);
});
</script>
2
3
4
5
6
7
8
运行结果
B
# 规则4
规则4:IIFE中的函数,上下文是window对象
(function(){})();
# 题目1
<script>
var a = 1;
var obj = {
a: 2,
fun: (function () {
var a = this.a;
return function () {
console.lcssog(a + this.a);
};
})() //适用规则4
};
obj.fun();//适用规则1
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
运行结果
3
# 规则5
规则5:定时器、延时器调用函数,上下文是window对象
setInterval(函数,时间);
setTimeout(函数,时间);
2
# 题目1
<script>
let obj = {
a: 1,
b: 2,
fun: function(){
console.log(this.a + this.b);
}
}
var a = 3;
var b = 4;
setTimeout(obj.fun , 2000) //适用规则5
//注意这个不是由延时器或者定时器调用的
setTimeout(function(){
obj.fun(); //适用规则1
},2000)
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果
7
3
2
# 规则6
规则6:事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function () {}
# 小案例1
请实现效果:点击哪个盒子,哪个盒子就变绿,要求使用同一个事件处理函数实现
<style>
div {
float: left;
width: 200px;
height: 200px;
border: 2px solid black;
margin: 0 10px;
}
</style>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<script>
let box1 = document.getElementById("box1");
let box2 = document.getElementById("box2");
let box3 = document.getElementById("box3");
box1.onclick = changeColor;
box2.onclick = changeColor;
box3.onclick = changeColor;
function changeColor() {
this.style.backgroundColor = "green";
}
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 小案例2
请实现效果:点击哪个盒子,哪个盒子在2000毫秒后就变绿,要求使用同一个事件处理函数实现
<style>
div {
float: left;
width: 200px;
height: 200px;
border: 2px solid black;
margin: 0 10px;
}
</style>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<script>
let box1 = document.getElementById("box1");
let box2 = document.getElementById("box2");
let box3 = document.getElementById("box3");
box1.onclick = changeColor;
box2.onclick = changeColor;
box3.onclick = changeColor;
function changeColor() {
// 备份上下文 有人喜欢that _this
let self = this;
setTimeout(function () {
self.style.backgroundColor = "green";
}, 2000);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# call和apply和bind
call和apply能间接的调用函数并且指定函数的上下文
函数.call(上下文);
函数.apply(上下文);
2
<script>
function sum(){
alert(this.chinese + this.math + this.english)
}
let xiaoming = {
chinese: 80,
math: 90,
english: 90,
sum:sum
};
xiaoming.sum();
sum();
sum.call(xiaoming);
sum.apply(xiaoming);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# call和apply的区别
<script>
function sum(b1, b2) {
alert(this.chinese + this.math + this.english + b1 + b2);
}
let xiaoming = {
chinese: 80,
math: 90,
english: 90,
sum: sum,
};
sum.call(xiaoming, 5, 3); //call要用逗号罗列参数 错位传参
sum.apply(xiaoming, [5, 3]); //apply要把参数写到数组中
//所有实参放在一个数组 数组第一个数对应第一个形参
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 到底使用call还是apply?
<script>
function fn1() {
fn2.apply(this, arguments); //this值是fn1的上下文window
}
function fn2(a, b) {
console.log(a + b);
}
fn1(11, 22);
</script>
2
3
4
5
6
7
8
9
10
11
事件函数可以改变this指向吗?
let xiyan = {name:"夕颜" , age:14}
function fn(){
console.log("fn执行了");
console.log(this);
}
// fn.call()函数会自执行 返回undefined
document.onclick = fn.call(xiyan);
2
3
4
5
6
7
8
现在我们既要改变函数this指向
不需要立即执行函数
let xiyan = {name:"夕颜" , age:14}
function fn(){
console.log("fn执行了");
console.log(this);
}
document.onclick = fn.bind(xiyan);
//内部实现原理类似于
document.onclick = function(){
fn.call(xiyan)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 构造函数与类
let tom = {
name: "汤姆",
age: 18,
company: "潭州教育",
sayName: function () {
console.log(`我叫${this.name},今年${this.age},来自${this.company}`);
},
};
let jerry = {
name: "杰瑞",
age: 20,
company: "潭州教育",
sayName: function () {
console.log(`我叫${this.name},今年${this.age},来自${this.company}`);
},
};
let zeek = {
name: "扎克",
age: 30,
company: "潭州教育",
sayName: function () {
console.log(`我叫${this.name},今年${this.age},来自${this.company}`);
},
};
tom.sayName();
jerry.sayName();
zeek.sayName();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
此时一个一个定义有点麻烦,我们可以封装一下 :
//工厂模式
function createTeacher(name, age) {
//原料
let obj = {};
//加工
obj.name = name;
obj.age = age;
obj.company = "潭州教育";
obj.sayName = function () {
console.log(`我叫${this.name},今年${this.age},来自${this.company}`);
};
//产出
return obj;
}
let tom = createTeacher("tom", 18);
let jerry = createTeacher("jerry", 18);
let zeek = createTeacher("zeek", 18);
tom.sayName();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
现在,我们学习一种新的函数调用方式:
new.函数()
你可能知道new操作符和“面向对象”息息相关,但是现在,我们先不探讨它的“面向对象”意义,而是先
把用new调用函数的执行步骤和它上下文弄清楚
JS规定,使用new操作符调用函数会进行“四步走”:
1)函数体内会自动创建出一个空白对象
2)函数的上下文 (this)会指向这个对象
3)函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有return语句
例子
<script>
function fn(){
this.a = 3;
this.b = 5;
}
let obj = new fn();
console.log(obj);
</script>
2
3
4
5
6
7
8
9
相当于
<script>
function fn(){
// 创建{} ①
// this => {} ②
this.a = 3;
this.b = 5;//执行语句 ③
//return this ④
}
let obj = new fn(); //返回的上下文对象被变量obj接收
console.log(obj);
</script>
2
3
4
5
6
7
8
9
10
11
12
例子
<script>
function fn(){
this.a = 3;
this.b = 5;
let m = 60;
if(this.a > this.b){
this.c = m+10;
}else{
this.c = m+20;
}
}
let obj = new fn();
console.log(obj);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 构造函数
# 什么是构造函数
我们将之前书写的函数进行一下小改进:
<script>
function People(name, age, sex) { //接收三个参数
this.name = name;
this.age = age; //this绑定同名属性
this.sex = sex;
}
let yunmu = new People("云牧", 18,"男"); //传入三个参数
let daiyu = new People("黛玉", 14,"女");
let xiaoming = new People("小明", 20, "男");
console.log(yunmu);
console.log(daiyu);
console.log(xiaoming);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果如下图
用new调用一个函数,这个函数就被称为**“构造函数”**,任何函数都可以是构造函数,只需要用new调用它
顾名思义,构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写
请判断对错:
一个函数名称首字母大写了,它就是构造函数
一定要记住:一个函数是不是构造函数,要看它是否用new调用,而至于名称首字母大写,完全是开发
者的习惯约定
如果不用new调用构造函数
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People("云牧", 18,"男");
People("黛玉", 16,"女");
// 此时这个函数单独执行没有太大意义(相当于给window加属性),只有在new执行的时候才能发挥其作用
2
3
4
5
6
7
8
9
# 尝试为对象添加方法
<script>
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function(){
console.log(`我是${this.name},今年${this.age}啦`);
}
this.sleep = function(){
console.log(this.name + "开始睡觉觉了zzzz");
}
}
let yunmu = new People("云牧", 18,"男");
let daiyu = new People("黛玉", 14, "女");
let xiaoming = new People("小明", 20, "男");
yunmu.sayHello();
daiyu.sayHello();
xiaoming.sayHello();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果如下
# 类和实例
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function () {
console.log(`我是${this.name},今年${this.age}啦`);
};
this.sleep = function () {
console.log(this.name + "开始睡觉觉了zzzz");
};
}
let yunmu = new Person("云牧", 18,"男");
let daiyu = new People("黛玉", 14, "女");
let xiaoming = new People("小明", 20, "男");
//我们通过new调用Person函数创建的对象都具有name,age,sex,sayHello()
//我可以将构造函数Person认为是类。yunmu, xiyan , xioaoming是实例
//通过new构造函数得到对象的过程,称之为实例化
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
类好比是蓝图
如同“蓝图”一样,类只描述对象会拥有哪些属性和方法,但是并不具体指明属性的值
实例是具体的对象
比如
“狗”是类,“史努比”是实例、“小白”是实例
Java、C++等是“面向对象”(object-oriented)语言
Javascript是“基于对象”(object-based)语言
Javascript中的构造函数可以类比于OO语言中的“类,写法的确类似,但和真正OO语言还是有本质不同,在后面还将看见和其他OO语言完全不同的、特有的原型特性
# 原型和原型链
任何函数都有prototype属性,prototype是英语“原型”的意思
prototype属性值是个对象,它默认拥有constructor属性指回函数
function fn(a , b){
return a + b;
}
console.log(fn.prototype);
console.log(typeof fn.prototype);
console.log(fn.prototype.constructor === fn);
2
3
4
5
6
7
普通函数来说的prototype属性没有任何用处,而构造函数的prototype属性非常有用
# 构造函数的prototype属性是它的实例的原型
function People(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 实例化
let xiaoming = new People("小明", 18, "男");
// 测试三角关系是否存在
console.log(People.prototype === xiaoming.__proto__); //true
2
3
4
5
6
7
8
9
# 原型链查找
JavaScript规定:实例可以打点访问它的原型的属性和方法,这被称为“原型链查找”
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//往原型上添加nationality属性
People.prototype.nationality = "美国";//在构造函数的prototype上添加company属性
let xiaoming = new People("小明", 18, "男");
console.log(xiaoming.nationality); //实例可以打点访问原型的属性和方法
2
3
4
5
6
7
8
9
10
11
12
遮蔽效应
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.nationality = "美国";
let xiaoming = new People("小明", 18, "男");
console.log(xiaoming.nationality); //美国
let yunmu = new People("云牧", 18, "男");
yunmu.nationality = "中国";
console.log(yunmu.nationality); //中国
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# hasOwnProperty
hasOwnProperty方法可以检查对象是否真正“自己拥有”某属性或者方法
<script>
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.nationality = "美国";
let xiaoming = new People("小明", 18, "男");
console.log(xiaoming.hasOwnProperty("name")); // true
console.log(xiaoming.hasOwnProperty("age")); // true
console.log(xiaoming.hasOwnProperty("sex")); // true
console.log(xiaoming.hasOwnProperty("nationality")); //false
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# in
in运算符只能检查某个属性或方法是否可以被对象访问
不能检查是否是自己的属性或方法
<script>
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.nationality = "美国";
let xiaoming = new People("小明", 18, "男");
console.log("name" in xiaoming); // true
console.log("age" in xiaoming); // true
console.log("sex" in xiaoming); // true
console.log("nationality" in xiaoming); //true
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 在prototype上添加方法
在前面,我们把方法都是直接添加到实例身上
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function () {
console.log(`我是${this.name},今年${this.age}啦`);
};
this.sleep = function () {
console.log(this.name + "开始睡觉觉了zzzz");
};
}
let yunmu = new People("云牧", 18, "男");
let daiyu = new People("黛玉", 14, "女");
let xiaoming = new People("小明", 20, "男");
//这个new了三个对象 三个对象分别有name,age,sex属性 和 sayHello,sleep方法
//所有每 new一个对象会创建一个 不同的函数
console.log(yunmu.sayHello == daiyu.sayHello); //false
console.log(yunmu.sleep === daiyu.sleep); //false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
方法要写到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.sleep = function () {
console.log(this.name + "开始睡觉觉了zzzz");
};
People.prototype.growup = function () {
this.age++;
};
let yunmu = new People("云牧", 18, "男");
let daiyu = new People("黛玉", 16, "女");
yunmu.growup();
yunmu.growup();
yunmu.sayHello();
daiyu.growup();
daiyu.sayHello();
yunmu.sleep();
daiyu.sleep();
console.log(yunmu.sayHello == daiyu.sayHello);
console.log(yunmu.sleep === daiyu.sleep);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
运行结果如下图
# 原型链的终点
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
let xiaoming = new People("小明", 18, "男");
console.log(People.prototype.__proto__ == Object.prototype); //true
console.log(People.__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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 关于数组的原型链
<script>
let arr = [12, 34, 56];
console.log(arr.__proto__ === Array.prototype); //true
console.log(arr.__proto__.__proto__ === Object.prototype); //true
console.log(Array.prototype.hasOwnProperty("pop")); //true
console.log(Array.prototype.hasOwnProperty("sort")); //true
</script>
2
3
4
5
6
7
8
9
var a =其实是var a = new Object()的语法糖
var a =[]其实是var a = new Array()的语法糖
function Foo(){..}其实是var Foo = new Function(...)
使用instanceof 判断一个函数是否是一个变量的构造函数
判断一个变量是否为“数组 变量 instanceof Array
# 继承
先来看两幅图
两个无关的类
People类和Student类的关系
People类拥有的属性和方法Student类都有,Student类还扩展了一些属性和方法
Student“是一种”People,两类之间是“**is a kind of"**关系
这就是继承关系:Student类继承自People类
继承
继承描述了两个类之间的“is a kind of”关系,比如学生“是一种”人,所以人类和学生类之间就构成继承关系
People是“父类”(或“超类”、“基类”) ; Student是“子类”(或“派生类”)
子类丰富了父类,让类描述得更具体、更细化
更多的继承关系举例
# 1.通过原型链实现继承
实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法
使用JavaScript特有的原型链特性来实现继承,是普遍的做法
<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 + "开始睡觉觉了zzzz");
};
//子类 学生类
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.scholl},我${this.age}了`);
}
// 实例化
let hanmeimei = new Student("韩梅梅" , 12, "女", "中心小学" , 12110);
hanmeimei.study();
hanmeimei.exam();
hanmeimei.sayHello();
hanmeimei.sleep();
let yunmu = new People("云牧", 18, "男");
yunmu.sayHello();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
运行结果如下
# 2. 构造函数继承
方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函
数复用,每个子类都有父类实例函数的副本,影响性能
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
// 核心语句
People.call(this,name);
// 这样就会在新parent对象上执行Person构造函数中定义的所有对象初始化代码
this.xuehao = xuehao;
}
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
let xiaohong = new Student("小红",1001);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3. 组合继承
就是将原型链继承和构造函数继承组合在一起;继承两个优点
通过调用父类构造,继承父类的属性并保留传参的优点,
然后再通过将父类实例作为子类原型,实现函数复用
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
People.call(this,name);
this.xuehao = xuehao;
}
//核心语句,继承的实现全靠这条语句了:
Student.prototype = new People('大明');
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
let xiaohong = new Student("小红",1001);
xiaohong.sayHello();
//调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4. 寄生组合继承
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例
方法/属性,避免的组合继承的缺点
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
People.call(this,name);
this.xuehao = xuehao;
}
//核心语句,继承的实现全靠这条语句了:
function Fn(){}
Fn.prototype = People.prototype
Student.prototype = new Fn();
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
let xiaohong = new Student("小红",1001);
xiaohong.sayHello();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5. 圣杯模式
function inherit(People,Student){
function Fn(){}
Fn.prototype = People.prototype
Student.prototype = new Fn();
Student.prototype.constructor = Student;
Student.prototype.parent = People;
}
inherit(People,Student )
2
3
4
5
6
7
8
# 面向对象案例拓展
面向对象的本质:定义不同的类,让类的实例工作
面向对象的优点:程序编写更清晰、代码结构更严密、使代码更健壮更利于维护
面向对象经常用到的场合:需要封装和复用性的场合(组件思维)
# 案例:红绿灯
页面上做一个红绿灯,点击红灯就变黄,点击黄灯就变绿点击绿灯就变回红灯
如何编写100个红绿灯?
使用面向对象编程,就能以**“组件化”**的思维解决大量红绿灯互相冲突的问题
面向对象编程,最重要的就是编写类
编写个TrafficLight类
请思考,红绿灯TrafficLight类有哪些属性和方法呢
属性**:自己的当前颜色color、自己的DOM元素dom**
方法:初始化init()、切换颜色changecolor()、绑定事件bindEvent()
<!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: 150px;
}
</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的实例
let 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`;
}
// 得到盒子
let box = document.getElementById("box");
// 实例化100个
let n = 100;
while(n--){
new TrafficLight()
}
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 案例:炫彩小球案例
ball球类的属性
//属性名 意义
x 圆心坐标x
y 圆心坐标y
r 圆半径
opacity 透明度
color 颜色
dom DOM元素
2
3
4
5
6
7
ball球类的方法
//属性名 意义
init 初始化
update 更新
2
3
如何实现多个小球动画
把每个小球实例都放到同一个数组中
[{小球实例},{小球实例},{小球实例}]
//只需要使用1个定时器,在每一帧遍历每个小球,调用它们的update方法
2
3
<!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 {
overflow: hidden;
background-color: black;
}
.ball {
position: absolute;
border-radius: 50%;
animation: changColor 1s ease infinite;
background-color: orange;
}
@keyframes changColor {
from {
filter: hue-rotate(0deg);
}
to {
filter: hue-rotate(360deg);
}
}
</style>
</head>
<body>
<script>
function Ball(x, y) {
// 属性x、y表示的是圆心的坐标
this.x = x;
this.y = y;
//小球的背景颜色
//this.color = "orange";
//this.color = colorArr[parseInt(Math.random() * colorArr.length)];
// 半径属性
this.r = 20;
//透明度
this.opacity = 1;
// 这个小球的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.classList.add("ball");
this.dom.style.width = this.r * 2 + "px";
this.dom.style.height = this.r * 2 + "px";
this.dom.style.top = this.y - this.r + "px";
this.dom.style.left = this.x - 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.15;
// 透明度改变
this.opacity -= 0.01;
this.dom.style.width = this.r * 2 + "px";
this.dom.style.height = this.r * 2 + "px";
this.dom.style.top = this.y - this.r + "px";
this.dom.style.left = this.x - this.r + "px";
this.dom.style.opacity = this.opacity;
// 当透明度小于0的时候,就需要从数组中删除自己,DOM元素也要删掉自己
if (this.opacity < 0) {
// 从数组中删除自己
for (let i = 0; i < ballArr.length; i++) {
if (ballArr[i] === this) {
ballArr.splice(i, 1);
}
}
// 还要删除自己的dom
document.body.removeChild(this.dom);
}
};
// 把所有的小球实例都放到一个数组中
let ballArr = [];
// 初始颜色数组
var colorArr = [
"#66CCCC",
"#CCFF66",
"#FF99CC",
"#FF6666",
"#CC3399",
"#FF6600",
];
// 定时器,负责更新所有的小球实例
setInterval(function () {
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].update();
}
}, 20);
// 鼠标指针的监听
document.onmousemove = function (e) {
//new Ball(200, 200);
// 得到鼠标指针的位置
let x = e.clientX;
let y = e.clientY;
new Ball(x, y);
};
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
← 15-bom 17-内置数学和时间对象 →
























