JavaScript对象

对象

面向对象编程(OOP)

  1. 程序是干嘛的?
    -程序就是对现实世界的抽象(照片就是对人的抽象)
  2. 对象是干嘛的?
    • 一个事物抽象到程序中后就变成了对象
    • 在程序的世界中,一切皆对象
  3. 面向对象的编程
    • 面向对象的编程指,程序中的所有操作都是通过对象来完成
    • 做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作
      对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型

简单说,对象就是一组**“键值对”(key-value)的集合**,是一种无序的复合数据集合
image

//创建对象user1,user2,user3
var user1=new Object();
var user2=Object();
let mySymbol = Symbol()
let user3 = {
	name:"孙悟空",
	age:18,
	["gender"]:"男",
	[mySymbol]:"特殊的属性",
	hello:{
		a:1,
		b:true
		}
}

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型,比如数组,对象,函数,字符串,布尔类型等。
如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用
函数和方法是同一个意思

var user = {
  getName: function (name) {
    return name;
  }
};
user.getName("it")
var obj=new Object(); //创建对象
obj.name = "孙悟空" //向对象中添加属性:对象.属性名 = 属性值
obj.age = 18//创建属性方法一
obj["gender"] = "男"//创建属性方法二
let str = "address"
obj[str] = "花果山" // 等价于 obj["address"] = "花果山",使用[]操作属性时可以用变量,变量就不需要加引号了
obj.name="Tom Sun"        // 修改属性
delete obj.name        // 删除属性,delete是运算符
obj.f = Object()//对象的属性值可以是对象
obj.f.name = "猪八戒"
obj.f.age = 28
console.log(obj)
console.log("name" in obj)//用in运算符来检查对象中是否含有某个属性,语法: 属性名 in obj,如果有返回true,没有返回false

运行结果
image

属性名

通常属性名就是一个字符串,没有变量命名的限制,所以可以用关键字命名,也可以数字开头
但是如果属性名有太多特殊字符,不能直接使用,需要使用[""]来设置
虽然如此,但强烈建议属性名也按照标识符的规范命名,建议使用驼峰命名法

var obj=new Object();
obj.name='Code6E';
obj.if="以if关键字命名属性"//不建议
obj["$*&^#%()"]="特殊字符的属性名";//不建议
console.log(obj["$*&^#%()"]);//控制台输出"特殊字符的属性名"

运行结果
image

也可以用symbol()作为属性名,获取这种属性时,也必须使用symbol, 使用symbol添加的属性,通常是那些不希望被外界访问的属性

let mySymbol = Symbol()
let newSymbol = Symbol()
obj[mySymbol] = "通过symbol添加的属性"
console.log(obj[mySymbol])//mySymbol相当于一把钥匙,只能通过mySymbol去读取symbol()属性的值
console.log(obj[newSymbol])//取不到结果,因为只能用创建symbol()属性时的变量名取symbol属性值

如果属性的值还是一个对象,就形成了链式引用

var user = {
    name:"itbaizhan",
    age:13,
    container:{
        frontEnd:["Web前端","Android","iOS"],
        backEnd:["Java","Python"]
    }
}
user.container.frontEnd // ["Web前端","Android","iOS"]

枚举对象中的属性

枚举属性,指将对象中的所有的属性全部获取

  1. for…in
    for(let 变量 in 对象){
    语句…
    }
  • for-in的循环体会执行多次,有几个属性就会执行几次,每次执行时,都会将一个属性名赋值给我们所定义的变量
  • 注意:并不是所有的属性都可以枚举,比如使用符号添加的属性不能被枚举
let obj = {
            name:'孙悟空',
            age:18,
            gender:"男",
            address:"花果山",
            [Symbol()]:"测试的属性" // 符号添加的属性不能被枚举
        }

for(let propName in obj){
	console.log(propName, obj[propName])//不能写成obj.propName,这样写会显示undefined,因为.不能识别变量
}

运行结果
image
2. 静态方法Object.keys()

  • 返回值:一个由一个给定对象的自身可枚举属性组成的数组,,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]

对象中的静态方法

Object.assign() 静态方法将一个或者多个源对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象。Object.assign()是ES6的一个方法,将后面的对象与第一个对象合并,相同属性后面的覆盖前面的,第一个对象不存在的属性会原封不动存到该对象。第一个对象有而后面的对象没有的属性保持不变

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// Expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget === target);
// Expected output: true

对象的解构赋值

对象的解构赋值与数组类似,但有些语法需要注意

const obj = { name: "孙悟空", age: 18, gender: "男" }
// 声明变量同时解构对象
let { name, age, gender } = obj 
// 或 声明与解构赋值分开写
// let name, age, gender
// 分开写时为避免被JS解析器误认为是给语句块赋值而报错,可以在花括号外加小括号来避免这种误解
// ({ name, age, gender } = obj)
// 没有的属性返回undefined
let { address } = obj 
console.log(name, age, gender)
// 这里取了别名。address:d="花果山"表示先在obj中寻找address属性,如果找到了就赋值给d,没有找到就取默认值"花果山"给d
let {name:a, age:b, gender:c, address:d="花果山"} = obj
console.log(a, b, c, d)

image

对象的序列化

对象的序列化

  • JS中的对象使用时都是存在于计算机的内存中的
  • 序列化指将对象转换为一个可以存储的格式,在JS中对象的序列化通常是将一个对象转换为字符串(JSON字符串)
  • 序列化的用途(对象转换为字符串有什么用):
    • 对象转换为字符串后,可以将字符串在不同的语言之间进行传递,甚至人可以直接对字符串进行读写操作,使得JS对象可以在不同的语言之间传递
    • 用途:
      1. 作为数据交换的格式
      2. 用来编写配置文件
  • 如何进行序列化:
    • 在JS中有一个工具类 JSON (JavaScript Object Notation) JS对象表示法
    • JS对象序列化后会转换为一个字符串,这个字符串我们称其为JSON字符串
  • 也可以手动的编写JSON字符串,很多程序的配置文件就是使用JSON编写的
  • 编写JSON的注意事项:
    1. JSON字符串有两种类型:
      JSON对象 ‘{}’
      JSON数组 ‘[]’
    2. JSON字符串的属性名必须使用双引号引起来(因为不是所有语言支持单引号引出字符串,但几乎都支持双引号引出字符串)。因为内部属性用了双引号,为了不发生双引号的配对错误,整个JSON字符串一般就用单引号引起
    3. JSON中可以使用的属性值(元素),一般是所有语言都支持的纯数据,因为函数不是纯数据,其他语言并不是都有bigint、NAN、undefined,所以它们不能作为转化为JSON字符串的对象中的属性值
      • 数字(Number)
      • 字符串(String) 必须使用双引号
      • 布尔值(Boolean)
      • 空值(Null)
      • 对象(Object {})
      • 数组(Array [])
    4. JSON的格式和JS对象的格式基本上一致的,
      注意:JSON字符串如果属性是最后一个,则不要再加逗号,
  • JSON.stringify() 可以将一个对象转换为JSON字符串
  • JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
const obj = {
    name: "孙悟空",
    age: 18,
}
// 将obj转换为JSON字符串
const str = JSON.stringify(obj) //JSON.stringify() 可以将一个对象转换为JSON字符串
const obj2 = JSON.parse(str) // JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
console.log(obj)
console.log(str) // {"name":"孙悟空","age":18}
console.log(obj2)
const str2 = '["hello", true, []]'
console.log(str2)

image

对象的存储

  • 原始值都属于不可变类型,一旦创建就无法修改
  • 在内存中不会创建重复的原始值
let a = 10 
let b = 10
a = 12 // 当我们为一个变量重新赋值时,绝对不会影响其他变量, 只是改变了变量中存储的地址
console.log("a =", a)
console.log("b =", b)

image

  • 对象属于可变类型
  • 当对两个对象进行相等或全等比较时,比较的是对象的内存地址
  • 如果有两个变量同时指向一个对象,通过一个变量修改对象时,对另外一个变量也会产生影响
let obj = Object()
obj.name = "孙悟空"
obj.age = 18
let obj2 = Object()
let obj3 = Object()
console.log(obj2 == obj3) // false
let obj4 = obj
console.log(obj === obj4)//true
obj4.name = "猪八戒" // 当修改一个对象时,所有指向该对象的变量都会收到影响
console.log("obj", obj)
console.log("obj4", obj4)

image
image
image
image

在使用变量存储对象时,很容易因为改变变量指向的对象,提高代码的复杂度, 所以多数情况下,声明存储对象的变量时会使用const(是否使用const声明对象看具体需求)
const只是禁止变量被重新赋值,不影响修改对象

const obj={
name:"孙悟空"};
var obj2=obj;
obj2.name="猪八戒";//修改对象
console.log(obj);
var obj3={};
obj=obj3;//报错

image

实时效果反馈

1. 下列关于对象描述正确的是:

A 对象是条件语句,负责进行判断

B 对象是一段反复调用的代码块

C 对象就是一组“键值对”(key-value)的集合

D 对象是按次序排列的一组值。每个值的位置都有编号(从0开始)

答案
1=>C
B是对函数的描述,D是对数组的描述

this对象

普通函数的this由调用函数的方式决定,与函数创建方式无关

  • 函数在执行时,JS解析器每次都会传递进一个隐含的参数,这个参数就叫做 this
  • this会指向一个对象,this所指向的对象会根据函数调用方式的不同而不同
    • 以函数形式调用时,this指向的是window
    • 以方法的形式调用时,this指向的是调用方法的对象
  • 通过this可以在方法中引用调用方法的对象
            function fn() {
                // console.log(this === window)
                console.log("fn打印", this)
            }

            const obj = { name: "孙悟空" }
            obj.test = fn

            const obj2 = { name: "猪八戒", test: fn }

            fn()//这里以函数形式调用函数且fn()方法属于window对象,所以这里this指向window对象
            window.fn()//这里以函数形式调用函数。这种写法与fn()等效
            obj.test() // {name:"孙悟空",test:fn()}
            obj2.test() // {name:"猪八戒", test:fn()}

            const obj3 = {
                name: "沙和尚",
                sayHello: function () {
                    console.log(this.name)
                },
            }
            const obj4 = { 
                name: "唐僧",
                sayHello: function(){
                    console.log(this.name)
                }
            }

            // 为两个对象添加一个方法,可以打印自己的名字
            obj3.sayHello()
            obj4.sayHello()

image

箭头函数的this

箭头函数的this由创建箭头函数时的外层作用域决定,和它的调用方式无关

            function fn() {
                console.log("fn -->", this)
            }

            const fn2 = () => {
                console.log("fn2 -->", this) // 总是window
            }

            fn() // window
            fn2() // window

            const obj = {
                name:"孙悟空",
                fn, // fn:fn,对象的方法可以省略属性名而直接写函数
                fn2,
                sayHello(){
                    console.log(this.name)

                    // function t(){
                    //     console.log("t -->", this)
                    // }
                    // t()

                    const t2 = () => {
                        console.log("t2 -->", this)
                    }

                    t2()
                }
            }

            obj.fn() // obj
            obj.fn2() // window

            obj.sayHello()

image

apply() call() bind()

作用:

  • 改变函数的this, call() apply() bind()的第一个参数,将会成为调用函数的this
  • 实现多重继承,但只能继承实例的属性和方法,不能完全继承原型上的属性和方法

异同

  • 都是函数的方法
  • 无法通过call apply 和 bind修改箭头函数的this(因为它本身没有this)
  • apply() call()还可以调用函数,fn()与fn.apply()/fn.call()等效
  • 而fn.bind()不能调用函数,fn()与fn.bind() fn()等效,所以用bind()指定this后别忘了调用函数或将返回值赋值给一个变量
  • bind()返回一个新函数,不影响原来的函数。bind为返回的新函数绑定this和其他参数(绑定后不可修改)
  • call()与apply()的区别就是第一个参数后的参数不同,call()后面的参数是单独用逗号隔开的,apply后面的参数是一个数组
    • call(“指定的this对象”,参数1,参数2,参数3,···)
    • apply(“指定的this对象”,arr) arr=[参数1,参数2,参数3,···]

根据函数调用方式的不同,this的值也不同:

  1. 以函数形式调用,this是window
  2. 以方法形式调用,this是调用方法的对象
  3. 构造函数中,this是新建的对象
  4. 箭头函数没有自己的this,由外层作用域决定
  5. 通过call和apply调用的函数,它们的第一个参数就是函数的this
function fn() {
    console.log("函数执行了~", this)
}
// 以函数形式调用,this是window
fn()
const obj = { name: "孙悟空", fn }
// 以方法形式调用,this是调用方法的对象
obj.fn()

image
调用函数除了通过函数名()这种形式外,还可以通过其他的方式来调用函数
比如,我们可以通过调用函数的call()或apply()方法来调用函数
格式:函数.call()函数.apply()

fn.call(obj)
fn.apply(obj)
function fn2(a, b) {
    console.log("a =", a, "b =", b, this)
}
fn2.call(obj, "hello", true)
fn2.apply(obj, ["hello", true])

image
平常不用指定函数的this对象时就以函数名()形式直接调用函数,如果需要指定不是函数默认的this对象,就用函数名().call()函数名().apply()来调用对象,用call()还是apply()就看参数类型,如果参数是需要一个个单独指定就用call(), 如果参数是数组就用apply()

function fn(a, b, c) {
    console.log("fn执行了~~~~", this)
    console.log(a, b, c)
}
const obj = {name:"孙悟空"}
// newFn()的this被指定为obj,参数固定为10,20,30, 后期无法修改。不影响原来的函数
const newFn1 = fn.bind(obj, 10, 20, 30)
newFn1()
const newFn2 = fn.bind(obj, 10)
newFn2()

image

箭头函数没有自身的this,它的this由外层作用域决定,

  • 箭头函数中没有arguments
//这里箭头函数的this为外层作用域的this,即window()
const arrowFn = () => {
    console.log(this)
}
// 无法修改
arrowFn.call(obj)
// 无法修改
const newArrowFn = arrowFn.bind(obj)
newArrowFn()
class MyClass {
    fn = () => {
        console.log(this)
    }
}
//这里箭头函数的this为外层作用域的this,即实例mc的this
const mc = new MyClass()
// 无法修改
mc.fn.call(window)

image

Map对象

  • Map用来存储键值对结构的数据(key-value),之前也提过键在对象中是唯一的
  • Object中存储的数据就可以认为是一种键值对结构
  • Map和Object的主要区别:
    • Object中的属性名只能是字符串或符号(Symbol()),如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
    • Map中任何类型的值都可以称为数据的key
const obj2 = {}
// Object()对象的属性名默认是省略了引号,特殊属性名需要用中括号`[]`括起
const obj = {
    "name":"孙悟空",
    'age':18,
    [Symbol()]:"哈哈",
    [obj2]:"嘻嘻"// 因为Object()对象的属性名(键)不能是对象,这里会将属性名转换成[object Object]
}
console.log(obj)
// 以下不管[]内是什么样的对象,一律会转换成字符串"object Object",所以都能读取到属性值"嘻
console.log(obj[obj2])
console.log(obj[{}])

image

创建Map对象:new Map()
属性和方法:

  • map.size() 获取map中键值对的数量
  • map.set(key, value) 向map中添加键值对
  • map.get(key) 根据键key获取值, 不是通过map.keymap[key]获取值,否则会返回undefined
  • map.delete(key) 删除指定数据
  • map.has(key) 检查map中是否包含指定键
  • map.clear() 删除全部的键值对
// 创建一个Map
const map = new Map()
map.set("name", "孙悟空")
map.set(obj2, "呵呵")
map.set(NaN, "哈哈哈")
map.delete(NaN)
// map.clear()
console.log(map)
console.log(map.get(obj2))
console.log(map.has("name"))

image

  • 将map转换为数组arr = Array.from(map)arr = [...map]
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
map.set({}, "呵呵")
// 将map转换为数组
// const arr = Array.from(map) // [["name","孙悟空"],["age",18],[{},"呵呵"]]
const arr = [...map]
console.log(arr)

image

  • 可以用二维数组去创建Map对象
    • map.keys() - 获取map的所有的键key
    • map.values() - 获取map的所有的值value
    • map.entries() -获取map的所有的键值对entry
//可以用二维数组去创建Map对象
const map2 = new Map([
    ["name", "猪八戒"],
    ["age", 18],
    [{}, () => {}],
])
console.log(map2)

image

  • 遍历Map对象
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
map.set({}, "呵呵")

// 将map对象的每一个键值对解构分别赋值给key,value
for (const [key, value] of map) {
    // const [key, value] = entry
    console.log(key, value)
}
// 可以用数组的forEach(element, index, array)方法遍历Map对象,对于Map对象forEach(value, key, map)它的参数分别为第一个参数表示值,第二个参数表示键,这里没有写出的第三个参数表示遍历的Map对象本身 
map.forEach((value, key)=>{
    console.log(value, key)
})

for(const key of map.keys()){
    console.log(key)

image

Set对象

  • Set用来创建一个集合
  • 它的功能和数组类似,不同点在于Set中不能存储重复的数据
  • Set对象本质上是键值对相同的Map对象,因为键不能重复,所以Set中没有重复的元素

创建Set对象

  • new Set()
  • new Set(数组)
    Set对象的属性与方法
  • size 元素数量
  • add() 添加元素
  • has() 检查元素
  • delete() 删除元素
// 创建一个Set
const set = new Set()
// 向set中添加数据
set.add(10)
set.add("孙悟空")
set.add(10)//重复的10加不进去
console.log(set)
// 遍历Set对象的元素
for(const item of set){
    console.log(item)
}
// Set对象本质上是键值对相同的Map对象, Set对象可以使用一些(不是全部)Map对象的方法
for(const item of set.values()){
    console.log(item)
}
// 将set转换成数组
const arr = [...set]
console.log(arr)
// 可以通过Set对象实现数组去重。思路:重复的数组->通过Set()函数转换成没有重复元素的Set对象->再把该
Set对象转换成数组
const arr2 = [1,2,3,2,1,3,4,5,4,6,7,7,8,9,10]
const set2 = new Set(arr2)
console.log([...set2])

image

Window对象

  • 在浏览器中,浏览器为我们提供了一个window对象,可以直接访问
  • window对象代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作,除此之外window对象还负责存储JS中的内置对象(如Number())和浏览器的宿主对象(如console,document)
  • window对象的属性可以通过window对象访问,也可以直接访问。例如window.alert(123)alert(123)等效, window.console.log("哈哈")console.log("哈哈")等效
  • 函数可以认为是window对象的方法
  • 用var声明的全局变量可以认为是window对象的属性
    alert(window)
    运行结果
    image
window.a = 10 // 向window对象中添加的属性会自动成为全局变量(即在最外层用var声明的变量)
console.log(a)//10
let b=33
console.log(window.b)//控制台输出undefined, 使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法通过window.c访问)
function fn2(){
	//var d = 10 // var虽然没有块作用域,但有函数作用域
	d = 10 //在局部作用域中,如果没有使用var或let声明变量,相当于window.d, 所以变量会自动成为window对象的属性, 也就是全局变量
}
fn2()
console.log(d)//10

image

Math对象

Math是 JavaScript 的原生对象,提供各种数学功能。

  • Math是一个工具类,不是构造函数,只是能使用Math提供的方法,即不能通过new Math()来创建Math()对象,let math= new Math()这样写会报错。
  • Math中为我们提供了数学运算相关的一些常量和方法
  • 常量:
    Math.PI 圆周率
  • 方法:
    Math.abs() 求一个数的绝对值
    Math.min() 求多个值中的最小值
    Math.max() 求多个值中的最大值
    Math.pow() 求x的y次幂
    Math.sqrt() 求一个数的平方根
    Math.floor() 向下取整
    Math.ceil() 向上取整
    Math.round() 四舍五入取整
    Math.trunc() 直接去除小数位
    Math.random() 生成一个0-1之间的随机数

Math.abs()

Math.abs方法返回参数值的绝对值

Math.abs(1) // 1
Math.abs(-1) // 1

Math.max(),Math.min()

Math.max方法返回参数之中最大的那个值,Math.min返回最小的那个值。如果参数为空, Math.min返回Infinity, Math.max返回-Infinity

Math.max(2, -1, 5) // 5
Math.min(2, -1, 5) // -1
Math.min() // Infinity
Math.max() // -Infinity

Math.floor(),Math.ceil()

Math.floor方法返回小于参数值的最大整数,floor原意是地板

Math.floor(3.2) // 3
Math.floor(-3.2) // -4

Math.ceil方法返回大于参数值的最小整数, ceil原意是天花板

Math.ceil(3.2) // 4
Math.ceil(-3.2) // -3

Math.random()

Math.random()返回0到1之间的一个伪随机数,0=<Math.random()<1

Math.random() // 0.28525367438365223

任意范围的随机数生成函数如下

function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}

getRandomArbitrary(5, 10)

实时效果反馈

1. 下列代码获得一个非负整数,横线处应该填写的内容是:

function ToInteger(x) {
    x = Number(x);
    return ___(___(x));
}
ToInteger(-10.4); // 向下取整:10

A Math.floor Math.abs

B Math.ceil Math.abs

C Math.ceil Math.min

D Math.floor Math.min

答案

1=>A

Date对象

详情参考MDN
Date对象是 JavaScript 原生的时间库。它以1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)
1s=1000ms

在JS中所有的和时间相关的数据都由Date对象来表示

时间戳

因为时间的单位与进制比较多,为了便于计算,计算机底层使用的都是时间戳
时间戳是指格林威治时间1970年01月01日00时00分00秒(相当于北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。

格林威治和北京时间就是时区的不同

Unix是20世纪70年代初出现的一个操作系统,Unix认为1970年1月1日0点是时间纪元。JavaScript也就遵循了这一约束

Date对象的方法

  • 实例.getTime():返回实例距离1970年1月1日00:00:00 UTC 的毫秒数
  • Date.now():返回当前时间距离1970年1月1日 00:00:00 UTC 的毫秒数
    以下返回值类型均为number, 且不会自动补齐0
  • 实例.getDate():返回实例对象对应每个月的几号(从1开始)
  • 实例.getDay():返回星期几,星期日为0,星期一为1,以此类推
  • 实例.getFullYear():返回四位的年份
  • 实例.getMonth():返回月份的索引(0表示1月,11表示12月
  • 实例.getHours():返回小时(0-23)
  • 实例.getMilliseconds():返回毫秒(0-999)
  • 实例.getMinutes():返回分钟(0-59)
  • 实例.getSeconds():返回秒(0-59)

如果想创建一个指定时间的Date对象,可以以let d = new Date(时间字符串/时间参数)的形式创建
字符串格式有:月/日/年 时:分:秒、年-月-日T时:分:秒
Date()函数的参数分别为:Date(年, 月, 日, 时, 分, 秒, 毫秒)
示例代码如下

d = new Date("2019-12-23T23:34:35")
d = new Date("12/23/2019 23:34:35")
d = new Date(2019, 11, 23, 23, 34, 35)//注意参数形式时月份是从0开始,0对应1月,1对应2月,以此类推... 个人喜欢这种写法,不容易搞混
let date = new Date() // 直接通过new Date()创建时间对象时,实例对象存储的时间是被创建的时间,即该条语句执行的时间
var d = new Date('January 6, 2022');
d.getDate() // 6
d.getMonth() // 0
d.getYear() // 122
d.getFullYear() // 2022

编写函数获得本年度剩余天数

function leftDays() {
  var today = new Date();
  var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);//年,月,日,时,分,秒,毫秒
  var msPerDay = 24 * 60 * 60 * 1000;//一天的毫秒数
  return Math.round((endYear.getTime() - today.getTime()) / msPerDay);//endYear-today得到的是秒数,不是标准的时间戳,所以要用getTime函数
}

实时效果反馈

1. 下列代码计算本年度剩余天数,划横线处应该填写代码是:

function leftDays() {
  var today = new Date();
  var endYear = new Date(today.___, 11, 31, 23, 59, 59, 999);
  var msPerDay = 24 * 60 * 60 * 1000;
  return Math.round((endYear.___ - today.___) / msPerDay);
}

A getTime() getDate() getTime()

B getTime() getTime() getDate()

C getFullYear() getTime() getDate()

D getFullYear() getTime() getTime()

答案

1=>D

日期的格式化

上面通过getDate()、getDay()、getFullYear、getMonth()、getHours()、toLocaleString()等方法获取当地日期时间再转化成相应的时间格式比较麻烦。Date对象内置了获取指定格式日期时间的方法
这里只列出一部分,详见MDN

toLocaleDateString():方法返回指定日期对象日期部分的字符串,该字符串格式因不同语言而不同。
toLocaleTimeString():返回指定日期对象时间部分的字符串,该字符串格式因不同语言而不同。
toLocaleString():返回指定日期对象的日期时间的字符串,该字符串格式因不同语言而不同

功能:可以将一个日期转换为本地时间格式的字符串

  • 参数:
    1. 描述语言和国家信息的字符串
      zh-CN 中文中国
      zh-HK 中文香港
      zh-TW 中文台湾
      en-US 英文美国
    2. 需要一个对象作为参数,在对象中可以通过对象的属性来对日期的格式进行配置
      dateStyle 日期的风格
      timeStyle 时间的风格
      full
      long
      medium
      short
      hour12 是否采用12小时值
      true
      false
      weekday 星期的显示方式
      long
      short
      narrow
      year
      numeric
      2-digit
const d = new Date()
let result1 = d.toLocaleDateString() // 将日期转换为本地格式的字符串
console.log(result1)
result1 = d.toLocaleTimeString() // 将时间转换为本地格式的字符串
console.log(result1)
result2 = d.toLocaleString("zh-CN", {
//对象参数中没写相应的属性则不会显示相应的时间,比如没写有关年份的属性则不会显示年份
    year: "numeric",
    month: "long",
    day: "2-digit",
    weekday: "short",
})
console.log(result2)

image

类与对象的关系:类是对象的模板/抽象,对象是类的实例/具体
使用Object创建对象的问题:

  1. 无法区分出不同类型的对象
  2. 不方便批量创建对象
    在JS中可以通过类(class)来解决这个问题:
  3. 将对象中的属性和方法直接定义在类中,就可以直接通过类来创建对象
  4. 通过同一个类创建的对象,我们称为同类对象。可以使用instanceof来检查一个对象是否是由某个类创建
    语法:
  • 创建类
    class 类名 {} // 类名要使用大驼峰命名

    const 类名 = class {}
  • 通过类创建对象
    let 变量名 = new 类()
            // const Person = class {}

            // Person类专门用来创建人的对象
            class Person{

            }

            // Dog类式专门用来创建狗的对象
            class Dog{

            }


            const p1 = new Person()  // 调用构造函数创建对象
            const p2 = new Person()

            const d1 = new Dog()
            const d2 = new Dog()

            console.log(p1 instanceof Person) // true
            console.log(d1 instanceof Person) // false

类的属性

类的代码块,默认就是严格模式。类的代码块是用来设置对象的属性的,不是什么代码都能写。比如在类中不能写let b;
类中的属性分为:

  • 实例属性:实例属性只能通过实例访问
  • 类属性:使用static声明的属性,是静态属性(类属性)。静态属性只能通过类去访问
        class Person{
           name = "孙悟空" // Person的实例属性name p1.name
           age = 18       // 实例属性只能通过实例访问 p1.age

           static test = "test静态属性" // 使用static声明的属性,是静态属性(类属性) Person.test
           static hh = "静态属性"   // 静态属性只能通过类去访问 Person.hh




        }

        const p1 = new Person()
        const p2 = new Person()

        console.log(p1)
        console.log(p2)

类的方法

同样的,类的方法有实例方法和静态方法(类方法)

        class Person{

            name = "孙悟空"

            // sayHello = function(){

            // } // 添加方法的一种方式

            sayHello(){
                console.log('实例方法' + this)
            } // 实例方法,通过实例来调用,实例方法中this就是当前实例

            static test(){
                console.log("我是静态方法", this)
            } // 静态方法(类方法),用static声明,通过类来调用,静态方法中this指向的是当前类

        }

        const p1 = new Person()

        // console.log(p1)

        Person.test()

        p1.sayHello()

image

包装类(了解就行)

JS通过包装类,使得我们可以直接调用原始值的属性与方法,格式为原始值.方法装载原始值的变量.方法
下面讲解其原理

JS中一共有5个包装类
String --> 字符串包装为String对象
Number --> 数值包装为Number对象
Boolean --> 布尔值包装为Boolean对象
BigInt --> 大整数包装为BigInt对象
Symbol --> 符号包装为Symbol对象

  • 通过包装类可以将一个原始值包装为一个对象,当我们对一个原始值调用方法或属性时,JS解释器会临时将原始值包装为对应的对象,然后调用这个对象的属性或方法
  • 由于原始值会被临时转换为对应的对象,这就意味着对象中的方法都可以直接通过原始值来调用
let num = 11
// 例如这里原始值num可以像对象一样直接调用Number对象的方法
num = num.toString() // "11"
console.log(num)// "11"
110.toString()// "110"

构造函数

当我们在类中直接指定实例属性的值时,意味着我们创建的所有对象的属性都是这个值

class Person{
	name="孙悟空"
	age=18
	gender="男"
	sayHello(){
	console.log(this.name)
	}
}
        const p1 = new Person()
        const p2 = new Person()
        const p3 = new Person()
//实例的属性都相同,这样不够灵活
        console.log(p1)
        console.log(p2)
        console.log(p3)

这时可以在类中添加构造函数constructor(),构造函数的名字是固定的。构造函数会在我们调用类创建对象时执行

        class Person{
            constructor(name, age, gender){
                // console.log("构造函数执行了~", name, age, gender)
                // 可以在构造函数中,为实例属性进行赋值
                // 在构造函数中,this表示当前所创建的对象
                this.name = name
                this.age = age
                this.gender = gender

            }

        }

        const p1 = new Person("孙悟空", 18, "男")
        const p2 = new Person("猪八戒", 28, "男")
        const p3 = new Person("沙和尚", 38, "男")

        console.log(p1)
        console.log(p2)
        console.log(p3)

封装

面向对象的特点:封装、继承和多态
封装主要用来保证数据的安全
“装”:对象就是一个用来存储不同属性的容器
“封”:对象不仅存储属性,还要负责数据的安全
直接添加到对象中的属性,并不安全,因为它们可以被任意的读取与修改
如何确保数据的安全:
1.私有化数据:将需要保护的数据设置为私有,只能在类内部使用。格式为在变量前加#,私有变量需要在类内部先声明才能使用
2.提供setter和getter方法来开放和控制对数据的操作

属性设置私有,通过getter setter方法操作属性带来的好处

  1. 可以控制属性的读写权限。想让私有变量不可读不可写就可以不提供getter和setter方法
  2. 可以在方法中对属性的值进行验证。setter方法中可以添加代码来控制值被修改的条件
    通过getter和setter方法来操作属性的常用格式
    get 属性名(){
    return this.#属性
    }
    set 属性名(参数){
    this.#属性 = 参数
    }
            class Person {
                // #address = "花果山" // 实例使用#开头就变成了私有属性,私有属性只能在类内部访问

                #name
                #age
                #gender

                constructor(name, age, gender) {
                    this.#name = name
                    this.#age = age
                    this.#gender = gender
                }
                //以下5个函数是读取与修改属性的老方法
                sayHello() {
                    console.log(this.#name)
                }

                // getter方法,用来读取属性
                getName(){// p1.getName(),以这种形式读取属性
                    return this.#name
                }

                // setter方法,用来设置属性
                setName(name){// p1.setName('猪八戒'),以这种形式修改属性
                    this.#name = name
                }

                getAge(){
                    return this.#age
                }

                setAge(age){

                    if(age >= 0){//控制年龄为非负数
                        this.#age = age
                    }
                }
                //以下2个函数是读取与修改属性的新方法,这样可以直接以属性而不是函数的形式读取和修改属性
                get gender(){//p1.gender
                    return this.#gender
                }

                set gender(gender){//p1.gender = "女",以这种形式修改属性
                    this.#gender = gender
                }
            }
            
            const p1 = new Person("孙悟空", 18, "男")//创建新对象的参数会传递到构造函数 
            p1.setAge(-11) //年龄不被修改
            console.log(p1.gender)

多态

多态

  • 在JS中不会检查参数的类型,所以这就意味着任何类型的数据都可以作为参数传递
  • 要调用某个函数,实参对象无需指定的类型,只要对象满足某些条件即可
  • 对于程序来说,如果一个东西走路像鸭子,叫起来像鸭子,那么它就是鸭子
  • 多态为我们提供了灵活性
        class Person{
            constructor(name){
                this.name = name
            }
        }

        class Dog{
            constructor(name){
                this.name = name
            }
        }
		const dog = new Dog('旺财')
        const person = new Person("孙悟空")
		function sayHello(obj){
            // if(obj instanceof Person){//不检查对象的类型,这种特点叫做多态
                console.log("Hello,"+obj.name)
            // }
       }

       sayHello(dog)

继承

继承

  • 可以通过extends关键来完成继承
  • 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
  • 继承发生时,被继承的类称为 父类(超类),继承的类称为 子类
  • 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展

面向对象编程的3大特点:
封装 —— 安全性
继承 —— 扩展性
多态 —— 灵活性

        class Animal{
            constructor(name){
                this.name = name
            }

            sayHello(){
                console.log("动物在叫~")
            }
        }

        class Dog extends Animal{

            // 在子类中,可以通过创建同名方法来重写父类的方法
            sayHello(){
                console.log("汪汪汪")
            }
            
        }

        class Cat extends Animal{

            // 重写构造函数
            constructor(name, age){
                // 重写构造函数时,构造函数的第一行代码必须为super()
                super(name) //需要传递参数给父类时,在super中写上相应的参数,调用父类的构造函数
                this.age = age
            }
            
            sayHello(){

                // 调用一下父类的sayHello
                super.sayHello() // 在方法中可以使用super来引用父类的方法
                console.log("喵喵喵")
            }
        }
        const dog = new Dog("旺财")
        const cat = new Cat("汤姆", 3)

        dog.sayHello()
        cat.sayHello()
        console.log(dog)
        console.log(cat)

image

对象的结构

对象中存储属性的区域实际有两个:

  1. 对象自身
    • 直接通过对象所添加的属性,位于对象自身中
    • 在类中通过 x = y 的形式添加的属性,位于对象自身中
  2. 原型对象(prototype)
    • 对象中还有一些内容,会存储到原型对象里
    • 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
    • 原型对象也负责为对象存储属性,当我们访问对象中的属性时,会优先访问对象自身的属性,对象自身不包含该属性时,才会去原型对象中寻找
    • 会添加到原型对象中的情况:
      1. 在类中通过xxx(){}方式添加的方法,位于原型中。而xxx = function(){}这种不会添加到原型对象中
      2. 主动向原型中添加的属性或方法
            class Person {
                name = "孙悟空"//存到对象自身中
                age = 18

                // constructor(){
                //     this.gender = "男"
                // }

                sayHello() {//存到原型对象中
                    console.log("Hello,我是", this.name)
                }
            }

            const p = new Person()

            // p.address = "花果山"
            // p.sayHello = "hello"

            console.log(p.sayHello)

image
image

原型

image

对象的隐式原型(实例对象.__proto__)===该对象构造函数的显式原型(Constructor.prototype)
访问一个对象的原型对象的方法

  • 对象.__proto__,注意这种方法不要以对象.__proto__ = 值这种方式赋值,这样会产生意想不到的后果
  • 类.prototype构造函数.prototype,这是以类的属性方式去访问原型
  • Object.getPrototypeOf(对象/实例)。这种方法避免了轻易被赋值的问题
    原型对象中的数据:
  • 对象中的部分属性、方法等
  • constructor (对象的构造函数)
class Person {
                name = "孙悟空"
                age = 18

                sayHello() {
                    console.log("Hello,我是", this.name)
                }
            }

            const p = new Person()
			
			const obj = {} //这是直接创建的对象,所以obj的父类是Object,obj.__proto__即Object.prototype,为原型链的尽头

类.prototype=实例/对象.proto,即类的显式原型等于其实例的隐式原型
原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同

  • p对象的原型链:p.__proto__ = Person.prototype --> Person.prototype .__proto__=Object.__proto__ --> Object.__proto__.__proto__ = null
  • obj对象的原型链:obj.__proto__ = Object.prototype --> Object.prototype .__proto__ = null
    Object.__proto__是原型链的尽头
    原型链:取对象属性时,会优先读取对象自身属性,如果对象中有,则使用,没有则去对象的原型中寻找,如果原型中有,则使用,没有则去原型的原型中寻找,直到找到Object对象的原型(Object的原型没有原型(为null)),如果依然没有找到,则返回undefined
           class Person {
                name = "孙悟空"
                age = 18

                sayHello() {
                    console.log("Hello,我是", this.name)
                }
            }

            class Dog {}

            const p = new Person()
            const p2 = new Person()
			console.log(p==p2)//false
            console.log(p.__proto__ === p2.__proto__)//true
            p.sayHello = "hello"

            const d = new Dog()
            const d2 = new Dog()
			
            class Animal{

            }

            class Cat extends Animal{

            }

            class TomCat extends Cat{

            }
			const cat = new Cat()
			// TomCat --> cat --> Animal --> Object --> Object原型 --> null
            // cat --> Animal --> Object --> Object原型 --> null
            // p.__proto__=Person.__proto__ --> object --> Object原型 --> null
			console.log(cat.__proto__)//Animal
			console.log(cat.__proto__.__proto__)//Object
			console.log(cat.__proto__.__proto__.__proto__);//Object.__proto__即Object的原型
            console.log(cat.__proto__.__proto__.__proto__.__proto__)//null

image

所有的同类对象它们的原型对象都是同一个,也就意味着,同类对象的原型链是一样的
image

原型的作用

原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将该类实例中,所有的公共属性(方法)统一存储到原型中, 这样我们只需要创建一个属性,即可被所有实例访问
JS中继承就是通过原型来实现的,当继承时,子类的原型就是一个父类的实例
在对象中有些值是对象独有的,像属性(name,age,gender)每个对象都应该有自己值,但是有些值对于每个对象来说都是一样的,像部分方法和属性,对于一样的值没必要重复的创建

原型链与作用域链的区别

  • 作用域链,是找变量的链,找不到会报错。寻找顺序是从内层到外层
  • 原型链,是找属性的链,找不到会返回undefined。寻找顺序是从外层到内层
修改原型

大部分情况下,我们不需要修改原型对象。修改原型时,最好通过通过类去修改
不要通过类的实例去修改原型,原因如下:

  1. 修改一个对象的原型会影响所有同类对象,这么做不安全
  2. 这样修改原型得先创建实例,麻烦

修改原型时,最好通过通过类去修改,原因如下

  1. 一修改就是修改所有实例的原型
  2. 无需创建实例即可完成对类的修改

修改原型的原则:

  1. 原型尽量不要手动改
  2. 要改也不要通过实例对象去改
  3. 通过 类.prototype 属性去修改
  4. 最好不要直接给prototype赋值
            class Person {
                name = "孙悟空"
                age = 18

                sayHello() {//以声明形式创建的方法会自动存储到原型中
                    console.log("Hello,我是", this.name)
                }
            }
            
            Person.prototype.fly = () => {//对类的原型的修改最好紧接着类的定义
                console.log("我在飞!")
            }

            class Dog{

            }

            const p = new Person()
            const p2 = new Person()

            //通过对象修改原型,向原型中添加方法,修改后所有同类实例都能访问该方法,但不建议这样做
            p.__proto__.run = () => {
                console.log('我在跑~')
            }

            p.__proto__ = new Dog() // 直接为对象赋值了一个新的原型,不建议这样做


            console.log(p)//p的原型变成了Dog
            console.log(p2)//p2的原型还是Object

            //p.run()//报错,因为p的原型已经修改成了Dog,Dog里面没有run()函数
            p2.run()//可以调用,因为p2的原型还未被修改

            console.log(Person.prototype) // 访问Person实例的原型对象
            console.log(Object.getPrototypeOf(p2))
            console.log(Person.prototype===p2.__proto__)//

            p2.fly()

运行结果
image

运算符instanceOf,in和实例方法对象/实例.hasOwnProperty和静态方法Object.hasOwn(对象,"属性/方法")

instanceof 用来检查一个对象是否是一个类的实例

  • instanceof检查的是对象的原型链上是否有该类实例,只要原型链上有该类实例,就会返回true
  • Dog -> Animal的实例 -> Object实例 -> Object原型
  • Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true

in:使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
对象.hasOwnProperty(“属性名/方法名”) (官方不推荐使用)

  • 这是一个实例方法
  • 用来检查一个对象的自身是否含有某个属性,如果属性或方法

Object.hasOwn(对象, “属性名/方法名”) (官方推荐使用)

  • 这是一个静态方法
  • 用来检查一个对象的自身是否含有某个属性
    原因如下截图:对于null对象没有继承,用对象.hasOwnProperty("属性名/方法名")会报错,用Object.hasOwn(对象, "属性名/方法名")不会报错,在大多数情况下2者其实差不多
    image
            class Animal {}

            class Dog extends Animal {}

            const dog = new Dog()
            console.log(dog instanceof Dog) // true
            console.log(dog instanceof Animal) // true
            console.log(dog instanceof Object) // true

            const obj = new Object()
            class Person {
                name = "孙悟空"
                age = 18

                sayHello() {
                    console.log("Hello,我是", this.name)
                }
            }

            const p = new Person()
            console.log("sayHello" in p)//true
            console.log(p.hasOwnProperty("sayHello"))//false
            console.log(p.__proto__.__proto__.hasOwnProperty("hasOwnProperty"))//true,hasOwnProperty()是p原型的原型中的方法
            console.log(Object.hasOwn(p, "sayHello"))

旧类(了解就行)

旧类就是早期javascript定义类的方法
早期JS中,直接通过函数来定义类

  • 一个函数如果直接调用 xxx() 那么这个函数就是一个普通函数
  • 一个函数如果通过new调用 new xxx() 那么这个函数就是一个构造函数
            /* 
                等价于:
                    class Person{

                    }
             
             */
            //用立即函数将分散的代码集合到一起执行
            var Person = (function () {
                function Person(name, age) {
                    // 在构造函数中,this表示新建的对象
                    this.name = name
                    this.age = age

                    // this.sayHello = function(){//这样添加的方法不会添加到原型中,导致每个类需重复创建方法代码,代码复用性较差
                    //     console.log(this.name)
                    // }
                }

                // 所以通过这种方法向原型中添加属性/方法
                Person.prototype.sayHello = function () {
                    console.log(this.name)
                }

                // 静态属性,添加到构造函数中
                Person.staticProperty = "xxx"
                // 静态方法,添加到构造函数中
                Person.staticMethod = function () {}

                return Person//记得返回类
            })()

            const p = new Person("孙悟空", 18)

            console.log(p)


            var Animal = (function(){
                function Animal(){

                }

                return Animal
            })()


            var Cat = (function(){
                function Cat(){

                }

                // 通过这种方法继承Animal
                Cat.prototype = new Animal()

                return Cat
            })()

            var cat = new Cat()

            console.log(cat)

image

new运算符

new运算符是创建对象时要使用的运算符

  • 使用new时,到底发生了哪些事,详见以下MDN链接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
  • 当使用new去调用一个函数时,这个函数将会作为构造函数调用,简单来说,使用new调用函数时,将会发生这些事:
    1. 创建一个普通的对象(Object对象 {}), 为了方便,称其为"新对象"var newInstance = {}
    2. 将构造函数的prototype属性赋值给新对象的原型newInstance.__proto__ = MyClass.prototype
    3. 如果有参数,将实参传递给构造函数,并且将新对象设置为函数中的this
      • 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值(不要这么做)
      • 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值
      • 通常我们不会为构造函数指定返回值
        function MyClass(){//旧类
            // var newInstance = {}
            // newInstance.__proto__ = MyClass.prototype
        }
        var mc = new MyClass()
        // console.log(mc)
        class Person{//新类
            constructor(){
            }
        }
        new Person()

面向对象本质就是,编写代码时所有的操作都是通过对象来进行的。

学习对象:

  1. 明确这个对象代表什么,有什么用
  2. 如何获取到这个对象
  3. 如何使用这个对象(对象中的属性和方法)

对象的分类:

  1. 内建对象:由ES标准所定义的对象,比如 Object Function String Number …
  2. 宿主对象(宿主就是JS的运行环境)
    • 由浏览器提供的对象
    • BOM、DOM
  3. 自定义对象:由开发人员自己创建的对象
  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值