JavaScript面向对象
JS 面向对象
对象是 “键值对”的集合,表示属性和值的映射关系
var xiaoming = {
name: '小明',
age: 12,
sex: '男',
hobbies: ['足球','编程']
};
-
如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹
-
可以使用 “点语法” 访问对象中指定键的值
-
如果属性名以变量形式存储,则必须使用方括号形式
var obj = {
a: 1,
b: 2,
c: 3
};
var key = 'b';
console.log(obj.[key]); // 2
- 如果要删除某个对象的属性,需要使用delete操作符
对象的方法
- 如果某个属性值是函数,则它也被称为对象的 “方法”
var xiaoming = {
sayHello: function() {
console.log('你好,我是小明');
}
}
使用 “点语法” 可以调用对象的方法
xiaoming.sayHello();
方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用
对象的遍历
for...in...循环:可以遍历对象的每个键
for (var k in obj) {
// k是循环变量,它会一次称为对象的每一个键
// obj是要遍历的对象
console.log('对象obj的属性' + k + '的值是' + obj[k]);
}
如果一个对象要使用变量当作它的键名的话,一定要写方括号
对象的深浅克隆
举例 | 当 var a = b变量传值时 | 当用 == 比较时 | |
---|---|---|---|
基本类型值 | 数字、字符串、布尔、undefined、null | 内存中产生新副本 | 比较值是否相等 |
引用类型值 | 对象、数组等 | 内存中不产生新的副本,而是让新变量指向同一个对象 | 比较内存地址是否相同,即比较是否为同一对象 |
对象时引用类型值,意味着:
- 不能用 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];
}
对象的深克隆 (克隆对象的全貌)**
不论对象的属性值是否又是引用类型值,都能将它们实现克隆
对象的深克隆需要使用递归(函数内部调用自己)
面试经常考察深克隆算法
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
</body>
<script type="text/javascript">
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);
obj1.c[2].p.push(99);
</script>
</html>
函数的上下文
- 函数中可以使用 this 关键字,它表示函数的上下文
- 函数中的 this 具体只带什么必须通过调用函数时的“前言后语”来判断
var xiaoming = {
nickname:'小明',
age:12,
sayHello:function() {
console.log('我是' + this.nickname + ',我' + this.age + '岁了');
}
};
var sayHello = xiaoming.sayHello();
sayHello();
函数的上下文由调用方式决定(函数只有被调用,它的上下文才能被确定)
- 同一个函数,用不同的形式调用它,则函数的上下文不同
情形1:对象打点调用函数,函数中的this指代 这个打点的对象
xiaoming.sayHello();
情形2:圆括号直接调用函数,函数中的 this 指代 window对象
var sayHello = xiaoming.sayHello;sayHello();
function 函数本身
function() 函数的执行结果
上下文规则
函数的上下文由 调用函数的方式 决定
规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
对象.方法()
规则2:圆括号直接调用函数,则函数的上下文是 window 对象
函数()
规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
数组[下标]()
规则4:IIFE中的函数,上下文是 window 对象
规则5:定时器、延时器调用函数,上下文是 window 对象
规则6:事件处理函数的上下文是绑定事件的DOM元素
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
// 此处上下文是 obj2
规则1 - 题
function outer() {
var a = 11;
var b = 22;
return {
a: 33,
b: 44,
fn: function () {
console.log(this.a + this.b);
}
};
}
outer().fn(); // 77
规则2:圆括号直接调用函数,则函数的上下文是 window 对象
函数()
规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
数组[下标]()
类数组对象
-
所有键名为自然数序列(从0开始),且有length属性的对象
-
argumengs 对象是最常见的类数组对象,它是函数的实参列表
规则4:IIFE中的函数,上下文是 window 对象
规则5:定时器、延时器调用函数,上下文是 window 对象
规则6:事件处理函数的上下文是绑定事件的DOM元素
call 和 apply 能指定函数的上下文
函数.call(上下文);
函数.apply(上下文);
function sum() {
alert(this.c + this.m + this.e);
};
var xiaoming = {
c: 100,
m: 90,
e: 80
};
sum.call(xiaoming, 3, 5);
sum.apply(xiaoming, [3, 5]);
上下文规则总结
规则 | 上下文 |
---|---|
对象.函数() | 对象 |
函数() | window |
数组[下标]() |
数组 |
IIFE | window |
定时器 | window |
DOM事件处理函数 | 绑定DOM的元素 |
call 和 apply | 任意指定 |
用new调用函数 | 秘密创建出的对象 |
用 new 操作符调用函数
新的函数调用方式:new 函数()
(送对象)
- 使用new操作符调用函数会进行”四步走“
1.函数体内会自动创建出一个空白对象
2.函数的上下文(this)会绑定到这个对象身上
3.函数体内的语句会执行
4.函数会自动返回上下文对象,即使函数没有return语句
function fun() {
this.a = 3;
this.b = 5;
}
var obj = new fun();
console.log(obj); // {a: 3, b: 5}
构造函数
function People(name, age, sex) {
// 接收3个参数
this.name = name;
this.age = age;
this.sex = sex;
// this上绑定同名属性
}
var xiaoming = new People('小明',)
用 new 调用一个函数,这个函数就被称为”构造函数“,
任何函数都可以是构造函数,只需要用 new 调用它
构造函数必须用 new 关键字调用,构造函数命名时首字母要大写
构造函数用来”构造新对象“,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
构造函数中的 this 不是函数本身
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 + '开始睡觉zzz');
}
};
var xiaoming = new People('小明',12,'男');
var xiaohong = new People('小红',4,'男');
var xiaogang = new People('小刚',17,'男');
xiaohong.sayHello();
xiaogang.sleep();
类 和 实例
类是 ”蓝图“,只描述对象会拥有哪些属性和方法,但是不具体指明属性的值
实例化:类创造出实例的过程
prototype 和原型链查找
任何函数都有prototype属性
prototype属性值是个对象,它拥有constructor属性指回函数本身
普通函数的prototype属性没有任何用处,构造函数的prototype属性非常有用
构造函数的 prototype 属性是它的实例的原型
原型链查找
JS规定:实例可以打点访问它的原型的属性和方法,这被称为”原型链查找“
//往原型上添加nationality属性
实例可以打点访问原型的属性和方法
hasOwnProperty
- 可以检查对象是否真正”自己拥有“某属性或者方法
xiaoming.hasOwnProperty('name'); // true
in
- in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
在prototype上添加方法
之前,我们将方法写到了对象身上,把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费
方法要写到prototype上
function people(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.sayHello = function() {
console.log('我是' + this.name);
};
People.prototype.sleep = function() {
console.log(this.name + '开始睡觉,zzz');
};
原型链的终点
关于数组的原型链
继承
两个类之间 ”is a kind of“ 关系
具体到某一个东西 -- 实例
JS中如何实现继承
-
子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法
-
使用JS特有的原型链特性来实现继承,是普遍的做法
通过原型链实现继承
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,school,studentNumber) {
this.name = name;
this.age = age;
this.sex = sex;
this.school = school;
this.studentNumber = studentNumber;
};
// 关键语句,实现继承
Student.prototype = new People();
Student.prototype.stydy = function () {
console.log(this.name + '正在学习');
}
Student.prototype.exam = function() {
console.log(this.name + '正在考试,加油');
}
//实例化
var hanmeimei = new Student('韩梅梅',9,'女','慕课小学',100566);
hanmeimei.study();
hanmeimei.sayHello();
hanmeimei.sleep();
包装类
-
Number()、String()和Boolean() 分别是数字、字符串、布尔值的“包装类”
-
很多编程语言都有“包装类”的设计包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
-
Number()、String()和Boolean()的实例都是object类型
-
new出来的基本类型值可以正常参与运算
包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
Date对象
使用 new Date() 即可得到当前时间的日期对象,它是object类型值
new Date(2020,11,1);
//即可得到指定日期的日期对象
第二个参数表示月份,从0开始算,11表示12月