前端基础学习之JavaScript part three

Posted by OuterCyrex on July 26, 2024

三.类、模块化与BOM

一.类

1.创建类

(class) 这一概念源于面对对象编程,是对象的抽象体现。实例化便是我们所知的对象

此前的javascript是没有这一概念的,而是使用prototype原型来实现面对对象的编程效果。

在之后的更新中,javascript加入了,来减少通过构造函数原型来构建对象的麻烦。

如此前我们要通过构造函数来定义一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const init = function(name,age){
    this.name = name;
    this.age = age;
}
init.prototype.Say = function(){
    console.log("Hello,I'm " + this.name)
}
const obj = new init("Outer",19)
console.log(obj)
obj.Say()
//输出结果,此处将整个原型链输出
init {name: 'Outer', age: 19}
age: 19
name: "Outer"

[[Prototype]]: Object
Say: ƒ ()
constructor: ƒ (name,age)

[[Prototype]]: Object

可能是意识到这样构建对象的过程过于冗杂,javascript为我们提供了语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class init{
    name
    age
    constructor(name,age) {
        this.name = name
        this.age = age
    }
    Say(){
        console.log("Hello,I'm " + this.name)
    }
}
const obj = new init("Outer",19)
console.log(obj)
obj.Say()
//输出结果,此处将整个原型链输出
init {name: 'Outer', age: 19}
age: 19
name: "Outer"

[[Prototype]]: Object
Say: ƒ Say()
constructor: class init
    
[[Prototype]]: Object

可以看到,此处的[[prototype]]内的constructor字段是class init,即我们此前定义的,而非构造函数

一个常常由三个成分构成,分别是成员变量,构造函数constructor和方法。以上方示例中的为例来介绍:

1
2
3
4
5
6
7
8
9
10
11
class 类名 {
    name
    age
    constructor(name,age){
        this.name = name
        this.age = age
    }
    Say(){
        console.log("Hello,I'm " + this.name)
    }
}

其中nameage被称为成员变量,是一个的基本构成内容。

constructor类似python中的__init__,即此前创建对象用的构造函数

在类中直接声明函数字段,实例化该后该函数便成为对应对象的方法

2.继承

若不引入类,而是通过javascript自身的原型来定义,其过程较为冗杂。

1
2
3
4
5
6
7
8
9
10
11
12
const parent = function(name,age){
    this.name = name;
    this.age = age;
}
parent.prototype.Say = function(){
    console.log("Hello,I'm " + this.name)
}
const sub = function(){
    this.sex = "Male"
}
sub.prototype = new parent("Outer",19)
console.log(new sub())

这个过程实际是原型链的对接,但需要重复定义多个原型,过于麻烦。

在引入后,我们可以轻松实现类的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class parent{
    name
    age
    constructor(name,age) {
        this.name = name
        this.age = age
    }
    Hello(){
        console.log("Hello!")
    }
}
class sub extends parent{
    job
    constructor(name,age,job) {
        super(name,age);
        this.job = job
    }
    Say(){
        console.log("My Job is " + this.job)
    }
}

const obj = new sub("Outer",19,"Programmer")
console.log(obj)
obj.Say()

//输出结果
sub {name: 'Outer', age: 19, job: 'Programmer'}
age: 19
job: "Programmer"
name: "Outer"

[[Prototype]]: parent
Say: ƒ Say()
constructor: class sub

[[Prototype]]: Object
Hello: ƒ Hello()
constructor: class parent
    
[[Prototype]]: Object
//Console.log的输出
My Job is Programmer

如上述的代码所示,我们需要extend来在父类原型链上接入子类super则可以将父类的成员变量直接传给子类

通过上述的继承过程,我们获得了这样一条原型链

1
sub -> class sub -> class parent -> Object -> Null

3.静态属性和方法

静态属性与静态方法类似于类中的常量,一般只能被调用

首先是静态方法静态方法类似于一个封装好的函数,这一点于java是如出一辙的

1
2
3
4
5
6
7
8
9
10
11
12
class Math{
    static power(a,b){
        let total = 1;
        for(let i = 0;i < b;i++){
            total *= a
        }
        return total
    }
}
console.log(Math.power(2,5))
//输出结果
32

通过static属性,可以让该方法类的实例无关,其不能被类的实例调用,而只能通过类本身来调用。

某种程度上,可以认为静态方法就是一个函数,只是这个函数被封装在了其里,这是面对对象编程的常见概念。

之后是静态属性,与静态方法一样,其不需要被实例化,而是直接访问来获取。

1
2
3
4
5
6
7
8
class Math{
    static Pi = 3.1415926535
    static Area(r){
        return r * r * Math.Pi
    }
}
console.log(Math.Pi)
console.log(Math.Area(2))

总而言之,通过静态属性与静态方法,我们可以实现对特定变量和方法封装,方便更好的调用。

二.模块化

模块化是指将一个复杂的程序依据一定的规范封装成几个块,是项目开发中的重要内容,可以帮助我们更好的分割开发任务,去除冗余内容,避免重复写入。

1.引入外部文件

HTML中引入一段javascript文件只需要在<script>标签内写入文件路径即可

1
2
3
<script src="./export.js" type="module"></script>
<!--export.js的内容-->
console.log("Hello World!")

注意,此处加type = "module"ES6的新规范,如果涉及到ES6的新特性而未加这段代码则会报错。因此一般建议将其加上。

2.引入部分内容

通常情况下,如果引入的javascript文件内容过多,仍然会导致冗余和编写效率下降。我们可以选择只引入特定的几个变量或函数,来减少引用内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="module">
    import {obj,Say,Pi} from './testJS.js'
    console.log(obj)
    Say()
    console.log(Pi)
</script>

//testJS.js的内容
export const obj = {
    name:"Outer",
    age : 19,
}
export const Say = function(){
    console.log("Hello!")
}
export const Pi = 3.1415926535

//输出结果
{name: 'Outer', age: 19}
Hello!
3.1415926535

这里需要通过import来导入对应的变量或函数,其格式为

1
import {...list} from '文件路径'

其中...list即要引入的变量或函数的变量列表

而在对应的javascript文件中,我们需要将要某个变量变为public公有,或被称为导出,只需要在变量或函数前加上export关键字即可。

1
2
3
4
5
6
7
8
export const obj = {
    name:"Outer",
    age : 19,
}
export const Say = function(){
    console.log("Hello!")
}
export const Pi = 3.1415926535

3.设置导出默认值

我们可以设置import默认值,这样当导入值未声明时,便会导入默认值

1
2
3
4
5
6
7
8
9
10
11
12
<script type="module">
    import func from './export.js'
    console.log(func(5,6))
</script>

//export.js的内容
export default function sum(a,b){
    return a + b
}

//输出结果
11

我们可以发现,通过import语句,我们将function sum(a,b){return a + b}的值赋值给了func变量,因此我们可以在后边调用它。

注意,只有我们在没有指定调用变量时,才会获取默认值default

但我们可以同时调用这两种import,其之间是不会产生冲突的。

1
2
3
4
5
6
<script type="module">
    import {Say} from './export.js'
    Say()
    import func from './export.js'
    console.log(func(5,6))
</script>

4.IIFE

javascript中存在一种语法,即可以在立即执行函数类封装一定的代码,有点类似于go语言的闭包。这样的语法被称为立即执行的函数表达式,缩写为IIFE

1
2
3
4
5
6
7
8
9
10
const Calc = (function (){
    const func = function(a, b){
        return a+b
    }
    return {
        add: func,
    }
}())

console.log(Calc.add(1,2))

上述代码我们在立即执行函数calc内定义了func函数,并让其返回一个对象,该对象只有一个方法func

这样之后,我们每次调用calc,实际是调用其返回的对象{add:func},通过检索对应的字段来获取立即执行函数内的函数func

三.BOM

BOMBrowser Object Model浏览器对象模型BOM的主要对象即window

window浏览器内置的一个对象,包含着一系列操作浏览器API

关键字 作用
History 用于记录浏览器访问的页面,便于跳转
Location 记录浏览器地址信息
Alert 弹出对话框
confirm 弹出询问框
prompt 弹出输入框

1.关键字

接下来对上述几个关键字进行说明

首先是History关键字:

通过History的对应API,我们可以实现页面的后退前进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button onclick="forward()">前进</button>
<button onclick="back()">后退</button>
<button onclick="go(1)">前进1页</button>
<button onclick="go(-1)">后退1页</button>

<script>
   function forward(){
      history.forward()
  }
  function back(){
    history.back()
   }
  function go(n){
      history.go(n)
  }
</script>

如上述代码所示,history.forward()可以前进一页,history.back()则是后退一页。

history.go()则可以根据传入的参数指定跳转几页。

之后是Location关键字:

URLhttp://outercyrex.github.io:3000/login/user?name=Outer为例。

属性 说明
location.portocol http: 页面使用的协议,通常是http:https:
location.hostname OuterCyrex.github.io 服务器域名
location.port 3000 请求的端口号
location.host outercyrex.github.io:3000 服务器名及端口号
location.origin http://outercyrex.github.io:3000 url的源地址,只读
location.href 完整的url地址 等价于window.location
location.pathname /login/user?name=Outer url中的路径和文件名,不会返回hash和search后面的内容,只有当打开的页面是一个文件时才会生效
location.search ?name=Outer 查询参数

可以对location的参数进行更改来进行页面的跳转。

此外,location还具有两个方法

方法 作用
location.reload() 刷新当前页面
location.replace() 跳转至replace()内的URL页面且不会保存历史记录

2.对话框

如之前的表格所示,常见的对话框有三种,分别是提示框询问框输入框

alert可以实现提示框,其内部是一个字符串,可以进行更改。

1
2
3
4
5
6
7
8
alert("Hello,World!")
//输出结果
localhost:63342显示Hello,World!
    
let name = "Outer"
alert(`${name},Hello,World!`)
//输出结果
localhost:63342显示Outer,Hello,World!

然后是询问框confirm,其会根据用户操作来返回对应的boolean值。

1
2
3
4
let name = "Outer"
console.log(confirm(`你是${name}吗?`))
//点击“是”之后的输出结果
true

最后是输入框prompt,会返回用户输入的值,也可以设置默认值。

1
2
3
4
5
6
7
8
let name = "Outer"
console.log(prompt(`你是${name}吗?`))

//输入"是的,我是"后的输出结果
是的我是

//设置默认值
prompt(`你是${name}吗?`,"感觉你是")

若点击取消则会返回null值。

注意:confirmprompt都会阻塞代码,即若用户不进行操作,接下来的代码就不会运行。

3.计时器

javascript中有两种计时器,分别是setTimeoutsetInterval

计时器  
setTimeout 倒计时执行函数,单位是毫秒,返回值为当前计时器的序号,且可以用clearTimeout来关闭
setInterval 间隔多少时间来执行函数,会一直执行,需要clearInterval来关闭。

分别介绍两个计时器:

首先是setTimeout,其单位为毫秒,且1秒 = 1000毫秒

其语法为setTimeout(function,time)function为要执行的函数,time为时间间隔。

1
2
3
4
5
6
function handler(){
    console.log("中间件已执行!")
}
setTimeout(handler,3000)
//3秒后的输出结果
中间件已执行

setTimeout存在返回值,其返回值为当前计时器是文件内第几个。

1
2
3
4
5
6
7
8
function handler(){
    console.log("中间件已执行!")
}
setTimeout(handler,3000)
console.log(setTimeout(handler,5000))
//输出结果
2
中间件已执行

注意setTimeout的返回值是立刻返回的,而并不是等待计时器倒计时结束才返回的。

clearTimeout的 传入的参数应为一个number类型,也即setTimeout的返回值,

1
2
3
4
5
function handler(){
    console.log("中间件已执行!")
}
setTimeout(handler,3000)
clearTimeout(1)

由于设置了clearTimeout,计时器关闭,无内容输出。

此后是setInterval,其语法为setInterval(function,time),其中function为要调用的函数,time为间隔的时间。

1
2
3
4
5
6
7
8
9
10
11
12
setInterval(()=>{
    let timer = new Date().toLocaleString()
    console.log(timer)
},3000)
//输出结果
2024/7/27 10:16:14
2024/7/27 10:16:17
2024/7/27 10:16:20
2024/7/27 10:16:23
2024/7/27 10:16:26
2024/7/27 10:16:29
......

setInterval的返回值也是其在当前文件中是第几个计时器,注意,此处的计时器包括setTimeoutsetInterval

通过clearInterval来关闭计算器,若不关闭则会一直进行下去。

1
2
3
4
5
6
7
8
9
10
11
let countdown = 3
setInterval(()=>{
    let timer = new Date().toLocaleString()
    console.log(timer)
    countdown--;
    if(countdown === 0)clearInterval(1)
},1000)
//输出结果
2024/7/27 10:21:07
2024/7/27 10:21:08
2024/7/27 10:21:09

4.防抖和节流

在项目开发中,要考虑到可能存在的由于用户多次点击导致某个函数重复执行的事件。

这里便需要两个函数,分别是debouncethrottle

防抖,即若用户在短时间内重复执行某个函数时,只计入最后一次点击的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<button onclick="handler('Outer')">按钮</button>

const out = function(name){
    console.log(name)
}
function debounce(fn,delay){
    let timer = null
    return function(){
        clearTimeout(timer)
        timer = setTimeout(()=>{
            fn.apply(this,arguments)
        },delay)
    }
}
let handler = debounce(out,1000)

对上述的debounce函数进行分析。

其传入值为要传入的函数fn和时间间隔delay,之后我们设置了timer = null,如果此前该函数已被调用,则timer的值会更改为setTimeout的返回值,这样我们便可以通过clearTimeout直接关闭其进程。(因为clearTimeout是全局性的,即使他在程序中位于setTimeout之前)

fn.apply(this,arguments)中的this指代的是fn本身,而arguments则是javascript的关键字,代表传入该函数的参数列表。在一起则相当于fn(...arguments),等于调用了一遍函数。

节流,即在设定的一段时间内只能进行一次交互,交互一次后必须等待该间隔才能再次进行交互。如果重复交互则会在间隔完成后立即执行。

下面先设计一个简单的节流函数,其实现了在间隔内重复交互无效的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const out = function(name){
    console.log(name)
}
function throttle(fn,delay){
    let st = new Date().getTime()
    return function(){
        let time = new Date().getTime()
        if(time - st > delay){
            out.apply(this,arguments)
            st = new Date().getTime()
        }
    }
}
let handler = throttle(out,1000)

为了完善刚才的效果,我们需要加入setTimeout来实现交互队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function throttle(fn,delay){
    let st = new Date().getTime()
    let timer = null
    return function(){
        let time = new Date().getTime()
        clearTimeout(timer)
        if(time - st > delay){
            out.apply(this,arguments)
            st = new Date().getTime()
        }else{
            timer = setTimeout(()=>{
                out.apply(this,arguments)
            },delay - (time - st))
        }
    }
}

其思路很简单,如果我们在间隔时间内重复交互,会进入else选择项,其会设置将剩余的delay间隔进行完成后再交互。如果过多此的重复交互则会触发clearTimeout将多余的交互清除。

5.滚动

window内有两个关于滚动的参数,分别是ScrollXScrollY

1
2
console.log(window.scrollY)
console.log(window.scrollX)

其分别对应了水平滚动距离垂直滚动距离。且由于是window的参数,因此只对body有效。

对于滚动的操作,存在相对滚动绝对滚动相对滚动的参照是当前滚动条的位置,而绝对滚动的参照是整个body

语句 参照
scrollBy 相对滚动,参照的是当前的滚动条位置
scrollTo 绝对滚动,参照的是body

其参数分别对应的是x轴y轴

1
2
window.scrollTo(0,1000)
window.scrollBy(0,1000)

更重要的是,二者内部均可以传入对象

1
window.scrollTo({top: 1200, behavior: "smooth"})

如上述代码传入了一个对象,实现了向下滚动1000px,且是平滑滚动平滑滚动滚动添加了过渡,让其更加自然,在实际开发中经常使用。


在页面中有三个很重要的概念:屏幕、窗口和视口

页面内容 作用
屏幕 整个电脑显示的屏幕
窗口 浏览器的操作页面,包括标签页和其他内容以及视口
视口 网站页面内部显示的内容,且不包括滚动条
1
2
3
4
5
6
7
console.log("视口",innerWidth,innerHeight)
console.log("窗口",outerWidth,outerHeight)
console.log("屏幕",screen.width,screen.height)
//输出结果
视口 849 903
窗口 1420 1032
屏幕 1920 1080

屏幕值一般是固定的。窗口视口会根据实际大小而变化。


6.浏览器信息

通过navigator字段,我们可以获取当前用户的浏览器信息,以便根据用户环境更改页面内容。

属性 描述
navigator.userAgent 获取浏览器的整体信息
navigator.appName 获取浏览器名称
navigator.appVersion 获取浏览器的版本号
navigator.platform 获取当前计算机的操作系统
1
2
3
4
5
6
7
8
9
10
11
12
console.log(navigator.userAgent)
console.log(navigator.appName)
console.log(navigator.appVersion)
console.log(navigator.platform)
//输出结果
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36

Netscape

5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36

Win32