面向对象
一、认识对象
在 JavaScript 中,对象是一种用于表示数据和行为的数据结构。它可以用来表示各种数据类型,如数字、字符串、数组、函数等。一个对象由若干属性组成,这些属性可以是任何类型的值。
什么是对象
对象的定义:对象的定义是通过一个花括号内的键值对来表示的。键是属性的名称,值可以是任何类型的值。例如:
var person = { name: 'Tom', age: 30, // 不符合js标识符命名规范的属性需要用引号包裹 'nick-name': 'TT', // 对象中最后一个键值对后面可以不写逗号 address: { street: '123 Main St', city: 'Anytown', state: 'CA' } }
在上面的例子中,person 是一个对象,它具有三个属性:name、age 和 address。这些属性分别用加引号的键表示。
对象访问:对象的属性可以使用点符号和方括号来访问。例如:
console.log(person.name); // 如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问 console.log(person['nick-name']); // 如果属性名以变量形式存储,则必须使用方括号形式 var key = 'age' console.log(person[key])
对象属性值修改:直接使用赋值运算符重新对某属性赋值即可更改属性。例如:
person.name = 'Amy' console.log(person.name)
对象属性值创建: 如果对象本身没有某个属性值,则用点语法赋值时,这个属 性会被创建出来。例如:
person.sex = '男' console.log(person)
对象属性值删除:可以使用 delete 运算符来删除对象的属性。例如:
delete person.address.city; console.log(person)
对象的方法
对象的方法定义:如果某个属性值是函数、则这个属性就是对象的方法。例如:
var person = { name: 'Tom', age: 30, say: function() { console.log('大家好,我是Tom') } }
对象的方法调用:通过点语法可以调用对象方法
person.say()
方法和函数的区别:方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用
对象的遍历
- 通过
for...in...
循环,例如for (var key in person) { console.log('属性:' + key + ';值为:' + person[key]) }
- 通过
Object.keys
获取所有键名数组,再通过键名进行遍历var keys = Object.keys(person) for (var i = 0; i < keys.length; i++) { console.log('属性:' + keys[i] + ';值为:' + person[keys[i]]) }
- 通过
对象的深浅克隆
浅克隆
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); // obj2的c属性这个数组也会被增加77数组 console.log(obj2); // true,true就证明了数组是同一个对象 console.log(obj1.c == obj2.c);
深克隆
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); // false console.log(obj1.c == obj2.c); obj1.c.push(99); // obj2不变的,因为没有“藕断丝连”的现象 console.log(obj2); obj1.c[2].p.push(999); // obj2不变的,因为没有“藕断丝连”的现象 console.log(obj2);
二、函数上下文
1.什么是函数上下文
在了解函数上下文之前,我们先看一句话:这首歌很好听。
我们单独看到这句话的时候会很懵逼,到底那首歌很好听呢,在这句话中这
这个字给我们的感觉前面是有其他话的,比如:我在听青花瓷,这首歌很好听。在这句话中,这
这个字就是这句话的上下文,它表示的意思要通过这句话的前言后语来判断。
那么在函数中我们可以使用this
关键字来表示函数的上下文,函数中的this
具体指代什么,必须通过调用函数时的“前言后语”来判断。
var person = {
name: 'Tom',
age: 30,
say: function() {
console.log('大家好,我是' + this.name)
}
}
person.say()
在上面的代码中this
表示了person
这个对象,我们再来看下另一段代码:
var person = {
name: 'Tom',
age: 30,
say: function() {
console.log('大家好,我是' + this.name)
}
}
var newSay = person.say
newSay()
在这段代码中函数调用形式变了,this
不在是person
对象,而是window
对象,由此我们可以得出一个结论:函数的上下文由调用方式决定
var person = {
a: 20,
b: 30,
cal: function() {
// 判断下this的上下文
console.log(this.a + this.b)
}
}
重点:函数的上下文只有在函数被调用时才能被确定
2.如何判断函数上下文
规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
面试题1function fn() { console.log(this.a + this.b); } var obj = { a: 66, b: 33, fn: fn }; obj.fn();
面试题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();
面试题3
function outer() { var a = 11; var b = 22; return { a: 33, b: 44, fn: function () { console.log(this.a + this.b); } }; } outer().fn();
面试题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();
规则2:圆括号直接调用函数,则函数的上下文是window对象
面试题1var obj1 = { a: 1, b: 2, fn: function () { console.log(this.a + this.b); } }; var a = 3; var b = 4; var fn = obj1.fn; fn();
面试题2
function fun() { return this.a + this.b; } var a = 1; var b = 2; var obj = { a: 3, b: fun(), fun: fun }; var result = obj.fun(); console.log(result);
规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象,arguments
对象是最常见的类数组对象,它是函数的实参列 表
面试题1var arr = ['A' ,'B' ,'C', function () { console.log(this[0]) }]; arr[3]()
面试题2
function fun() { arguments[3](); } fun('A', 'B', 'C', function () { console.log(this[1]); });
规则4:定时器、延时器调用函数,上下文是window对象
面试题1var obj = { a: 1, b: 2, fun: function () { console.log(this.a + this.b); } } var a = 3; var b = 4; setTimeout(obj.fun, 2000);
面试题2
var obj = { a: 1, b: 2, fun: function () { console.log(this.a + this.b); } } var a = 3; var b = 4; setTimeout(function() { obj.fun(); }, 2000);
规则5:IIFE(立即执行函数)中的函数,上下文是window对象
(function() { })();
面试题1
var a = 1; var obj = { a: 2, fun: (function () { var a = this.a; return function () { console.log(a + this.a); } })() }; obj.fun();
规则6:事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function () { };
三、构造函数和类
构造函数是一种函数调用方式
1、如何创建一个构造函数
我们可以使用new
操作符来创建构造函数:new 函数()
,构造函数执行步骤:
- 执行流程1:函数体内会自动创建出一个空白对象
- 执行流程2:函数的上下文(this)会指向这个对象
- 执行流程3:函数体内的语句会执行
- 执行流程4:函数会自动返回上下文对象,即使函数没有return语句
我们来看下下面的代码:
function fun() {
this.a = 3;
this.b = 5;
}
var obj = new fun()
console.log(obj)
2、什么是构造函数
- 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
- 构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
- 构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写
function People(name, age, sex) { this.name = name this.age = age this.sex = sex } var xiaoming= new People('小明', 12, '男'); var xiaohong= new People('小红', 10, '女'); var xiaogang= new People('小刚', 13, '男');
思考上面代码,如果new调用构造函数,会是怎样的?
3、类与实例
- 类只描述对象会拥有哪些属性和方法, 但是并不具体指明属性的值
- 实例是具体的对象
举个通俗易懂的例子,“狗”是类,“哈士奇”是实例、“萨摩耶”也是实例
四、原型和原型链
任何函数都有prototype(原型)属性,prototype属性值是个对象,默认拥有constructor(构造器)属性指回函数本身
function sum(a, b) {
return a + b
}
console.log(sum.prototype)
console.log(typeof sum.prototype)
console.log(sum.prototype.constructor === sum)
- 普通函数来说的prototype属性没有任何用处,而构造函数 的prototype属性非常有用
- 构造函数的prototype属性是它的实例的原型
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 实例化
var xiaoming = new People('小明', 12, '男');
// 测试三角关系是否存在
console.log(xiaoming.__proto__ === People.prototype);
1、原型链查找
实例打点访问它的原型的属性和方法
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
console.log(xiaoming.national);
hasOwnProperty方法可以检查对象是否真正“自己拥有” 某属性或者方法
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('age'); // true
xiaoming.hasOwnProperty('sex'); // true
xiaoming.hasOwnProperty('nationality');
in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.national = '中国'
var xiaoming = new People('小明', 12, '男');
'name' in xiaoming // true
'age' in xiaoming // true
'sex' in xiaoming // true
'nationality' in xiaoming // true
2、原型链的终点
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
五、继承
1、定义
- 继承描述了两个类之间的”is a kind of“关系
- ”父类“:超类、基类;”子类“:派生类
- 子类丰富了父类,让类描述的更具体、更细化
2、实现继承
- 子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法
- 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
// 父类,人类
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, '女', '慕课小学', 100556);
hanmeimei.study();
hanmeimei.sayHello();
hanmeimei.sleep();
var laozhang = new People('老张', 66, '男');
laozhang.sayHello();
六、面向对象
- 定义:定义不同的类、上类的实例工作
- 优点:程序编写更清晰,代码结构更严密,使代码更健壮更便于维护
- 使用场合:需要封装和复用性场合
案例:页面上做100个红绿灯,点击红灯就变黄,点击黄灯就变绿,点击绿灯就变回红灯,图片资源下载地址
<div id="box"></div>
#box img{
width: 80px;
}
// 定义红绿灯类
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();
}
七、JS内置对象
Math
对象
// 最大值
console.log(Math.max(44, 55, 33, 22));
// 最小值
console.log(Math.min(44, 55, 33, 22));
// 四舍五入
console.log(Math.round(3.49));
console.log(Math.round(3.51));
Date
对象
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());