面向对象
面向对象
对象
对象 (object)是“键值对”的集合,表示属性和值的映射关系
var xiaoming = {
name: '小明',
age: 12,
sex: '男'
};
如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹
属性
可以用“点语法”访问对象中指定键的值
如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问
如果属性名以变量形式存储,则必须使用方括号形式
直接使用赋值运算符重新对某属性赋值即可更改属性
如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
如果要删除某个对象的属性,需要使用delete操作符
var obj = {
a: 1,
b: 2
}
delete obj.a;
对象的方法
如果某个属性值是函数,则它也被称为对象的“方法”
使用“点语法”可以调用对象的方法
方法也是函数,只不过方法是对象的“函数属性”,它需要用对象打点调用
对象的遍历
和遍历数组类似,对象也可以被遍历,遍历对象需要使用for...in...循环
使用for...in...循环可以遍历对象的每个键
for(var k in obj){
console.log('属性' + k + '的值时' + obj[k]);
}
this
函数中可以使用this关键字,它表示函数的上下文
与中文中“这”类似,函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断
var xiaoming = {
name : '小明',
age : 12,
sayHello: function (){
cosole.log('我是' + this.name + ',我' + this.age + '岁了');
}
};
xiaoming.sayHello(); //我是小明,我12岁了
var sayHello = xiaoming.sayHello;
sayHello(); //我是undefined,我undefined岁了
函数的上下文由调用方式决定
同一个函数,用不同的形式调用它,则函数的上下文不同
情形1: 对象打点调用函数,函数中的this指代这个打点的对象xiaoming.sayHello();
情形2: 圆括号直接调用函数,函数中的this指代window对象
var sayHello = xiaoming.sayHello;
sayHello();
函数的上下文(this关键字) 由调用函数的方式决定function是“运行时上下文”策略
函数如果不调用,则不能确定函数的上下文
规则1: 对象打点调用它的方法函数,则函数的上下文是这个打点的对象
对象.方法()
规则2: 圆括号直接调用函数,则函数的上下文是window对象
函数()
规则3:数组 (类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
数组[下标]()
什么是类数组对象:所有键名为自然数序列(从开始)且有length属性的对象
arguments对象是最常见的类数组对象,它是函数的实参列表
规则4: IIFE中的函数,上下文是window对象
(function(){
})();
规则5: 定时器、延时器调用函数,上下文是window对象
setInterval(函数,时间);
setTimeout(函数,时间);
规则6:事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function (){
};
规则7 指定上下文
call和apply能指定函数的上下文
函数.call(上下文);
函数.apply(上下文);
call和apply的区别在于参数传递
sum.call(xiaoming,5,3);
sum.apply(xiaoming,[5,3]);
new
现在,我们学习一种新的函数调用方式
new 函数()
JS规定,使用new操作符调用函数会进行“四步走”:
- 函数体内会自动创建出一个空白对象
- 函数的上下文 (this)会指向这个对象
- 函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有return语句
构造函数和“类”
Java、C++等是“面向对象( object-oriented)语言”
JavaScript是“基于对象( object-based ) 语言”
JavaScript中的构造函数可以类比于OO语言中的“类'写法的确类似,但和真正OO语言还是有本质不同,在后续课程还将看见JS和其他OO语言完全不同的、特有的原型特性
prototype和原型链查找
任何函数都有prototype属性,prototype是英语“原型的意思
prototype属性值是个对象,它默认拥有constructor属性指回函数
构造函数的prototype属性是它的实例的原型
用new构造出来的对象的属性__proto__就是prototype
JavaScript规定:实例可以打点访问它的原型的属性和方法,这被称为“原型链查找”
function People(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.nationality = '中国';
var xiaoming = new People('小明',12,'男');
console.log(xiaoming.nationality);
内存优化
把方法直接添加到实例身上的缺点: 每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费
解决办法: 将方法写到prototype上
原型链的终点就是Object.prototype
hasOwnProperty
hasOwnProperty方法可以检查对象是否真正“自己拥有某属性或者方法”
in
in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
继承
实现继承的关键在于:子类必须拥有父类的全部属性和方法同时子类还应该能定义自己特有的属性和方法
使用JavaScript特有的原型链特性来实现继承,是普遍的做法
在今后学习ES6时,将介绍新的实现继承的方法
通过原型链实现继承的问题
问题1:如果父类的属性中有引用类型值,则这个属性会被所有子类的实例共享
可题2:子类的构造函数中,往往需要重复定义很多超类定义过的属性。即,子类的构造函数写的不够优雅
借用构造函数
为了解决原型中包含引用类型值所带来问题和子类构造函数不优雅的问题,开发人员通常使用一种叫做“借助构造函数的技术”,也被称为“伪造对象”或“经典继承”
借用构造函数的思想非常简单: 在子类构造函数的内部调用超类的构造函数,但是要注意使用call()绑定上下文
组合继承
将借用原型链和借用构造函数的技术组合到一起,叫做组合继承,也叫作伪经典继承
组合继承是JavaScript中最常用的继承模式
组合继承最大的问题就是无论什么情况下,都会调用两次超类的构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数的内部
原型式继承
IE9+开始支持0bject.create()方法,可以根据指定的对象为原型创建出新对象
var obj1 = {
a: 1,
b: 2,
c: 3
};
var obj2 = Object.create(obj1,{
d: {
value: 99
},
a: {
value: 11
}
});
console.log(obj2.__proto__ == obj1); //true
在没有必要“兴师动众”地创建构造函数,而只是想让新对象与现有对象“类似”的情况下,使用object.create()即可胜任,称为原型式继承
如何在低版本浏览器中实现object.create()呢?
// 道格拉斯·可罗克福德
// 函数的功能就是以0为原型,创建新对象
function object(o){
//创建一个临时构造函数
function F() {}
//让这个临时构造函数的prototype指向o,这样依赖它new出来的对象,__proto__ 指向了o
F.prototype = o;
retrun new F();
}
寄生式继承
寄生式继承: 编写一个函数,它接收一个参数,返回以o为原型的新对象p,同时给p上添加预置的新方法
var o1 = {
name: 'xiaoming',
age: 12,
sex: 'nan'
};
//寄生式继承
function f(o) {
//以o为原型创建除新对象
var p = Object.create(o);
//补充方法
p.sayHello = function() {
//todo
}
p.sleep = function() {
//todo
}
return p;
}
var p1 = f(o1);
p1.sayHello();
寄生式继承就是编写一个函数,它可以“增强对象”,只要创建把对象传入这个函数,这个函数将以此对象为“基础”出新对象,并为新对象赋予新的预置方法
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,即方法没有写到prototype上
寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类的构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数的内部
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
// 这个函数接收两个参数,subType式子类的构造函数,superType父类的构造函数,可以少new一次父类的构造函数
function inheritPrototype(subType,superType){
var prototype = Object.create(superType.prototype);
subType.prototype = prototype;
}
包装类
Number()、string()和Boolean()分别是数字、字符串布尔值的“包装类”
Number、String、Boolean是三个基本类型值的包装类用new调用它们可以生成“对象”版本的基本类型值
很多编程语言都有“包装类”的设计,包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
Number()、string()和Boolean()的实例都是object类型,它们的PrimitiveValue属性存储它们的本身值
new出来的基本类型值可以正常参与运算
包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
Math
幂和开方: Math.pow()、Math.sqrt()
向上取整和向下取整: Math.ceil()、Math.floor()
Math.round()可以将一个数字四舍五入为整数
Math.max()可以得到参数列表的最大值
Math.min()可以得到参数列表的最小值
Math.random()可以得到~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') 这样的写法,会算上时区
常用方法
- getDate() 得到日期1~31
- getDay() 得到星期0~6,星期日是0
- getMonth() 得到月份0~11
- getFullYear() 得到年份
- getHours() 得到小时数0~23
- getMinutes() 得到分钟数0~59
- getSeconds() 得到秒数0~59
时间戳
时间戳表示1970年1月1日零点整距离某时刻的毫秒数
通过getTime()方法(精确到毫秒)或者Date.parse()函数(精确到秒,但也是毫秒数,只不过最后三位一定是000)可以将日期对象变为时间戳
通过new Date(时间戳)的写法,可以将时间戳变为日期对象
内置构造函数
JavaScript有很多内置构造函数,比如Array就是数组类型的构造函数,Function就是函数类型的构造函数,Object就是对象类型的构造函数
内置构造函数非常有用,所有该类型的方法都是定义在它的内置构造函数的prototype上的,我们可以给这个对象添加新的方法,从而拓展某类型的功能
Object.prototype是万物原型链的终点。JavaScript中函数、数组皆为对象。
任何函数都可以看做是Function“new出来的”,那我们开一个脑洞: Object也是函数呀,它是不是Function“new出来的呢”?答案是肯定的
Object.__proto__ === Function.prototype; true
Object.__proto__.__proto__ === Object.prototype; true
Function.__proto__ === Function.prototype; true
Function instanceof Object; true
Object instanceof Function; true
Function instanceof Function; true
Object instanceof Object; true
instanceof运算符
instanceof运算符用来检测“某对象是不是某个类的实例”
底层机理:检查Student.prototype属性是否在xiaoming的原型链上(多少层都行,只要在就行)