一、认识对象
1. 是什么
- 对象(object)是键值对(k:v)的集合,表示属性和值得映射关系
- 数组中要考虑顺序,对象不考虑顺序,而是看映射关系
2. 对象的语法
- k和v之间用
冒号
分隔,每组k:v之间用逗号
分隔,最后一个k:v对之后可以不加逗号
const obj = {
name: 'dudu',
age:18,
sex:'女'
}
- 如果属性的键名不符合js表示命名规范,则这个键名必须用引号包裹
const obj = {
'test-key':'测试'
}
3. 访问属性
- 通过“点语法”访问,类似于数组中的下标
obj.name // 'dudu'
obj.age // 18
- 对于不符合js命名规范的,则必须使用
方括号[]
的写法访问,方括号内部要加单引号
obj['test-key'] // 测试
- 如果属性名是通过变量的形式存储,则必须通过方括号形式访问,此时方括号内部
不加引号
const obj = {
name: 'dudu',
age:18,
sex:'女',
'test-key':'测试'
}
var key = 'name'
obj.key // undefined
obj[key] // dudu
4. 更改属性
- 直接使用
赋值运算符=
重新赋值即可
obj.age = 20
5. 创建属性
- 若对象本身没有某个属性,在使用点语法赋值时,这个属性就会自动被创建
obj.tset = '创建'
6. 删除属性 delete
- 通过
delete操作符
来删除对象的某个属性
delete obj.test
二、对象的方法和对象的遍历
- 若
某个属性值是函数
,则其也被称为对象的方法 - 对象的方法也是通过点语法调用
const obj = {
name:'dudu',
sayHello() {
console.log("hello")
}
}
obj.sayHello() // hello
- 对象需要通过
for...in...循环
进行遍历键
for(let k in obj) {
console.log('属性'+k+'的值是'+obj[k])
}
三、 对象的深浅克隆
1. 基本类型和引用类型
var a = 3
var b = a
a++
console.log(a) // 4
console.log(b) //3
var arr1 = [1,2,3,4]
var arr2 = arr1
arr1.push(5)
console.log(arr1) // [1,2,3,4,5]
console.log(arr2) // [1,2,3,4,5]
- 基本类型:number、string、Boolean、undefined、null
- 引用类型:object、array、function、regexp等
2. 对象是引用类型
- 不能用
var obj2 = obj2
的语法克隆一个对象 - 使用 == 或 === 进行对象的比较时,他们比较的是
是否指向内存中的同一个对象
,而不是比较值是否相同
var obj1 = {
a:1,
b:2,
c:3
}
var obj2 = {
a:1,
b:2,
c:3
}
// obj1和obj2是两个不同的对象
console.log( obj1 == obj2) //false
console.log(obj1 === obj2) //false
3. 对象的浅克隆
- 只克隆对象的“
表层
”,如果对象的某些属性值又是引用类型,则不克隆他们,只是传递他们的引用
var obj1 = {
a:1,
b:2,
c:[12,23,34]
}
var obj2 = obj1; //不能实现克隆,obj1和obj2指向同一个栈,属于同一个对象
/**浅克隆*/
var obj3 = {}
for(var k in obj1) {
// 每遍历一个K属性,就给obj3添加一个同名的K属性,并且值和obj1的值相同
// a、b是基本类型,在赋值时会进行值克隆,所以两个对象中的a、b互不相关
// c是引用类型,在赋值时不会进行克隆,而是让两个对象中的c指向同一个栈
obj3[k] = obj1[k]
}
4. 对象的深克隆
克隆对象的全貌
,不管是基本类型还是引用类型,都可以进行克隆- 对象的深克隆也会使用
递归
function deepClone(obj) {
// 数组和对象都是引用类型,但两者遍历方式不同,数组通过for遍历、对象通过 for...in遍历
// typeof不能判断出对象和数组,两个都会返回object
// Array.isArray()可以判断一个对象是否是数组,所以可以先判断数组,不是数组再判断是否是对象
if(Array.isArray(obj)) {
var result = []
// 遍历数组中的每一项,然后添加到新数组中
for(var i=0;i<obj.length;i++) {
// 再添加前也需要调用一次函数判断类型(递归)
result.push(deepClone(obj[i]))
}
}else if(typeof obj == 'object') {
var result = {}
// 遍历对象,然后添加到新对象中
for(var k in obj) {
// 再添加前也需要调用一次函数判断类型(递归)
result[k] = deepClone(obj[k])
}
}else {
// 基本类型,直接进行赋值即可
var result = obj
}
// 最后要返回最终结果
return result
}
var obj1 = [22,33,{
name:'dudu',
age:18,
hobbies:['run']
}]
var obj2 = deepClone(obj1)
5. 递归
5.1 是什么
- 函数内部可以调用函数自身,从而发起对函数的迭代,即
函数自己调用自己
5.2 递归的要素
边界条件
:确定递归何时终止,也称为递归出口
递归模式
:大问题是如何分解为小问题的,也称为递归体
/** 递归
* 求4的阶层
**/
// 求阶层
function factorial(n) {
// 递归的出口:如果n为1直接返回1
if(n == 1) return 1
// 否则继续呈上n-1的阶乘
return n * factorial(n-1)
}
var res = factorial(4)
console.log(res) //24
5.3 斐波那契数列
- 形如:1、1、2、3、5、8、13、21的数列
// 返回斐波那契数列中下标为n的项对应的值
function sequence(n) {
if(n == 0 || n ==1 ) return 1
return sequence(n-1) + sequence(n-2)
}
console.log(sequence(7)) // 21
四、函数的上下文 this
this关键字
表示函数的上下文对象- this的具体指代要通过调用函数时的“前言后语”来判断
1. 函数的上下文this是由函数的调用方式
决定的
- 以函数的形式调用,this指向window
- 以对象打点的形式调用,this指向调用的那个对象
var person = {
name:'dudu',
age:18,
sayHello() {
console.log(`我是${this.name},今年${this.age}岁了`)
}
}
person.sayHello() // 此时this指向person对象
var sayHello = person.sayHello
sayHello() // 此时this指向window
- 函数只有被调用时,才可以确定上下文
2. 上下文规则
- 规则一:
对象打点调用函数时(对象.方法),上下文是打点的对象
var obj1 = {
a:1,
b:2,
fn() {
console.log(this.a+this.b)
}
}
var obj2 = {
a:3,
b:4,
fn:obj1.fn
}
obj2.fn() // obj2调用的,所以this是obj2。然后函数也是引用类型,进行赋值的时候会指向同一个堆,所以会打印出 7
function outer() {
var a = 20
var b = 12
return {
a:33,
b:44,
fn() {
console.log(this.a+this.b)
}
}
}
outer().fn() // outer()返回一个对象,所以最后也是构成了对象.方法的调用,this就会指向返回的对象,所以会打印出77
- 规则二:
圆括号直接调用函数( 函数() ),则上下万是window对象
var obj1 = {
a:1,
b:2,
fn() {
console.log(this.a+this.b)
}
}
var a = 3
var b = 4
var fn = obj1.fn
fn() //通过函数直接调用,this指向window对象,则结果会返回7
function fn() {
return this.a + this.b
}
var a = 1
var b =2
var obj = {
a:3,
b:fn(),
fn:fn
}
var result = obj.fn() //通过对象的形式调用,this指向obj对象,a=3,b:fn()相当于全局调用了fn方法,b中的this指向window,所以b=3
console.log(result) // 6
- 规则三:
数组(类数组对象)枚举出函数进行调用:数组[下标]() ,上下文是这个数组(类数组对象)
类数组对象
:所有的键名为自然数序列(从0开始)且有length属性的对象arguments对象
是最常见的类数组对象,它是函数的实参列表
function fun() {
arguments[3]()
}
fun('A','B','C',function() {
console.log(this[1]) // this指向arguments类数组对象'A','B','C',function(),结果输出B
})
- 规则四:
IIFE中的函数,上下文是window对象
(function() {
…
})()
var a = 1
var obj = {
a:2,
fun:(function(){
var a = this.a; //IIFE中的this指向window,则a=1
return function() {
// a 在函数体内部,则a=1
// return 返回了一个函数
console.log(a + this.a) // 3
}
})()
}
obj.fun() //点的方式调用函数,则this指向obj对象,则this.a=2
- 规则五:
定时器、延时器调用函数,上下文是window对象
setInterval(函数,时间)
setTimeout(函数,时间)
var obj = {
a:1,
b:2,
fun() {
console.log(this.a + this.b)
}
}
var a = 3
var b = 4
setTimeout(obj.fun, 2000); //this指向window,则this.a=3,this.b=4
setTimeout(() => {
obj.fun() //对象点方法的形式调用函数,则this指向obj对象,this.a=1、this.b=2
}, 2000);
- 规则六:
事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function() {
//当函数内部有其他函数,比如定时器时,要考虑this的指向
// 必要时需要备份this,即 var _this = this,这时在内部的函数中就可以用_this指向需要的DOM
}
3. call和apply
3.1 call和apply能指定函数的上下文
函数.call(上下文)
函数.apply(上下文)
function sum() {
console.log(this.chinese + this.math + this.english)
}
const person = {
chinese:98,
math:100,
english:95
}
sum.call(person)
sum.apply(person)
3.2 call和apply的区别
- 在传递参数时,
call
要用逗号
分隔参数,apply
是用数组
存放参数 - 第一个参数为上下文,第二个参数为要传的参数值
function sum(x,y) {
console.log(this.chinese + this.math + this.english + x +y)
}
const person = {
chinese:98,
math:100,
english:95
}
sum.call(person,4,5)
sum.apply(person,[4,5])
五、构造函数
5.1 用new调用函数的四步骤
new 函数()
- 函数体内部自动创建一个空白对象
- 函数的上下文(this)会指向这个对象
- 函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有return语句
5.2 构造函数
- 用
new 调用
的函数,就是构造函数 - 构造函数命名时
首字母要大写
- 构造函数的this不是函数本身
5.3 类与实例
- ‘萨摩耶’是类,嘟嘟是实例
// Person是类
class Perosn(name,age,sex) {
this.name = name
this.age = age
this.sex = sex
}
// dudu是实例
var dudu = new Perosn('嘟嘟',18,'女')
// heihei是实例
var heihei = new Perosn('黑黑',19,'男')
六、原型和原型链
6.1 原型对象
- 在创建函数时,解析器会自动为函数添加一个属性
prototype(原型)
,这个属性对应着一个对象称为原型对象
- 任何函数对象都有prototype属性
- 如果函数作为普通函数调用,则prototype无意义
- 若作为构造函数调用,它所创建的对象(实例)中都会有一个隐含的属性,指向该构造函数的原型对象,可以通过
__ proto__
访问该隐含属性
console.log(dudu.__proto__ === Perosn.prototype) //true
- 构造函数的prototype是其实例的原型
6.2 原型链查找
- JavaScript规定:
实例可以打点访问它的原型的属性和方法
,这被称为“原型链查找
” - 原型对象相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象。所以可以将对象中公共的内容,统一设置到原型对象中
- 当访问对象的一个属性或方法时,它会优先在对象自身寻找,如果没有则会去原型对象找
// 向原型对象中添加一个属性hobby
Perosn.prototype.hobby = 'run'
console.log(dudu.hobby) // run
// 向heihei对象添加属性hobby
heihei.hobby = 'eat'
console.log(heihei.hobby) // eat
6.3 hasOwnProperty()
- 使用
in
检测对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log('hobby' in dudu) // true
- 使用
hasOwnProperty()
检测对象自身是否含有某属性,只有对象自身含有该属性才会返回true
console.log(dudu.hasOwnProperty('hobby')) // false
6.4 原型链的终点
- 原型对象也是对象,所以也有原型
- 当使用一个对象的属性或方法时,首先会先在对象自身寻找
- 当自身没有,就会在原型对象中寻找
- 如果原型对象中没有,则会寻找原型的原型中,直到找到Object对象的原型(一般只会查找两层)
- Object对象的原型没有原型,如果在Object中仍然没有找到,则返回undefined
// 检测hasOwnProperty所在的位置
console.log(dudu.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) // true
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')) //true
console.log(dudu.__proto__.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
- prototype属性值是对象,它默认拥有
constructor属性
指向函数
function sum(a,b) {
return a+b
}
console.log(sum.prototype.constructor === sum) //true
6.5 在prototype上添加方法
- 这样可以避免内存的浪费
Perosn.prototype.sayHello = function() {
console.log(`hello!我是${this.name}~`)
}
console.log(dudu.sayHello === heihei.sayHello) // true
dudu.sayHello() // hello!我是嘟嘟~
6.6 继承
- 实现继承的关键:子类拥有父类的
全部属性和方法
,同时子类还可以定义自己特有的属性和方法
可以通过JavaScript特有的原型链特性实现继承
// 父类
function Perosn(name,age,sex) {
this.name = name
this.age = age
this.sex = sex
}
Perosn.prototype.sayHello = function() {
console.log(`hello!我是${this.name},我今年${this.age}岁啦~~`)
}
Perosn.prototype.sleep = function() {
console.log(this.name+'已经开始睡觉啦zzzzzzzz')
}
// 子类
function Student(name,age,sex,school,studentId) {
this.name = name
this.age = age
this.sex = sex
this.school = school
this.studentId = studentId
}
// 实现继承的关键语句
Student.prototype = new Perosn()
Student.prototype.study = function() {
console.log(this.name+'要准备学习了~~~~~')
}
Student.prototype.exam = function() {
console.log(this.name+'正在积极备考!!!!!!!!')
}
// 实例化
var dudu = new Student('嘟嘟',19,'女','汪汪汪大学',201802)
dudu.sayHello()
dudu.study()
dudu.sleep()
dudu.exam()
console.log(dudu.studentId)
- 子类可以重写父类的方法
七、其他
7.1 包装类
- Number()、String()、Boolean()分别是数字、字符串、布尔值的
包装类
- 包装类是为了让基本类型值可以通过他们构造函数的prototype上获得方法
- Number()、String()、Boolean()的实例都是object类型
- new出来的基本类型值可以正常参与运算
var a = new Number(123)
var b = new String('慕课网')
var c = new Boolean(true)
console.log(a)
console.log(typeof a)
console.log(b)
console.log(typeof b)
console.log(c)
console.log(typeof c)
console.log('--------------------------------')
console.log(5 + a)
console.log(b.slice(0,2))
console.log(c && true)
7.2 Math(数学)对象
- 幂和开方:Math.pow()、Math.sqrt()
- 向上取整和向下取整:Math.ceil()、Math.floor()
7.2.1 Math.round()
- 将一个数字四舍五入为整数
- 四舍五入到小数点后的某位
var x = 9.345667
// 将x四舍五入到小数点后3位
console.log(Math.round(x * 1000) / 1000)
7.2.2 Math.max() 和 Math.min()
- Math.max():参数列表中的最大值
- Math.min():参数列表的最小值
- 利用Math.max()求数组最大值——借助apply
- Math.max()要求参数是通过逗号分隔,不能是数组
- 所以借助apply方法,不指定上下文,传入数组
var arr = [2,12,34,546,7]
var max = Math.max.apply(null,arr)
console.log(max) // 546
7.2.3 Math.random()
- 可以得到 0 ~ 1 之间的小数
- 获取 [a,b] 区间内的整数:
parseInt(Math.random() * (b - a + 1)) + a
// 生成[3,8]之间的整数
console.log(parseInt(Math.random() * 6) + 3)
7.3 Date(日期)对象
- 通过
new Date()
即可获得当前时间的日期对象,他是Object类型值 - 通过
new Date(yyyy,mm,d)
可以指定日期的时间对象,但月份是从0开始,即11代表12月,这种写法不算时区 new Date('yyyy-mm-dd')
字符串的写法中月份是从1开始算,并且月份和日期都要保持两位,不足补0,这种写法会算时区
7.3.1 时间对象常见的方法
7.3.2 时间戳
- 时间戳表示1970年1月1日零点距离某时刻的毫秒数
- 通过
getTime()
或者Date.parse()
可以将日期对象变为时间戳 - 通过
new Date(时间戳)
可以将时间戳变为日期对象
var d = new Date()
var timestamp1 = d.getTime() // 精确到毫秒
var timestamp2 = Date.parse(d) // 精确到秒