JavaScript语法相关知识汇总(一)

前言

学习JavaScript这一篇就够了_轻松的小希的博客-CSDN博客

         JavaScript(简称“JS”) 是一种具有函数优先轻量级,解释型或即时编译型的脚本语言。(执行网页的动作) ,基于对象和事件驱动并具有相对安全性,JavaScript 是属于 web 的语言,JavaScript 被设计为向 HTML 页面增加交互性。JavaScript和java没有任何关系。

第一章、JavaScript简介

一、JavaScript的起源

        JavaScript诞生于1995年,它的出现主要是用于处理网页中的前端验证。所谓的前端验证,就是指检查用户输入的内容是否符合一定的规则。比如:用户名的长度,密码的长度,邮箱的格式等。但是,有的同学可能会有疑问,这些验证,后端不也可以进行验证吗?确实,后端程序的确可以进行这些验证,但你要清楚,在1995年那个年代,网速是非常慢的,向后端发送一个请求,浏览器很久才能得到响应,那这无疑是一种非常不好的用户体验。

        为了解决前端验证的问题,当时的浏览器巨头NetScape(网景)公司就开发出一种脚本语言,起初命名为LiveScript,后来由于SUN公司的介入更名为了JavaScript。但是你要清楚,Java和JavaScript是没有什么关系的,只不过当时Java非常流行,为了蹭热度,才将LiveScript更名为JavaScript,它们的关系就像雷锋和雷峰塔的关系一样,没啥关系。

        但是,浏览器开发商不止网景一家,还有一个大家都知道的公司,微软公司,它们的主打产品是IE(Internet Explorer)浏览器,当网景公司的Netscape Navigator浏览器推出JavaScript语言时,微软就急了啊,好家伙,人网景都推出了专门用于前端验证的语言,不仅大大减少了后端程序的压力,还提高了用户的体验。我微软这么大的公司不也得整一个,在1996年,微软公司在其最新的IE3浏览器中引入了自己对JavaScript的实现JScript

        于是在市面上存在两个版本的JavaScript,一个网景公司的JavaScript和微软的JScript,虽然当时浏览器的巨头是网景,但是网景的浏览器是收费的,虽然微软的IE浏览器在全球的市场份额远远不及网景,但是微软的拳头产品是Windows操作系统,每一个操作系统都自带一个IE浏览器并且免费,那么,未来的发展大家可能也想到了,网景让微软给干倒闭了,1998年11月网景被美国在线(AOL)收购

        老大哥就是老大哥,为了抢先获得规则制定权,网景最先将JavaScript作为草案提交给欧洲计算机制造商协会,也就是ECMA组织,希望能将JavaScript做成行业标准,最终在网景、SUN以及微软等公司的参与下,由一众程序员和相关组织人员组成的第39技术委员会也就是TC39发布了ECMA-262标准,这个标准定义了名为ECMAScript的全新脚本语言,为啥又来了个ECMAScript?

        因为Java是SUN的商标,SUN授权了NetScape可以叫JavaScript,但是ECMA没有SUN的授权就不能叫JavaScript,哪怕NetScape成员特别希望ECMA把它叫做JavaScript,但是ECMA也有成员并不希望这个标准就叫JavaScript,总之经过几轮磋商和博弈,ECMAScript这个名字就定下来。

我们可以简单看一下历史事件发展表:

二、JavaScript的组成

        ECMAScript是一个标准,而这个标准需要由各个厂商去实现,不同的浏览器厂商对该标准会有不同的实现。

        我们已经知道ECMAScript是JavaScript标准,所以一般情况下这两个词我们认为是一个意思。但是实际上JavaScript的含义却要更大一些。一个完整的JavaScript实现应该由以下三个部分构成: 

三、JavaScript的特点

1、脚本语言(解释性语言)

        JavaScript是一门解释型语言,所谓解释型值语言是指不需要被编译为机器码在执行,而是直接执行。由于少了编译这一步骤,所以解释型语言开发起来尤为轻松,但是解释型语言运行较慢也是它的劣势。不过解释型语言中使用了JIT技术,使得运行速度得以改善。

2、动态语言

        JavaScript是一门动态语言,所谓的动态语言可以暂时理解为在语言中的一切内容都是不确定的。比如一个变量,这一时刻是个整型,下一时刻可能会变成字符串了。当然这个问题我们以后再谈。不过在补充一句动态语言相比静态语言性能上要差一些,不过由于JavaScript中应用的JIT技术,所以JavaScript可能是运行速度最快的动态语言了

3、类似与C和java的语法结构

        JavaScript的语法结构与C和Java很像,向for、if、while等语句和Java的基本上是一模一样的。所以有过C和Java基础的同学学习起来会轻松很多。不过JavaScript和与Java的关系也仅仅是看起来像而已

4、基于原型的面向对象

JavaScript是一门面向对象的语言。

Java也是一门面向对象的语言,但是与Java不同JavaScript是基于原型的面向对象。

5、严格区分大小写

        JavaScript是严格区分大小写的,也就是abc和Abc会被解析器认为是两个不同的东西。

6、单线程语言

JavaScript 是一门单线程的编程语言。

        在 JavaScript 中,单线程意味着只有一个执行线程来处理代码的执行。这是由 JavaScript 的设计决策所决定的,主要是为了简化语言的复杂性和提供更好的安全性。

        在单线程的环境下,JavaScript 代码按照顺序执行,一次只能执行一个任务。这意味着如果存在一个长时间运行的任务或者阻塞操作,将会导致整个线程被阻塞,其他任务无法执行,造成页面冻结或者不响应。

        然而,尽管 JavaScript 是单线程的,但它支持异步编程模型。通过异步编程,JavaScript 可以利用事件循环机制来处理异步任务,使得在等待异步任务完成时,能够继续处理其他任务,从而提高效率和响应性。

        在浏览器环境中,JavaScript 运行在浏览器的主线程中,主要用于处理用户交互、DOM 操作、网络请求等任务。同时,浏览器提供了一些 Web API(例如定时器、AJAX、Fetch、Web Workers 等),这些 API 可以创建额外的线程来处理一些耗时的任务,以避免阻塞主线程,提高响应性和性能。

        总结: JavaScript 是一门单线程的编程语言,意味着它只有一个执行线程来处理代码的执行。尽管如此,JavaScript 支持异步编程模型,通过事件循环机制处理异步任务,以提高效率和响应性。在浏览器环境中,JavaScript 运行在主线程中,可以利用 Web API 创建额外的线程来处理一些耗时任务。

7、既支持同步编程模型又支持异步编程模型

JavaScript(JS)是一门既支持同步编程又支持异步编程的编程语言。

        同步编程: 在同步编程中,代码的执行是按照顺序进行的,每个操作都会等待前一个操作完成后再执行下一个操作。当遇到一个耗时的操作时,整个程序的执行会被阻塞,直到该操作完成。这种编程方式适用于简单的任务和顺序执行的操作。

        异步编程: 在异步编程中,代码的执行不是按照顺序进行的,而是通过回调函数、Promise、async/await 等方式来处理异步操作。异步操作会在后台进行,不会阻塞程序的执行,允许同时执行其他操作。当异步操作完成后,会通过回调函数或 Promise 的处理函数来处理结果。这种编程方式适用于涉及网络请求、文件操作、定时任务等需要等待的操作。

        JavaScript 在浏览器中广泛用于前端开发,而前端开发中常涉及到用户交互、网络请求等异步操作,因此 JavaScript 非常适合异步编程。JavaScript 提供了一系列的异步编程机制,如回调函数、Promise、async/await、事件处理等,使开发者能够处理异步任务,并在任务完成后执行相应的操作。

        总结: JavaScript 同时支持同步编程和异步编程。同步编程按照顺序执行,而异步编程通过回调函数、Promise、async/await 等方式处理异步操作,允许并行执行其他操作,适用于处理涉及等待的任务和操作

四、JavaScript执行原理

JavaScript 是一种解释性语言,它的执行过程如下:

1、预编译:

        JavaScript 代码在执行之前,首先会经过语法分析器的处理,将代码转换成抽象语法树(AST),检查代码是否符合语法规范。如果代码存在语法错误,则会抛出语法错误异常。

2、编译

        在 JavaScript 代码执行之前,会进行预编译,将变量和函数声明提升到当前作用域的顶部,这个过程叫做变量提升和函数提升。变量提升会将变量的声明提升到作用域的顶部,但是变量的赋值仍然保留在原来的位置。函数提升会将函数的声明提升到作用域的顶部,这意味着在函数定义之前就可以调用函数。

3、执行代码

        JavaScript 代码在执行过程中,是逐行解释执行的。在执行每一行代码时,都会检查变量的作用域链,以确定变量的值。当遇到函数调用时,会将函数调用放入调用栈(Call Stack)中,然后执行函数内部的代码,执行完毕后将函数从调用栈中弹出。

4、垃圾回收

        在 JavaScript 中,内存管理是自动的,当变量不再被引用时,JavaScript 引擎会将其标记为待回收的垃圾对象。当内存不足时,JavaScript 引擎会启动垃圾回收机制,回收不再使用的内存,释放给操作系统。

        总体来说,JavaScript 的执行过程是比较复杂的,需要考虑到作用域、变量提升、调用栈、垃圾回收等多个方面。了解这些原理可以帮助我们更好地理解 JavaScript 的运行机制,更好地进行 JavaScript 编程。

第二章、JavaScript基本用法

一、在html标签中套入JS

//demo.html
<body>
    <button type="button" onclick="javascript:alert('hello javascript!')">点击这里</button>
</body>

二、标签引用

在HTML中在script标签中就可以编写JavaScript代码,以下是一个简单演示。

<body>
    <!-- <button type="button" onclick="javascript:alert('hello javascript!')">点击这里</button> -->
    <script>
        alert("hello!");
    </script>
</body>

三、文件引用

使用src属性

main.js

alert("hello!");

demo.html

<body>
    <script src="main.js"></script>
</body>

第三章、JavaScript的输出

一、页面输出

<body>
    <script>
        document.write("hello");
    </script>
</body>

二、控制台输出

注意:页面按F12弹出控制台

<body>
    <script>
        console.log("输出一条日志"); //最常用
        console.info("输出一条信息");
        console.warn("输出一条警告");
        console.error("输出一条错误");
    </script>
</body>

输出级别不一样,在控制台的显示也不一样

三、弹出窗口输出

<script>
    alert("Hello,World!");
</script>

四、innerHTML属性输出

<body>
    <div id="app"></div>
    <script>
        document.getElementById("app").innerHTML="hello"
    </script>
</body>

第四章、JavaScript的注释

        注释中的内容不会被解析器解析执行,但是会在源码中显示,我们一般会使用注释对程序中的内容进行解释。

JS中的注释和Java的的一致,分为两种:

  • 单行注释:// 注释内容
  • 多行注释:/* 注释内容 */

一、单行注释  //

<script>
    // 这是注释内容
    console.log("Hello,World!");
</script>

二、多行注释  <!--注释的内容-->

<body>
    <!-- <script>
        alert("Hello,World!");
    </script> -->
</body>

三、相关快捷键

vs里快捷键: ctrl+/

格式化代码快捷键:shift+alt+f

第五章、用法实例

一、将JS写在上面,且通过html标签定义方法

要写为方法,后面加括号

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jzq</title>
    //将代码写在上面
    <script>
        function myClick(){
            alert("hello!");
        }
    </script>
</head>
<body>
    <button onclick="myClick()">点击</button>
</body>

二、将代码写在上面,且通过JS查找对应标签元素定义方法

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jzq</title>
    <!-- 将代码写在上面 -->
    <script>
        window.onload=function(){
            document.getElementById("btn").onclick=function(){
                alert("hello!");
            }
        }
    </script>
</head>
<body>
    <button id="btn">点击</button>
</body>

三、将代码写在下方

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jzq</title>

</head>
<body>
    <button id="btn">点击</button>
    <button onclick="myClick()">点击2</button>
    <script>
        document.getElementById("btn").onclick=function(){
            alert("hello!");
        };
        function myClick(){
            alert("hello!");
        };
    </script>
</body>

四、语法解析

第六章、代码块和语句

一、语句

        前边我所说表达式和运算符等内容可以理解成是我们一 门语言中的单词,短语。而语句(statement)就是我们这个语言中一句一句完 整的话了。语句是一个程序的基本位,JavaScript的程序就是由一条一条语句构成的,每一条语句使用;结尾

        JavaScript中的语句默认是由上至下顺序执行的,但是我们也可以通过一些流程控制语句来控制语句的执行顺序。

二、代码块

代码块是在大括号 {} 中所写的语句,以此将多条语句的集合视为一条语句来使用

        我们一般使用代码块将需要一起执行的语句进行分组,需要注意的是,代码块结尾不需要加 分号。

第七章、常量和变量

变量与常量_变量和常量_长白听书人的博客-CSDN博客

一、常量(字面量)

字面值就是数据本身,常量就是声明赋值后固定不变的变量

        常量实际上就是一些固定的值,比如:1、2 、3、true、false、null、NaN、“hello”,字面量都是不可以改变的,由于常量不是很方便使用,所以在JavaScript中很少直接使用字面量,使用的而是变量。

二、变量

        变量的作用是给某一个值或对象标注名称。比如我们的程序中有一个值123,这个值我们是需要反复使用的,这个时候 我们最好将123这个值赋值给一个变量,然后通过变量去使用123这个值。

1、变量的命名规则

变量是用于存储信息的"容器"。命名规则如下:

  • 变量名区分大小写,允许包含数字、字母、下划线_、美元符号$,但不能以数字开头,即第一个字符不能为数字

  • 变量名中不能出现汉字、空格、连字符(-)、点(.)号等特殊字符。

  • 变量名不能是 JavaScript 中的关键字、保留字

  • 当变量名中包含多个英文单词时,推荐使用驼峰命名法(大驼峰:每个单词首字母大写,例如 FileType、DataArr;小驼峰:第一个单词首字母小写后面的单词首字母大写,例如 fileType、dataArr)

2、标识符

标识符就是指开发人员为变量、属性、函数、参数取的名字,标识符不能是关键字或保留字。

关键字:是指 JS本身已经使用了的字,不能再用它们充当变量名、方法名。

 保留字:实际上就是预留的“关键字”,意思是现在虽然还不是关键字,但是未来可能会成为关键字,同样不能使用它们当变量名或方法名。

 其它不建议使用的标识符

三、变量的声明

变量的声明: 使用var/let关键字声明一个变量。

var a;

变量的赋值: 使用=为变量赋值。

a = 123;

声明和赋值同时进行

var a = 123;

 

1、var 、let 、const 关键字

letconst具有块级作用域var不存在块级作用域,可以跨块访问, 不能跨函数访问

这里只有var声明的变量才能打印出来,因为var声明的是全局变量

可以将{}理解成为块级作用域

当var在script标签里面,在函数外面声明时,才是真正意义上的在全局作用域中,都可以使用

当var在函数function中声明时,只是在函数作用域中,函数外不可使用;

const关键字:

  • 用于声明常量,声明后不可以在修改
  • 不允许重复声明
  • 块儿级作用域
  • 声明必须赋初始值
  • 值不允许修改
  • 标识符一般为大写

let关键字:

  • 不允许重复声明
  • 块儿级作用域
  • 不存在变量提升
  • 不影响作用域链

 var存在变量提升,let和const不存在变量提升(变量能在声明之前使用,就是变量提升

用var关键字声明的变量会在所有代码执行之前执行,但不会赋值

 var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

在JavaScript中,var,let和const都是用来声明变量的关键字。它们之间有以下区别和联系:

  1. 作用域:var声明的变量作用域一般是函数级别,而let和const声明的变量作用域一般是块级别。这意味着,在函数内部使用var声明的变量在函数内部可以被访问,而在函数外部是不可见的。在块中使用let和const声明的变量则只能在它们被定义的块级作用域中使用。

  2. 变量提升:var声明的变量会在代码解析时进行变量提升,这意味着它们可以在声明前使用。而let和const声明的变量不会进行变量提升,如果在声明之前使用它们,会抛出ReferenceError。

  3. 重复声明var声明的变量可以被重复声明,重复声明后要求变量的数据类型必须一致,而let和const声明的变量不允许重复声明。

  4. 可变性:let声明的变量和const声明的变量都是可变的,但是const声明的变量在声明时必须进行初始化,初始化后就无法更改了。

  5. 全局变量:在全局作用域中声明的var变量会成为全局对象的属性,而let和const声明的变量不会成为全局对象的属性。

  6. 在全局作用域中声明变量,不管是使用 varlet 还是 const,都会将变量声明为全局变量,也就是在任何地方都可以访问该变量。

        总的来说,var、let和const在变量作用域、变量提升、重复声明、可变性和全局变量等方面有不同的行为和限制。在选择使用哪个关键字时,需要根据具体的需求和场景进行考虑。

        总结:在JavaScript中,一般来说使用let来替换var,减少污染全局bianl,声明对象类型使用 const,非对象类型声明选择 let

使用 var 关键字。

function myFunction() {
  var x = 10;
  console.log(x); // 输出 10
}
myFunction();
console.log(x); // 报错,x 未定义

        在这个例子中,变量 x 是使用 var 关键字在 myFunction() 函数中声明的。由于 x 是在函数作用域中声明的,它只能在函数内部访问。

        块级作用域是指在代码块内部声明的变量只能在该代码块内部访问。这种变量声明方式使用 letconst 关键字。

if (true) {
  let x = 10;
  console.log(x); // 输出 10
}
console.log(x); // 报错,x 未定义

        在这个例子中,变量 x 是使用 let 关键字在 if 代码块中声明的。由于 x 是在代码块中声明的,它只能在该代码块内部访问。

   const 关键字与 let 关键字类似,但是声明的变量是常量,即不能被重新赋值。在使用 const 声明变量时,必须同时初始化变量的值。

        总之,使用 var 声明的变量作用域是函数级别,而使用 letconst 声明的变量作用域是块级别。因此,我们应该根据需要选择合适的变量声明方式来确保代码的正确性和可读性。

2、声明提前

  • 变量的声明提前使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前
  • 函数的声明提前:使用函数声明形式创建的函数 function 函数名(){} ,它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数。使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用

四、变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式

1、数组的解构赋值:

//数组的解构赋值
const arr = ["张学友", "刘德华", "黎明", "郭富城"];
let [zhang, liu, li, guo] = arr;
console.log(zhang);
console.log(liu);
console.log(li);
console.log(guo);

2、简单对象的解构赋值:

//对象的解构赋值
const lin = {
    name: "林志颖",
    tags: ["车手", "歌手", "小旋风", "演员"]
};
let {name, tags} = lin;
console.log(name);
console.log(tags);

3、复杂对象的解构赋值:

//复杂对象的解构赋值
let wangfei = {
    name: "王菲",
    age: 18,
    songs: ["红豆", "流年", "暧昧"],
    history: [
        {name: "窦唯"},
        {name: "李亚鹏"},
        {name: "谢霆锋"}
    ]
};
let {name, age, songs: [one, two, three], history: [first, second, third]} = wangfei;
console.log(name);
console.log(age);
console.log(one);
console.log(two);
console.log(three);
console.log(first);
console.log(second);
console.log(third);

五、作用域

作用域详解

作用域指一个变量的作用的范围,在JS中一共有四种作用域:

  • 全局作用域 Global Scope
  • 模块作用域(Module Scope)
  • 函数作用域(局部作用域)Function Scope
  • 块级作用域(Block Scope)

        在 JavaScript 中,作用域的大小(即变量的可访问范围)由其声明方式和声明位置决定。一般来说,作用域从小到大排列如下:

  1. 块级作用域:使用 letconst 声明的变量只能在其声明的块级作用域内访问。这意味着它们对于代码块(如 if、for、while 等)中定义的变量可见,但对于包含该块的函数或文件中定义的变量不可见。

  2. 函数作用域:使用 var 声明的变量在函数内部声明,但在函数外部无法访问。这也被称为函数作用域。

  3. 模块作用域:使用模块化方式组织代码时,每个模块都具有自己的作用域。模块中声明的变量只能在该模块内访问,不会影响其他模块或全局命名空间。

  4. 全局作用域:未使用任何关键字声明的变量将被视为全局变量,并且可以在代码中的任何位置访问它们。全局变量的作用域是整个程序。

        综上所述,作用域的大小从小到大为:块级作用域 < 函数作用域 < 模块作用域 < 全局作用域。

        变量和函数的作用域和 声明的位置 和 声明的关键字 有关,var,let,const分别有自己的默认作用域,分别使用时和声明位置的作用域大小做比较:

  • 声明位置处作用域 > 所用关键字默认作用域 :以声明位置处作用域为准
  • 声明位置处作用域 < 所用关键字默认作用域 :以所用关键字默认作用域为准
  • 声明位置处作用域:若是非函数作用域和块级作用域,
    • 未采用模块作用域语法,则为全局作用域(污染全局)
    • 采用模块作用域语法,则为模块作用域(不污染全局)

1、全局作用域(包级别)

  • 直接编写在script标签中的JavaScript代码,都在全局作用域
  • 全局作用域在页面打开时创建,在页面关闭时销毁
  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
  • 在全局作用域中:
    • 创建的变量都会作为window对象的属性保存
    • 创建的函数都会作为window对象的方法保存
  • 全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到

2、模块作用域(文件级别)

        在ES6中引入了模块化,模块中定义的变量具有模块作用域,即只能在该模块内部被访问

        在 JavaScript 中,模块作用域是指模块内部的变量、函数和类等声明只在该模块内部有效,而不会泄漏到全局作用域或其他模块作用域中。这样可以避免命名冲突和变量污染等问题,提高代码的可维护性和可重用性。

        在 ES6 之前,JavaScript 并没有原生支持模块化的语法,开发者需要使用命名空间或者立即执行函数等方式来模拟模块作用域。而在 ES6 中,JavaScript 引入了模块化的语法,可以使用 importexport 关键字来定义和导入模块。

3、函数作用域(函数级别)

  • 只能在函数内部访问
  • 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
  • 每调用一次函数就会创建一个新的函数作用域,它们之间是互相独立的
  • 在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
  • 在函数中要访问全局变量可以使用window对象
  • 作用域链:当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错ReferenceError

4、块级作用域(代码块级别)

        在块级作用域内定义的变量,只能在该块级作用域内被访问。块级作用域包括在函数内部使用{}括起来的代码块和在全局作用域使用{}括起来的代码块(如if语句、for循环等)。

全局作用域和模块作用域的区别和联系

全局作用域和模块作用域是JavaScript中两种不同的作用域。它们的区别和联系如下:

  1. 区别:
  • 全局作用域是整个程序中可见的最大范围,包括程序中定义的所有函数和变量。在全局作用域中定义的变量可以在程序的任何地方被访问。

  • 模块作用域是ES6中引入的概念,用于解决全局命名冲突和代码复用问题。每个模块都有自己的作用域,模块中定义的变量只能在该模块内部被访问。模块作用域可以通过ES6的exportimport关键字来暴露和引入变量和函数。

  1. 联系:
  • 全局作用域和模块作用域都可以定义全局变量,但是全局变量在全局作用域中定义时容易造成命名冲突和不可预测的行为,而模块作用域可以避免这些问题。

  • 全局作用域和模块作用域都可以定义函数,但是在模块中定义的函数只能在该模块内部被访问,可以避免函数命名冲突的问题。

  • 全局作用域和模块作用域都可以使用闭包来隐藏变量和函数,保护代码的私密性和安全性。

        总的来说,全局作用域和模块作用域在JavaScript中具有不同的作用和应用场景,开发者需要根据具体的需求和场景来选择使用哪种作用域。

  1. 全局作用域的例子:
var count = 0; // 在全局作用域中定义一个计数器变量

function incrementCount() {
  count++; // 在函数中对计数器变量进行自增操作
}

incrementCount(); // 调用函数自增计数器变量
console.log(count); // 输出计数器变量的值

在上面的例子中,count变量在全局作用域中定义,可以在函数中和全局范围内进行访问和修改。

  1. 模块作用域的例子:
// module.js
let message = "Hello World!"; // 在模块作用域中定义一个消息变量

export function printMessage() {
  console.log(message); // 在函数中打印消息变量的值
}

// main.js
import { printMessage } from "./module.js"; // 引入模块中的函数

printMessage(); // 调用函数打印消息变量的值

        在上面的例子中,message变量在模块作用域中定义,只能在module.js模块内部被访问。通过ES6的exportimport关键字,将printMessage函数从module.js模块暴露出来,在main.js模块中引入并调用该函数,从而可以访问message变量的值。

5、作用域链

  • 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的。
  • 查找一个变量的查找规则:
    • 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入上一级
    • 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入上一级
    • 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的ReferenceError异常

六、删除变量或对象

语法:delete 变量名或函数名

第八章、数据类型(基于原型的面向对象)

在 JavaScript 中,有基本数据类型和引用数据类型两种不同的数据类型。

一、基本数据类型

JavaScript 的基本数据类型,又称原始数据类型,是由语言内部定义的、不可再分的基础数据类型包括以下七种:

  •     数字类型(Number):表示数字,例如 123、3.14 等。
  •     字符串类型(String):表示字符串,例如 "hello"、'world' 等。
  •     布尔类型(Boolean):表示真或假,例如 true 或 false。
  •     空类型(Null):表示空值,只有一个值 null。
  •     未定义类型(Undefined):表示未定义的值,只有一个值 undefined。
  •     大整数类型(BigInt):表示任意精度的整数,例如 123n等。
  •     符号类型(Symbol):表示唯一的值,通常用于对象的属性名。Symbol类型详解

        基本数据类型的比较是值的比较也就是只要两个变量的值相等,我们就认为这两个变量相等 

只声明变量不初始化默认为String类型

二、引用数据类型

        JavaScript 的引用数据类型,又称对象类型,object类型,是由多个属性和方法组成的复杂数据类型,是一种可以自定义的数据类型。在 JavaScript 中,除了原始类型之外的所有值都是引用数据类型

object类型包括普通对象、数组、日期,正则表达式,函数等,不包括基本数据类型

包括以下几种:

  • 普通对象类型(Object):用于存储键值对(properties)的集合。对象可以包含各种数据类型的属性,包括字符串、数字、布尔、数组和其他对象等
  • 数组类型(Array):表示一组有序的值,可以包含任意类型的值。
  • 函数类型(Function):表示可执行的代码块,通常用于实现特定功能。
  • 日期类型(Date):表示日期和时间。
  • 正则表达式类型(RegExp):表示正则表达式。

        需要注意的是,在 JavaScript 中,变量没有类型。基本数据类型的变量直接存储数据的值,而引用数据类型的变量存储的是对象的引用(也就是内存地址)引用数据类型的变量需要通过 new 关键字创建对象并分配内存空间,才能够使用该变量引用对象。而基本数据类型的变量则不需要创建对象,可以直接使用其值。

三、基本数据类型和可变数据类型的区别和联系

1、可变性分析

基本数据类型全都是不可变的,引用数据类型全都是可变的

        基本数据类型的不可变性指的是,一旦一个基本数据类型的值被赋值,它的值就不能再被改变了。当对一个基本数据类型的值进行操作时,实际上是创建了一个新的值,而原来的值并没有改变。例如,对一个字符串进行操作,会生成一个新的字符串,而不是修改原有的字符串。

        引用数据类型的可变性指的是,一个引用数据类型的值可以被修改,也可以添加或删除属性或元素。这是因为引用数据类型的值实际上是一个指向存储在堆内存中的对象的引用,而不是对象本身。因此,修改对象的属性或元素时,实际上是在修改对象在堆内存中的值,而不是修改对象本身。

        需要注意的是,虽然基本数据类型的值不可变,但是可以使用一些操作符(如加号、等号)将其与其他值组合起来生成新的值,而这些操作并不改变原有的基本数据类型的值。另外,虽然引用数据类型是可变的,但是如果使用 const 关键字定义一个引用数据类型的变量,它所指向的对象不能被重新赋值。

2、存储方式(栈和堆)

JavaScript在运行时数据是保存到栈内存和堆内存当中的。

简单来说栈内存用来保存变量和基本类型,堆内存是用来保存对象。

我们在声明一个变量时,实际上就是在栈内存中创建了一个空间用来保存变量。

如果是基本类型则在栈内存中直接保存,如果是引用类型则会在堆内存中保存,变量中保存的实际上对象在堆内存中的地址。

 值类型:值存储在内存中的栈中

  • 相当于在栈中挖了个坑,命名为该变量名,把对应的值放在坑中

引用类型:值存储在内存中的堆中

  • 相当于在堆中挖了个坑,将该对象的值存放在坑中,并放回一个内存地址编号给栈,然后在栈中又挖了个坑,命名为该对象的变量名,将堆返回的内存地址放在栈的坑中

当我们写了下边这几句代码的时候,栈内存和堆内存的结构如下

var a = 123;
var b = true;
var c = "hello";
var d = {name: 'sunwukong', age: 18};

栈的特点:先进后出,后进先出

引用类型的值是保存在内存中的对象。

当一个变量是一个对象时,实际上变量中保存的并不是对象本身,而是对象的引用。

当从一个变量向另一个变量复制引用类型的值时,会将对象的引用复制到变量中,并不是创建一个新的对象。

这时,两个变量指向的是同一个对象。因此,改变其中一个变量会影响另一个

3、值比较区别点

        原始类型的特点是它们的值是不可变的(immutable),即一旦创建后,其值就无法修改,只能通过赋值操作改变其变量的值。例如,对于字符串类型的变量,可以通过连接操作创建新的字符串,但是原有字符串的值并不会改变。同时,原始类型的值在进行比较时是按照值的比较方式进行的,而非按照引用的比较方式进行的,这也是原始类型和对象类型的一个重要区别。

可以通过以下代码示例来说明原始类型值和对象类型值在比较时的区别:

let a = 10;
let b = 10;

if (a === b) {
  console.log("a 和 b 相等");
}

let obj1 = { name: "John" };
let obj2 = { name: "John" };

if (obj1 === obj2) {
  console.log("obj1 和 obj2 相等");
}

        在这个例子中,我们定义了两个变量 ab,它们都是数字类型的变量,并且它们的值相等。我们使用 === 运算符对它们进行比较,结果返回 true,表示它们的值相等。

        接着,我们定义了两个对象类型的变量 obj1obj2,它们都有一个名为 name 的属性,属性值都是字符串 "John"。我们同样使用 === 运算符对它们进行比较,结果返回 false,表示它们不相等。

        这是因为,在 JavaScript 中,原始类型值的比较是按照值的比较方式进行的,即当两个变量的值相等时,它们被认为是相等的;而对象类型值的比较是按照引用的比较方式进行的,即只有当两个变量引用同一个对象时,它们被认为是相等的。

        因此,在上面的例子中,虽然 obj1obj2 的属性值都相等,但它们引用的是两个不同的对象,因此在进行比较时返回了 false

4、类型区别点

        在 JavaScript 中,引用数据类型又称Object 类型,包括普通对象、数组、日期、正则表达式、函数等,但不包括基本数据类型,比如字符串、数字、布尔值等。

下面是它们的详细解释:

1、普通对象

        普通对象是 JavaScript 中最常见的对象类型,它由一组键值对组成,可以通过点操作符或中括号操作符来访问其中的属性。例如:

let obj = { name: 'Alice', age: 18 };
console.log(obj.name); // 'Alice'
console.log(obj['age']); // 18

2、数组

数组是一种特殊的对象,它由一组有序的值组成,可以通过下标来访问其中的元素。例如:

let arr = [1, 2, 3];
console.log(arr[0]); // 1
console.log(arr.length); // 3

3、日期

日期是 JavaScript 中的一个内置对象,它用于表示时间和日期。例如:

let date = new Date();
console.log(date.getFullYear()); // 当前年份
console.log(date.getMonth() + 1); // 当前月份
console.log(date.getDate()); // 当前日期

4、正则表达式

正则表达式是一种特殊的对象,它用于匹配字符串中的模式。例如:

let regExp = /ab+c/;
console.log(regExp.test('abc')); // true
console.log(regExp.test('ac')); // false

5、函数

函数也是 JavaScript 中的一种对象类型,它可以被调用并执行一些操作。例如:

function add(x, y) {
  return x + y;
}
console.log(add(1, 2)); // 3

基本数据类型(如字符串、数字、布尔值等)虽然不属于 Object 类型,但它们可以通过包装对象的方式转换为 Object 类型。例如:

let str = 'hello';
console.log(str.length); // 5
let num = 123;
console.log(num.toString()); // '123'
let bool = true;
console.log(bool.valueOf()); // true

这些基本数据类型在调用方法或属性时会自动转换为对应的包装对象类型,然后再进行操作。

四、判断数据类型运算符

1、typeof运算符

JavaScript 中的 typeof 运算符用于判断一个值的类型,其语法如下:

使用方式:

typeof 数据

其中 value 是要检测的值,可以是任意类型的值,包括原始类型和对象类型。typeof 运算符返回一个字符串,表示 value 的类型,其可能的取值有以下几种:

  • "undefined":表示值未定义(undefined)。
  • "boolean":表示值是布尔值(truefalse)。
  • "number":表示值是数字(包括整数和浮点数)。
  • "string":表示值是字符串。
  • "bigint":表示值是大整数(ES2020 新增)。
  • "symbol":表示值是符号(ES2015 新增)。
  • "object":表示值是对象或 null
  • "function":表示值是函数对象。

需要注意的是,typeof 运算符有一些特殊的行为:

  • 对于 null 值,typeof null 返回 "object",这是一个历史遗留问题,由于 JavaScript 最初的实现中将 null 值的类型信息存储在对象头中,导致 typeof null 返回了错误的类型。
  • 对于函数对象,typeof 运算符返回 "function",但是函数对象本质上还是一种对象类型,可以拥有属性和方法。
  • 对于未定义的变量或属性,typeof 运算符返回 "undefined"
  • 对于数组类型,typeof 运算符返回 "object",因为数组本质上是一种对象类型。
  • 对于正则表达式类型,typeof 运算符返回 "object",因为正则表达式本质上是一种对象类型。

   typeof 运算符的实现原理是通过检查值的内部类型标记来确定值的类型。对于原始类型的值,JavaScript 引擎会将其类型信息存储在值本身的标记中,而对于对象类型的值,JavaScript 引擎会将其类型信息存储在对象头中,这些类型信息可以通过 JavaScript 引擎的内部 API 访问到。

2、instanceof运算符

instanceof 运算符检查一个对象是否是一个类的实例,返回true或false

语法

对象 instanceof 构造函数

案例

console.log(person1 instanceof Person);

3、Object.prototype.toString.call()

可以检测任意类型的数据,包括基本类型和引用类型

const arr = ['a','b','c'];
console.log(typeof arr); //检测基本类型
console.log(arr instanceof Array); //检测引用类型
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(Symbol('123')));

特殊情况

a = {1:2}; //对象中的属性只能为字符串或Symbol,默认为字符串,可省略引号
console.log(a[1]);  // 访问数值形式的须使用中括号
// a = new Set([1,2]);
console.log(Object.prototype.toString.call(a));

[object String] 表示一个对象的字符串表示形式,其中 "String" 表示对象的类型。

在 JavaScript 中,使用 Object.prototype.toString 方法可以将任意对象转换为字符串表示形式。对于不同类型的对象,其返回的字符串表示形式也会不同。

对于字符串对象,例如使用 new String() 创建的对象,其字符串表示形式为 [object String]。这个表示形式可以告诉我们该对象的类型是 "String"。

示例代码如下

const strObj = new String("Hello");
console.log(Object.prototype.toString.call(strObj));  // 输出: [object String]

需要注意的是,这种表示形式是 JavaScript 内部提供的标准字符串表示方式,用于区分不同类型的对象。它并不是直接可见的字符串值,而是用于类型标识和判断的工具。

如果你想获取字符串对象的实际值,可以通过 valueOf() 方法或将其隐式转换为原始字符串类型。例如:

const strObj = new String("Hello");
console.log(strObj.valueOf());  // 输出: Hello
console.log(String(strObj));    // 输出: Hello

通过 valueOf() 方法或隐式转换,我们可以获取字符串对象的实际字符串值,而不是表示类型的字符串。

第九章、字符串类型 string

String用于表示一个字符序列,即字符串。字符串需要使用 单引号 或 双引号 括起来,JS不区分单引号和双引号

一、包装类:String()

可以将基本数据类型字符串转换为String对象

        在 JavaScript 中,字符串(string)可以被看作是 String 对象的实例。当我们使用字符串字面量创建字符串时,JavaScript 引擎会自动将其转换为对应的 String 对象。这个过程被称为“装箱”(boxing)。

例如,当我们创建一个字符串字面量 "Hello World" 时,JavaScript 引擎会自动将其转换为一个对应的 String 对象。我们可以通过 typeof 操作符来验证:

let str = "Hello World";
console.log(typeof str);  // 输出:string

// 等价于:

let strObj = new String("Hello World");
console.log(typeof strObj);  // 输出:object

        在这个例子中,尽管我们使用的是字符串字面量,但实际上 JavaScript 引擎将其转换为了一个对应的 String 对象。这个对象拥有 String 对象的所有属性和方法,例如 lengthtoUpperCase()

        尽管 JavaScript 引擎会自动进行装箱操作,但在实际开发中,我们通常不建议直接创建 String 对象,而是使用字符串字面量来创建字符串,这样可以提高代码的简洁性和效率。只有在特殊情况下(例如需要使用 String 对象提供的某些方法时),我们才需要显式地创建 String 对象。

二、基本写法

注意:单,双引号里面不能包含自己

三、转义字符

 注意:使用typeof运算符检查字符串时,会返回"string"。

输出斜杠 / ,这两种方式是一样的

console.log('/');
console.log('\/');

四、字符串常用属性

1、constructor属性演示:返回创建字符串对象的原型函数

var str = "Hello,World!";
console.log(str.constructor);

2、length属性演示:可以用来获取字符串的长度

var str = "Hello,World!";
console.log(str.length);

五、字符串常用方法

1、charAt()方法演示:该方法可以根据索引获取指定位置的字符

var str = "Hello,World!";
console.log(str.charAt(1));

2、charCodeAt()方法演示:该方法获取指定位置字符的字符编码(Unicode编码)

var str = "Hello,World!";
console.log(str.charCodeAt(1));

3、concat()方法演示:该方法可以用来连接两个或多个字符串

var str = "Hello,World!";
console.log(str.concat("你好,", "世界!"));

4、indexof()方法演示:返回指定字符索引

        该方法可以检索一个字符串中是否含有指定内容,如果字符串中含有该内容,则会返回其第一次出现的索引,如果没有找到指定的内容,则返回-1,可以指定一个第二个参数,指定开始查找的位置

var str = "Hello,World!";
console.log(str.indexOf("o"));
console.log(str.indexOf("o", 5));

5、lastIndexOf()方法演示:返回指定字符索引

        该方法的用法和indexOf()一样,不同的是indexOf是从前往后找,而lastIndexOf是从后往前找,也可以指定开始查找的位置

var str = "Hello,World!";
console.log(str.lastIndexOf("o"));
console.log(str.lastIndexOf("o", 5));

6、slice()方法演示:可以从字符串中截取指定的内容,不会影响原字符串,而是将截取到内容返回

参数:

  • 第一个参数:开始位置的索引(包括开始位置)
  • 第二个参数:结束位置的索引(不包括结束位置),如果省略第二个参数,则会截取到后边所有的

注意:也可以传递一个负数作为参数,负数的话将会从后边计算

var str = "Hello,World!";
var result = str.slice(1, 4);
console.log(result);
result = str.slice(1);
console.log(result);
result = str.slice(1, -1);
console.log(result);

7、substring()方法演示:可以用来截取一个字符串,它和slice()类似

参数:

  • 第一个参数:开始截取位置的索引(包括开始位置)
  • 第二个参数:结束位置的索引(不包括结束位置),如果省略第二个参数,则会截取到后边所有的

注意:不同的是这个方法不能接受负值作为参数,如果传递了一个负值,则默认使用0,而且它还自动调整参数的位置,如果第二个参数小于第一个,则自动交换

var str = "Hello,World!";
var result = str.substring(1, 4);
console.log(result);
result = str.substring(1);
console.log(result);
result = str.substring(1, -1);
console.log(result);

8、substr()方法演示:该方法用来截取字符串

参数:

  • 第一个参数:截取开始位置的索引
  • 第二个参数:截取的长度
var str = "Hello,World!";
var result = str.substr(6, 6);
console.log(result);

9、split()方法演示:该方法可以将一个字符串拆分为一个数组,需要一个字符串作为参数,将会根据该字符串去拆分数组

var str = "Hello,World!";
var result = str.split(",");
console.log(result);

10、toUpperCase()方法演示:将一个字符串转换为大写并返回

var str = "Hello,World!";
var result = str.toUpperCase();
console.log(result);

11、toLowerCase()方法演示:将一个字符串转换为小写并返回

var str = "Hello,World!";
var result = str.toLowerCase();
console.log(result);

s1 = "hello World"
        console.log(s1.length) //获取字符串长度,空格也算长度
        console.log(s1.charAt(2)) //返回指定索引位置的字符
        console.log(s1.concat("hello")) //连接两个或多个字符串,返回连接后的字符串
        console.log(s1.indexOf("l")) //返回字符串中检索指定字符第一次出现的位置
        console.log(s1.lastIndexOf("l")) //返回字符串中检索指定字符最后一次出现的位置
        console.log(s1.split(" ")) //把字符串分割为子字符串数组,分隔符不在数组内
        console.log(s1.trim()) //移除字符串首尾空白
        console.log(s1.toUpperCase()) //把字符串转换为大写
        console.log(s1.toLowerCase()) //把字符串转换为小写
        console.log(s1.substr(1, 2)) //从起始索引号提取字符串中指定数目的字符
        console.log(s1.substring(1, 4)) //提取字符串中两个指定的索引号之间的字符,左闭右开
        console.log(s1.search(/ll/))  //返回第一个匹配的位置,如果没有找到任何匹配的子串,则返回 -1
        console.log(s1.replace(/ll/, "aa")) //替换与正则表达式匹配的子串,只能替换最前面一个
        console.log(s1.match(/l/g)) // 找到一个或多个正则表达式的匹配,没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null,g表示全局查找

注意:/.../表示JS中的正则表达式

六、模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)Tab键上边的键,标识,特点:

  • 字符串中可以出现换行符
  • 可以使用 ${xxx} 形式输出变量

注意:当遇到字符串与变量拼接的情况使用模板字符串

字符串中可以出现换行符:

//定义字符串
let str = `<ul>
               <li>沈腾</li>
               <li>玛丽</li>
               <li>魏翔</li>
               <li>艾伦</li>
           </ul>`;
console.log(str);

变量拼接:

//变量拼接
let name = '小可爱';
let result = `欢迎${name}访问我的文章`;
console.log(result);

第十章、数字类型  number

number 类型用来表示整数和浮点数,最常用的功能就是用来表示10进制的整数和浮点数。

Number 在 JavaScript 中既是一个类也是一个全局函数。

        作为类,Number 有一些静态属性和静态方法,可以通过 Number.propertyNumber.method() 的方式来访问,例如 Number.MAX_VALUE

        作为函数,Number() 是一个全局函数,可以用于将传递的值转换为数字类型。它可以作为构造函数使用,如 new Number(42),也可以直接调用,如 Number("42")。当直接调用 Number() 函数时,它会返回转换后的数字值。

    

        综合来说,Number 既是一个对象,拥有自己的属性和方法,也是一个函数,用于进行数字的转换和处理。

一、包装类 Number( )

        当我们在基本类型的值上调用属性或方法时,JavaScript 引擎会自动创建临时的包装对象,使我们能够访问对应的属性和方法。一旦操作完成,这些临时的包装对象会被销毁。

        因此,Number 对象并不是从某个类实例化来的,而是在访问基本类型数字时由 JavaScript 引擎自动创建的临时包装对象。

        在 JavaScript 中,存在一些特殊的包装类,如 NumberStringBoolean。这些包装类允许开发者在基本类型(primitive types)上执行对象操作。

Number 包装类是用来处理数字的对象。当我们使用 Number(value) 构造函数时,它会将传递的值转换为一个 Number 对象。这种转换使我们能够在原始数字值上执行对象操作。

1、Number对象

let num = 42;

console.log(typeof num); // 输出: "number"

let numObj = new Number(num);
console.log(typeof numObj); // 输出: "object"

console.log(Number.valueOf()); // 输出: [Function: Number]

        在上述示例中,我们首先创建了一个基本类型的数字 num,并使用 typeof 运算符验证其类型为 "number"。接下来,我们使用 new Number(num) 创建了一个 Number 对象 numObj,并使用 typeof 运算符验证其类型为 "object"。

        需要注意的是,虽然 numObj 是一个对象,但它与基本类型的数字 num 不完全相同。它具有 Number 对象的属性和方法,但在使用时可能会导致性能上的一些损失和额外的内存消耗。

        因此,在大多数情况下,我们更倾向于直接使用基本类型的数字,而不是使用 Number 对象。只有在需要进行特定的数字操作时,才会显式地创建 Number 对象。

2、数字本身对象

 例如,我们可以直接在基本类型的数字上调用方法:

let num = 42;

console.log(num.toFixed(2)); // 输出: "42.00"
console.log(num.toString()); // 输出: "42"

        在这种情况下,JavaScript 会自动将基本类型的数字转换为临时的 Number 对象,以便我们可以调用 Number 包装类的方法。

只能使用变量名来调用方法,直接使用数字会报错

需要注意的是,与基本类型相比,使用包装类创建的对象会占用更多的内存,并且可能导致性能上的一些损失。因此,在大多数情况下,我们更倾向于直接使用基本类型而不是包装类来处理数字值。

3、new Number()对象

 以下是一些使用 Number 包装类的示例:

  1. 创建 Number 对象:
let numObj = new Number(42);
console.log(numObj); // 输出: [Number: 42]

在上述示例中,通过 new Number(42) 创建了一个 Number 对象,该对象包装了数字值 42。

输出的字符串[Number: 42]表示该对象的类型为Number,并包含了值为42的数值

  1. 调用 Number 包装类的方法:
let numObj = new Number(42);

console.log(numObj.valueOf()); // 输出: 42
console.log(numObj.toFixed(2)); // 输出: "42.00"
console.log(numObj.toString()); // 输出: "42"

        在上述示例中,我们可以看到通过 numObj 调用了 Number 包装类的一些方法,如 valueOf()toFixed()toString()。这些方法允许我们在 Number 对象上执行操作,比如获取原始的数值、格式化数值以及将数值转换为字符串。

需要注意的是,JavaScript 中的自动装箱机制(autoboxing)使得我们可以直接在基本类型上使用对象的方法,而不需要显式地创建包装类实例。

二、基本写法

1、有效范围

Number表示的数字大小是有限的,如果超过了这个范围,则会返回 ±Infinity。

  • 最大值:+1.7976931348623157e+308
  • 最小值:-1.7976931348623157e+308
  • 0以上的最小值:5e-324

2、特殊的数字 Infinity  -Infinity  NaN

  • Infinity:正无穷
  • -Infinity:负无穷
  • NaN:非法数字(Not A Number)

3、其它的进制

  • 二进制:0b 开头表示二进制,但是,并不是所有的浏览器都支持
  • 八进制:0o 开头表示八进制
  • 十六进制:0x 开头表示十六进制

注意:使用typeof检查一个Number类型的数据时(包括NaN 和 Infinity),会返回"number"。

let b = 0b1010//二进制
let o = 0o777;//八进制
let d = 100;//十进制
let x = 0xff;//十六进制
console.log(b);
console.log(o);
console.log(d);
console.log(x);

默认都是以十进制输出

4、基本写法

三、Number静态变量(属性)

1、Number.MAX_VALUE: 表示 JavaScript 中最大的正数值

console.log(Number.MAX_VALUE); // 输出: 1.7976931348623157e+308

2、Number.MIN_VALUE: 表示 JavaScript 中最小的正数值(接近于 0)

console.log(Number.MIN_VALUE); // 输出: 5e-324

3、Number.NaN: 表示非数字 (NaN)

console.log(Number.NaN); // 输出: NaN

4、Number.POSITIVE_INFINITY: 表示正无穷大

console.log(Number.POSITIVE_INFINITY); // 输出: Infinity

5、Number.NEGATIVE_INFINITY: 表示负无穷大

console.log(Number.NEGATIVE_INFINITY); // 输出: -Infinity

6、Number.EPSILON 表示的最小精度

Number.EPSILON:它是 JavaScript 表示的最小精度,EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16

function equal(a, b) {
    if (Math.abs(a - b) < Number.EPSILON) {
        return true;
    } else {
        return false;
    }
}
console.log(0.1 + 0.2 === 0.3);
console.log(equal(0.1 + 0.2, 0.3));

四、Number常用静态方法

1、Number.parseInt(): 将字符串解析为整数

Number. 可省略 

let num = Number.parseInt("42");
console.log(num); // 输出: 42

2、Number.parseFloat(): 将字符串解析为浮点数

Number. 可省略 

let num = Number.parseFloat("3.14");
console.log(num); // 输出: 3.14

3、Number.isNaN(): 检查一个值是否为 NaN

Number. 可省略 

console.log(Number.isNaN(42)); // 输出: false
console.log(Number.isNaN(NaN)); // 输出: true

4、Number.isFinite(): 检查一个值是否为有限数

Number. 可省略 

console.log(Number.isFinite(42)); // 输出: true
console.log(Number.isFinite(Infinity)); // 输出: false

5、Number.isInteger(): 检查一个值是否为整数

Number. 不可省略

console.log(Number.isInteger(42)); // 输出: true
console.log(Number.isInteger(3.14)); // 输出: false

五、number包装对象的实例方法

1、number.toFixed(): 格式化一个数值的小数位数

let num = 3.14159;
console.log(num.toFixed(2)); // 输出: "3.14"

2、number.toPrecision(): 格式化一个数值的总位数

let num = 42.12345;
console.log(num.toPrecision(4)); // 输出: "42.12"
num1 = 10.2345
        console.log(Math.floor(num1)) //向下舍入,3.1 变成 3,-1.1 变成 -2
        console.log(Math.ceil(num1)) //向上舍入:3.1 变成 4,-1.1 变成 -1。
        console.log(Math.round(num1)) // 向最近的整数舍入:3.1 变成 3,3.6 变成 4,中间值 3.5 变成 4。
        console.log(Math.trunc(num1)) //移除小数点后的所有内容而没有舍入:3.1 变成 3,-1.1 变成 -1
        console.log(num1.toFixed(3)) // 四舍五入保留3为有效数字,小数点后几位
        console.log(isNaN(num1)) // isNaN ---> is not a number 
        console.log(parseInt('100px')) // 100
        console.log(parseInt('p100px')) //NaN
        console.log(parseFloat('12.5em')) // 12.5
        console.log(parseInt('12.3')) // 12
        console.log(parseFloat('12.3.4'))// 12.3
        console.log(Math.random()) // 返回一个从 0 到 1 的随机数(不包括 1)
        console.log(Math.max(3, 5, -10, 0, 1)) //获取最大值
        console.log(Math.min(3, 5, -10, 0, 1)) //获取最小值 
        console.log(Number("12.2")) //转成数字

六、Number( ) 全局函数

在 JavaScript 中,Number() 是一个内置的全局函数,用于将传递的值转换为数字类型。它可以用于创建基本类型的数字,也可以用于将其他类型的值转换为数字。

以下是一些使用 Number() 函数的示例:

1、将字符串转换为数字:

let num = Number("42");
console.log(num); // 输出: 42

        在上述示例中,通过将字符串 "42" 作为参数传递给 Number() 函数,我们将其转换为数字类型的值。

2、将布尔值转换为数字:

let num = Number(true);
console.log(num); // 输出: 1

在这个示例中,将布尔值 true 作为参数传递给 Number() 函数,它会被转换为对应的数字值。

3、将其他类型的值转换为数字:

let str : string = "3.14";
console.log(typeof str); //输出:string
let num = Number(str);
console.log(num); // 输出: 3.14
console.log(typeof num) //输出:number

let invalidNum = Number("abc");
console.log(invalidNum); // 输出: NaN

        在第一个示例中,字符串 "3.14" 被成功转换为数字类型的值。而在第二个示例中,由于字符串 "abc" 无法转换为有效的数字,Number() 函数返回了特殊的非数字值(NaN)。

        总之,Number() 是一个全局函数,用于将值转换为数字类型。它不是一个类,而是一个用于数字转换的内置函数。

第十一章、布尔类型  Boolean

布尔型也被称为逻辑值类型或者真假值类型。

布尔型只能够取真(true)和假(false)两种数值。除此以外, 其它的值都不被支持。

注意:js里,空字符串;null;undefined;0;NaN 转成布尔类型,都是false 。

var param1 = 0;
var param2 = null;
var param3 = '';
var param4 = NaN;
var param5;
//由此可见,以上基本数据类型转为boolean全为false
if(param1){
    console.log(1);
}else{
    console.log(2); //输出2
}
if(param2){
    console.log(1);
}else{
    console.log(2); //输出2
}
if(param3){
    console.log(1);
}else{
    console.log(2); //输出2
}
if(param4){
    console.log(1);
}else{
    console.log(2); //输出2
}
if(param5){
    console.log(1);
}else{
    console.log(2); //输出2
}

在 JavaScript 中,空字符串 (''""),nullundefined0NaN 被转换为布尔类型时都会变为 false

然而,对于引用数据类型(对象、数组等),转换为布尔类型时会被视为真值(truthy),即转换结果为 true。这意味着无论引用数据类型的值是什么,它们在布尔上下文中都被认为是真值。

以下是一些示例来说明引用数据类型转换为布尔类型的行为:

let obj = {}; // 空对象
let arr = []; // 空数组

console.log(Boolean(obj)); // true
console.log(Boolean(arr)); // true

let func = function() {}; // 空函数

console.log(Boolean(func)); // true

let date = new Date(); // 非空日期对象

console.log(Boolean(date)); // true

在上述示例中,空对象、空数组、空函数、非空日期对象都在布尔上下文中被视为真值,所以它们转换为布尔类型时的结果为 true

因此,空字符串、nullundefined0NaN 被转换为布尔类型时为 false,而引用数据类型转换为布尔类型时为 true

第十二章、Undefined(针对变量)

1、在 JavaScript 中,undefined 是一种特殊的数据类型,表示一个未定义的值或变量。当一个变量被声明但没有赋值时,会赋默认值为 undefined。

2、undefined类型是第一个只有一个值的数据类型,这个特殊的值是 undefined。

一、undefined 的特点

        undefined 表示一个未定义的值或变量。在 JavaScript 中,变量在声明但未被赋值时,其默认值为 undefined,函数中没有返回值时,其返回值为 undefined。

二、undefined 的使用

undefined 可以被用于多种场合,比如:

  • 检查变量是否被定义:可以通过判断一个变量是否为 undefined 来判断它是否被定义过。
  • 初始化变量:可以将变量赋值为 undefined,表示这个变量初始时没有赋值。
  • 检测函数返回值:可以通过判断函数的返回值是否为 undefined 来判断函数是否正常执行并返回有效值。

三、undefined 的注意事项

在使用 undefined 时,需要注意以下几点:

  • 变量未声明和变量值为 undefined 是不同的情况。如果一个变量没有声明,那么它在使用时会抛出一个 ReferenceError 错误;如果一个变量值为 undefined,则表示这个变量已经声明但未赋值。
  • 对于使用 typeof 运算符检查 undefined 类型的变量,会返回 "undefined"
  • 在使用 undefined 时,需要注意与 null 的区别。null 表示一个空对象引用,而 undefined 表示一个未定义的变量或对象属性。

        总的来说,undefined 是 JavaScript 中表示未定义值或变量的特殊类型。在使用时,需要注意与 null 的区别,以及与未声明变量的区别。可以使用 typeof 运算符来检查一个变量的类型是否为 undefined。

在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined。

 注意:使用typeof对声明但为赋值的变量,会返回“undefined”。

第十三章、null(针对实例对象的引用)

1、在 JavaScript 中,null 是一种特殊的数据类型,表示一个空对象引用中的值。当一个变量被赋值为 null 时,它表示该变量不指向任何对象。

2、Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null。

3、undefined值实际上是由null值衍生出来的,所以如果比较undefined和null是否相等,会返回true。

一、null 的特点

        null 代表一个空对象引用,它是 JavaScript 中原始值的一种。null 与 undefined 有些相似,但有一个重要的区别:null 表示一个被赋值为空的对象引用,而 undefined 表示一个未定义的变量或对象属性。

二、null 的使用

null 可以被用于多种场合,比如:

  • 将对象清空:可以将一个对象的属性设置为 null,表示这个属性不再指向任何对象。
  • 初始化变量:可以将变量赋值为 null,表示这个变量初始时不指向任何对象。
  • 检测对象是否存在:可以通过判断一个对象是否为 null 来判断它是否存在。

三、null 的注意事项

在使用 null 时,需要注意以下几点:

  • 对于使用 typeof 运算符检查 null 类型的变量,会返回 "object",这是一个历史遗留问题。因此,如果需要检查一个变量是否为 null,应该使用严格相等运算符(===)
  • null 不等于任何其他的值,包括 undefined、0、空字符串等。例如,null === undefined 返回 false。
  • 当通过调用函数返回 null 时,表示函数执行正常,但没有返回任何有效值。因此,不应该将返回 null 作为判断函数执行是否成功的依据。

        总的来说,null 是 JavaScript 中表示空对象引用的特殊值,通常用于初始化变量或清空对象属性。在使用时,需要注意 null 与 undefined 的区别,并使用严格相等运算符进行比较

注意:从语义上看null表示的是一个空的对象引用,所以使用typeof检查null会返回一个Object。而null是一个对象引用,只是这个对象在堆里没有开辟空间

四、object,null,undefined三个之间的区别和联系

        在 JavaScript 中,Object、null 和 undefined 是三个不同的概念,它们之间有区别和联系,下面是它们之间的详细解释:

1、Object

        Object 是 JavaScript 中的一个引用类型,用于表示一个对象,它可以拥有多个属性和方法。Object 通常通过对象字面量或构造函数来创建,例如:

// 对象字面量创建对象
let obj = { name: 'Alice', age: 18 };

// 构造函数创建对象
let obj = new Object();
obj.name = 'Alice';
obj.age = 18;

2、null

        null 是 JavaScript 中的一种特殊类型,用于表示空对象引用。当一个对象不再需要时,可以将其设置为 null,例如:

let obj = { name: 'Alice', age: 18 };
obj = null;

3、undefined

        undefined 是 JavaScript 中的一种特殊类型,用于表示一个未定义的值或变量。当一个变量被声明但未赋值时,它的值为 undefined,例如:

let x;
console.log(x); // undefined

区别:

Object、null 和 undefined 之间的主要区别在于它们的用途和含义:

  • Object 用于表示一个具有属性和方法的对象
  • null 用于表示一个空对象引用中的
  • undefined 用于表示一个未初始化的变量中的默认

联系:

        Object、null 和 undefined 之间的联系在于它们都是 JavaScript 中的数据类型,同时它们也都是 JavaScript 中的原始值或特殊值。另外,当一个对象的属性值为 undefined 时,它也可以被认为是一个空值,这与 null 类型有些类似。

        总的来说,Object、null 和 undefined 之间都有各自独特的含义和用途,需要根据具体场景选择使用哪种类型。同时,在使用过程中也需要注意它们之间的区别和联系。

第十四章、symbol

用来表示一个独一无二的值

语法格式

let xxx=Symbol(‘标识字符串’);

使用Symbol类型作为属性名

    对象的属性要么是字符串,要么是symbol类型

默认是字符串,所以不加‘’也可以,如果需要类型为symbol,需要使用[]

不可以用.来访问,因为点运算符后面总是字符串

Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

注意点

  1. Symbol是基本数据类型!!!!不要加new哦
  2. 后面括号可以传入一个字符串,只是一个标记,方便我们阅读,没有任何意义
  3. 类型转化的时候不可转化为数值
  4. 不能做任何运算
  5. symbol生成的值作为属性或者方法的时候,一定要保存下来,否则后续无法使用
  6. for循环遍历对象的时候是无法遍历出symbol的属性和方法的

对象的遍历方法

  •     for (let xx in obj) :i代表key
  •     for (let xx of obj):不是自带的哈
  •     Object.keys(obj) :返回包含key的数组
  •     Object.values(obj) :返回包含value的数组
  •     Object.getOwnPropertyNames() :返回包含key的数组

上述的所有方法都是遍历不到symbol类型的(注意,是遍历时取不到symbol,并不是说我们访问不到对象的symbol类型)

可以遍历到symbol的方法:

  •     Object.getOwnPropertySymbols() :返回对象中只包含symbol类型key的数组
  •     Reflect.ownKeys() :返回对象中所有类型key的数组(包含symbol)

3.6. 1 symbol自带的方法

symbol.for()

因为symbol类型的值都是独一无二的,但有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

3.6.2 Symbol内置值

        除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

        可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

第十五章、普通对象Object概述

        普通Object类型,我们也称为一个对象,是JavaScript中的引用数据类型用于存储键值对(properties)的集合。对象可以包含各种数据类型的属性,包括字符串、数字、布尔、数组和其他对象等

        它是一种复合值它将很多值聚合到一起,可以通过名字访问这些值。对象也可以看做是属性的无序集合,每个属性都是一个名/值对。对象除了可以创建自有属性,还可以通过从一个名为原型的对象那里继承属性

类、对象、原型关系梳理

       类,原型,对象的关系可理解为平级的,继承的关系可理解为上下(父)级的

一、对象的语法表示

在 JavaScript 中,表示对象的语法是使用一对花括号 {} 将对象的属性和方法包含起来。

const 对象名= {

        属性名: 属性值,   //属性(方法)名和值之间用(冒号)连接,

        实例方法名: function() {   //键值对完成后用(逗号)隔开

                方法体;

                }

        };

        对象的属性和方法都使用键值对的形式表示,键表示属性或方法的名称,值表示属性或方法的值, 在对象内部,使用逗号分隔属性和方法

        在 JavaScript 中,对象的属性名可以是字符串或符号(Symbol)类型。当属性名是字符串类型时,可以使用引号或不使用引号来定义。如果需要类型为symbol,需要使用[ ]

例如,以下两个对象定义是等价的:

const obj1 = {
  name: 'John',
  age: 30
};

const obj2 = {
  'name': 'John',
  'age': 30
};

在这个例子中,obj1obj2 都定义了两个属性,nameage,只是在 obj2 中使用了引号将属性名包含起来。

实际上,只有在属性名包含特殊字符(如空格、破折号、中文等)时,才需要使用引号将属性名包含起来。如果属性名只包含字母、数字和下划线,可以不使用引号。

例如,以下两个对象定义也是等价的:

const obj1 = {
  firstName: 'John',
  lastName: 'Doe'
};

const obj2 = {
  'firstName': 'John',
  'lastName': 'Doe'
};

        总之,对象的属性名可以是字符串或符号类型,如果属性名只包含字母、数字和下划线,可以不使用引号将属性名包含起来,但如果属性名包含特殊字符,需要使用引号将属性名包含起来。

二、简化对象写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。

注意:对象简写形式简化了代码,所以以后用简写就对了

let name = "张三";
let age = 18;
let speak = function () {
    console.log(this.name);
};

//属性和方法简写
let person = {
    name,
    age,
    speak
};

console.log(person.name);
console.log(person.age);
person.speak();

第十六章、创建对象

一、通过字面量(常量)创建对象

使用花括号 {} 创建一个新的对象,并使用键值对初始化对象的属性和方法。

let user = {
            name: "张三",
            age: 20,
            say: function () {
                console.log("我叫张三");
            }
        };
        console.log(typeof user);
        console.log(user.name);
        user.say();

特殊情况:

this永远指向调用其运行环境的那个对象

let school = {
    name: "张三",
    getName() {
        let subFun = () => {
            console.log(this);  //this是静态的,始终指向函数在声明时所在作用域下的this的值
        }
        subFun();
    }
};
school.getName();  //{ name: '张三', getName: [Function: getName] }

二、通过构造方法constructor创建对象

使用一个方法作为对象的模板,并使用 new 运算符创建对象实例。

function Person(name, age) {
            this.name = name;
            this.age = age;
            this.say = function () {
                console.log("我叫" + this.name);
            };
        };
console.log(typeof Person);
function myClick() {
        let p1 = new Person("张三", 20);
        console.log(p1.name);
        p1.say();
};
myClick();

1、构造方法详解

1、在 JavaScript 中,构造函数是一种特殊的方法(在类体中,只能用于实例化对象),用于创建对象并初始化其属性和方法。在 ES5 中,构造函数是通过在函数内部使用 this 关键字来定义对象的属性和方法的,然后使用 new 运算符创建对象实例。

2、 构造方法创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用

3、需要注意的是,虽然在 ES6 中引入了 class 关键字,但 JavaScript 中的类本质上仍然是基于构造函数实现的。因此,尽管构造函数并不是类,但它们在 JavaScript 中起到了类似的作用。

构造方法执行原理:

  1. 调用构造函数,它会立刻创建一个新的对象
  2. 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
  3. 逐行执行函数中的代码
  4. 将新建的对象作为返回值返回

使用 instanceof 运算符检查一个对象是否是一个类的实例,它返回true或false

语法格式:

对象 instanceof 构造函数

// 使用构造函数来创建对象
        function Person(name, age) {
            // 设置对象的属性
            this.name = name;
            this.age = age;
            // 设置对象的方法
            this.sayName = function () {
                console.log(this.name);
            };
        }

        var person1 = new Person("孙悟空", 18);
        var person2 = new Person("猪八戒", 19);
        var person3 = new Person("沙和尚", 20);

        console.log(person1);
        console.log(person2);
        console.log(person3);
        console.log(person1 instanceof Person);

三、通过class创建对象

使用 ES6 中的 class 关键字创建一个类,并使用 new 运算符创建对象实例

class Person {
            //构造函数,包括普通字段和普通方法,给对象用的
            constructor(name, age) {
                this.name = name;
                this.age = age;
                // 对象方法
                this.say = function () {
                    console.log("我叫" + this.name);
                };
            };

            //挂载到原型对象上,相当于父类中的类方法
            sayName() {
                console.log("aaaa");
            };
            //静态方法 只能由类来调用
            static bar() {
                console.log(`静态方法`);
            };
        }

        function myClick() {
            let p1 = new Person("张三", 20);
            console.log(p1.name);
            p1.say();
            //相当于继承后修改父类方法
            Person.prototype.sayName = function () {
                console.log(this.name);
            }

            // Person.sayName = function(){    这个不能调
            //     console.log(this.name);
            // }  

            p1.sayName();
            Person.bar();

            //设置原型对象属性和方法,当使用一个对象的属性或方法时,会先在自身中寻找
            //自身有,则直接使用,没有则去原型对象中寻找,原型对象中有,则使用
            //再没有则去原型的原型中寻找,直到找到Object对象的原型
            //Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined
            
            // //获取原型对象  Person的原型对象是Object,相当于它自己,p1.constructor.prototype就是Person,所有才相等
            console.log(p1.constructor.prototype == Person.prototype)
        };
        myClick();

四、通过实例化Object()创建对象

五、用for循环批量创建对象

// 使用工厂模式创建对象
function createPerson(name, age) {
    // 创建新的对象
    var obj = new Object();
    // 设置对象属性
    obj.name = name;
    obj.age = age;
    // 设置对象方法
    obj.sayName = function () {
        console.log(this.name);
    };
    //返回新的对象
    return obj;
}

for (var i = 1; i <= 1000; i++) {
    var person = createPerson("person" + i, 18);
    console.log(person);
}

第十七章、对象的操作

一、访问属性和方法

访问属性的两种方式

第一种方式:使用 . 来访问

对象.属性名

第二种方式:使用 [] 来访问

对象[‘属性名’]

4.3 删除属性

删除对象的属性可以使用delete关键字,格式如下:

delete 对象.属性名

4.4 遍历对象

枚举遍历对象中的属性,可以使用for … in语句循环,对象中有几个属性,循环体就会执行几次。

语法格式:

for (var 变量 in 对象) {

        函数体;

}

相当于遍历python字典中的key,取其value

遍历一个空对象,不会执行其中代码

var a = {};
for(var i in a){
    console.log(444444444444444);
    console.log('222'+i);
}

第十八章、原型prototype

 一、概述

关于JS中的prototype介绍_js中prototype_xiaoweids的博客-CSDN博客

        我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。(就像一个同一个类所有对象的公有云一样)

        如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。

以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。

原型法的主要思想是,现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展。我们称B的原型为A。

//设置原型对象属性和方法,当使用一个对象的属性或方法时,会先在自身中寻找
//自身有,则直接使用,没有则去原型对象中寻找,原型对象中有,则使用
//再没有则去原型的原型中寻找,直到找到Object对象的原型
//Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined

 function People(name) {
            this.name = name;
            //对象方法
            this.Introduce = function () {
                console.log("My name is " + this.name);
            };
        };

        //类方法,不是静态方法
        People.Run = function () {
            console.log("I can run");
        }

        //原型方法
        People.prototype.IntroduceChinese = function () {
            console.log("我的名字是" + this.name);
        };

        //测试
        var p1 = new People("Windking");
        p1.Introduce();
        People.Run();
        p1.IntroduceChinese();

 无论是在function里外要想使用静态方法,必须使用方法名.静态方法名来使用

注意:

虽然构造方法已经结束了,但是还是可以方法外定义该构造方法的类方法和原型方法

原型方法相当于普通方法,只有对象能调用,类不能调用

function baseClass() {
            this.showMsg = function () {
                console.log("baseClass::showMsg");
            }
            this.baseShowMsg = function () {
                console.log("baseClass::baseShowMsg");
            }
        };
        //静态方法
        baseClass.showMsg = function () {
            console.log("baseClass::showMsg static");
        };
        function extendClass() {
            this.showMsg = function () {
                console.log("extendClass::showMsg");
            };
        };
        //静态方法:可以重名
        extendClass.showMsg = function () {
            console.log("extendClass::showMsg static");
        };

        extendClass.prototype = new baseClass();
        var instance = new extendClass();
        instance.showMsg(); //显示extendClass::showMsg
        instance.baseShowMsg(); //显示baseClass::baseShowMsg
        instance.showMsg(); //显示extendClass::showMsg
        baseClass.showMsg.call(instance);//显示baseClass::showMsg static
        var baseinstance = new baseClass();
        baseinstance.showMsg.call(instance);//显示baseClass::showMsg

 baseinstance.showMsg.call(instance);阅读为“将instance当做baseinstance来调用,调用它的对象方法showMsg”

总结:prototype原型对象就相当于类中的静态方法,不能理解为继承,可以理解为克隆,也可以自己创造,相当于一个云端公共仓库一样

原型对象使用举例

在前一节中,我们学习了使用构造函数的方式进行创建对象,但是,它还是存在一个问题,那就是,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数,在构造函数中直接引用就可以了,上代码:
 

// 使用构造函数来创建对象
        function Person(name, age) {
            // 设置对象的属性
            this.name = name;
            this.age = age;
            // 设置对象的方法
            this.sayName = sayName
        }

        // 抽取方法为全局函数
        function sayName() {
            console.log(this.name);
        }

        var person1 = new Person("孙悟空", 18);
        var person2 = new Person("猪八戒", 19);
        var person3 = new Person("沙和尚", 20);

        person1.sayName();
        person2.sayName();
        person3.sayName();

但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?答案肯定是有的,这就需要原型对象了
 

// 使用构造函数来创建对象
function Person(name, age) {
    // 设置对象的属性
    this.name = name;
    this.age = age;
}

// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {
    console.log(this.name);
};

var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);

person1.sayName();
person2.sayName();
person3.sayName();

二、原型链

1、原型链概述

        访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。

我们使用一张图来梳理一下上一节原型案例的代码:

注意:Object对象是所有对象的祖宗,Object的原型对象指向为null,也就是没有原型对象

2、原型链内存图示

原型链案例:

//仅在JS中才有这种语法,TS中不行
Object.prototype.name = "最高级";
function Parent(){
    // this.name = 1;
}
Parent.prototype.name = 2;
 
function Child(){
    // this.name = 3;
}
Child.prototype = {
    // name: 4,
    __proto__: new Parent()
}
 
const c = new Child();
console.log(c.name); // 3  4  1  2  最高级

console.log(c.hasOwnProperty('hasOwnProperty')) //false
console.log(c.__proto__.hasOwnProperty('hasOwnProperty')) //false
console.log(c.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) //false
console.log(c.__proto__.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) //false
console.log(c.__proto__.__proto__.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) //true
//编译报错
// console.log(c.__proto__.__proto__.__proto__.__proto__.__proto__.hasOwnProperty('hasOwnProperty'))

console.log(Child.prototype.hasOwnProperty('hasOwnProperty')) //false
//编译报错
// console.log(Child.prototype.prototype.hasOwnProperty('hasOwnProperty'))

如果最高级也没有的话就返回undefined

4.6 hasOwnProperty方法

        前边章节我们学过,如何遍历一个对象所有的属性和值,那我们要是判断当前对象是否包含指定的属性或方法可以使用 in 运算符来检查,如下代码演示:

可以先定义一个对象,然后再向里面增加属性和方法

// 创造一个构造函数
function MyClass() {
}

// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";

// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;

// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log("age" in mc);
console.log("name" in mc);

如果我只想要检查自身对象是否含有某个方法或属性,我们可以使用Object的hasOwnProperty()方法,它返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。如下代码演示:

原型对象prototype算是继承

// 创造一个构造函数
function MyClass() {
}

// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";

// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;

// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log("age" in mc);
console.log("name" in mc);

// 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象自身中含有属性时,才会返回true
console.log(mc.hasOwnProperty("age"));
console.log(mc.hasOwnProperty("name"));

这个MyClass类对象中没有hasOwnProperty这个方法啊,它是哪来的?对了,就是原型中的,在执行方法的时候它会通过原型链进行查找,这个方法是Object的特有方法,如下代码演示:

// 创造一个构造函数
function MyClass() {
}

// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";

// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;

// 检查当前对象
console.log(mc.hasOwnProperty("hasOwnProperty"));
// 检查当前对象的原型对象
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));
// 检查当前对象的原型对象的原型对象
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));

第十九章、对象继承

        面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是在JavaScript中没有类的概念,前边我们说所的类只是我们自己这么叫,大家要清楚。因此它的对象也与基于类的对象有所不同。实际上,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。(基于原型的面向对象)

那实现继承有一个最大的好处就是子对象可以使用父对象的属性和方法,从而简化了一些代码。

JavaScript有六种非常经典的对象继承方式,但是我们只学习前三种:

  • 原型链继承
  • 借用构造函数继承
  • 组合继承(重要)
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承

4.7.1 原型链继承

核心思想: 子类型的原型为父类型的一个实例对象

基本做法:

  1. 定义父类型构造函数
  2. 给父类型的原型添加方法
  3. 定义子类型的构造函数
  4. 创建父类型的对象赋值给子类型的原型
  5. 将子类型原型的构造属性设置为子类型
  6. 给子类型原型添加方法
  7. 创建子类型的对象: 可以调用父类型的方法
// 定义父类型构造函数
function SupperType() {
    this.supProp = 'Supper property';
}

// 给父类型的原型添加方法
SupperType.prototype.showSupperProp = function () {
    console.log(this.supProp);
};

// 定义子类型的构造函数
function SubType() {
    this.subProp = 'Sub property';
}

// 创建父类型的对象赋值给子类型的原型
SubType.prototype = new SupperType();

// 将子类型原型的构造属性设置为子类型
SubType.prototype.constructor = SubType;

// 给子类型原型添加方法
SubType.prototype.showSubProp = function () {
    console.log(this.subProp)
};

// 创建子类型的对象: 可以调用父类型的方法
var subType = new SubType();
subType.showSupperProp();
subType.showSubProp();

缺点描述:

  1. 原型链继承多个实例的引用类型属性指向相同,一个实例修改了原型属性,另一个实例的原型属性也会被修改
  2. 不能传递参数
  3. 继承单一

4.7.2 借用构造函数继承

核心思想: 使用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类

基本做法:

  • 定义父类型构造函数
  • 定义子类型的构造函数
  • 给子类型的原型添加方法
  • 创建子类型的对象然后调用

案例演示:

        借用构造函数继承的重点就在于SuperType**.call(this, name)**,调用了SuperType构造函数,这样,SubType的每个实例都会将SuperType中的属性复制一份。

call() 是函数对象的方法

// 定义父类型构造函数
function SuperType(name) {
    this.name = name;
    this.showSupperName = function () {
        console.log(this.name);
    };
}

// 定义子类型的构造函数
function SubType(name, age) {
    // 在子类型中调用call方法继承自SuperType
    SuperType.call(this, name);
    this.age = age;
}

// 给子类型的原型添加方法
SubType.prototype.showSubName = function () {
    console.log(this.name);
};

// 创建子类型的对象然后调用
var subType = new SubType("孙悟空", 20);
subType.showSupperName();
subType.showSubName();
console.log(subType.name);
console.log(subType.age);

缺点描述:

  1. 只能继承父类的实例属性和方法,不能继承原型属性和方法
  2. 无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿

4.7.3 组合继承

核心思想: 原型链+借用构造函数的组合继承

基本做法:

  1. 利用原型链实现对父类型对象的方法继承
  2. 利用super()借用父类型构建函数初始化相同属性
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.setName = function (name) {
    this.name = name;
};

function Student(name, age, price) {
    Person.call(this, name, age); // 为了得到父类型的实例属性和方法
    this.price = price; // 添加子类型私有的属性
}

Student.prototype = new Person(); // 为了得到父类型的原型属性和方法
Student.prototype.constructor = Student; // 修正constructor属性指向
Student.prototype.setPrice = function (price) { // 添加子类型私有的方法 
    this.price = price;
};

var s = new Student("孙悟空", 24, 15000);
console.log(s.name, s.age, s.price);
s.setName("猪八戒");
s.setPrice(16000);
console.log(s.name, s.age, s.price);

缺点描述:

  1. 父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法 。

注意:这个方法是JavaScript中最常用的继承模式

4.8 垃圾回收

        垃圾回收(GC):就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。

        当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。

        在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作,我们需要做的只是要将不再使用的对象设置null即可。

// 使用构造函数来创建对象
function Person(name, age) {
    // 设置对象的属性
    this.name = name;
    this.age = age;
}

var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);

person1 = null;
person2 = null;
person3 = null;

第二十章、常用的对象方法

1、Object.is() 

Object.is:判断两个值是否完全相等,返回true或false

等同于 ===  有些许差别

==,===,Object.is() 区别与联系

2、Object.assign ()

Object.assign:对象的合并,后边的对象会把前边对象的相同属性和方法覆盖,没有的属性和方法会合并

const config1 = {
    host: "localhost",
    port: 3306,
    name: "zhangsan",
    pass: "root",
    test1: "test1"
};
const config2 = {
    host: "127.0.0.1",
    port: 3309,
    name: "lisi",
    pass: "root",
    test2: "test2"
}
console.log(Object.assign(config1, config2));

3、Object.setPrototypeOf()

设置对象的原型对象

4、Object.getPrototypeof()

获取对象的原型对象

const school = {
    name: "MySchool"
};
const cities = {
    xiaoqu: ["北京", "上海", "深圳"]
};
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);
console.log(school.xiaoqu);
console.log(school.xiaoqu[0]);

5、 Object.keys()

返回一个给定对象的所有可枚举键值的数组

6、Object.values()

返回一个给定对象的所有可枚举属性值的数组

7、Object.entries()

返回一个给定对象自身可遍历属性 [key,value] 的数组

//声明对象
const person = {
    name: "张三",
    age: 20
};

//获取对象所有的键
console.log(Object.keys(person));
//获取对象所有的值
console.log(Object.values(person));
//获取对象所有的键值对数组
console.log(Object.entries(person));
//创建 Map
const m = new Map(Object.entries(person));
console.log(m.get("name"));

8、Object.getOwnPropertyDescriptors()

返回指定对象所有自身属性的描述对象

//声明对象
const person = {
    name: "张三",
    age: 20
};
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(person));

//声明对象
const obj = Object.create(null, {
    name: {
        //设置值
        value: "李四",
        //属性特性
        writable: true,
        configurable: true,
        enumerable: true
    },
    age: {
        //设置值
        value: 21,
        //属性特性
        writable: true,
        configurable: true,
        enumerable: true
    }
});
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(obj));

 9、Object.fromEntries()

Object.entries()的逆操作,用于将一个键值对数组转为对象。

//ES6:Map
//ES10:Object.fromEntries
const m = new Map();
m.set("name", "张三");
m.set("age", 20);
const result = Object.fromEntries(m);
console.log(result);

//ES8:Object.entries
const arr = Object.entries(result);
console.log(arr);

10、Object实例对象的constructor属性

const ccc = new Object();
console.log(ccc.constructor) //[Function: Object]
console.log(Object.prototype.constructor.call(ccc)) //{}

[Function: Object] 表示 Object 是一个函数(Function),并且该函数的名称是 "Object"。

在 JavaScript 中,函数也是一种对象。当你将一个函数打印到控制台或以字符串形式进行输出时,通常会显示函数的名称和一些表示其类型的标识。

在这种情况下,[Function: Object] 表示你打印的是一个函数,并且它的名称是 "Object"。这是 JavaScript 内置的构造函数 Object 的标识。

通过 new Object() 创建的对象都是 Object 构造函数的实例,因此它们的 constructor 属性会引用 Object 函数。

需要注意的是,[Function: Object] 中的 Object 是函数的名称,并非表示整个对象的字面值或内容。它只是用于标识对象的类型是由 Object 构造函数创建的

5、常见的对象类型

实例对象的constructor属性详解

JavaScriptconstructor属性详解 - 百度文库

常见对象方法

第二十一章、数组  Array (可变的)

【JavaScript 数组】 Array 用法详解(一篇搞定JS数组)_array js_programmer11123的博客-CSDN博客

Js之中Array(数组)方法解读_XAIhan的博客-CSDN博客_js array size

22个超详细的 JS 数组方法

一、数组创建

1、通过new Array类创建数组(数组内元素类型必须一致)

1.1、使用无参构造

const arr = new Array(); //创建一个空数组
console.log(arr); //[] 

1.2、使用带参构造

如果只传一个数值参数,则表示创建一个初始长度为指定数值空数组

const arr = new Array(20); // 创建一个包含20项的数组
console.log(arr) //[ <20 empty items> ]

 如果传入一个非数值的参数或者参数个数大于 1,则表示创建一个包含指定元素的数组

//数组元素类型一致
var myCars = new Array("Saab", "Volvo", "BMW");
console.log(typeof myCars); //object
console.log(myCars);

1.3、先创建一个空数组,再根据索引添加元素(数组内元素类型可以不一致)

//先创建一个空数组
var myCars = new Array();
//在通过索引向数组中添加元素
myCars[0] = "Saab";
myCars[1] = "Volvo";
myCars[2] = "BMW";

console.log(typeof myCars);
console.log(myCars);

2、Array.of 方法创建数组(数组内元素类型必须一致)es6 新增

  • ES6 为数组新增创建方法的目的之一,是帮助开发者在使用 Array 构造器时避开 js 语言的一个怪异点。
  • Array.of()方法总会创建一个包含所有传入参数的数组,而不管参数的数量与类型。
const arr = Array.of(true,false);
console.log(arr.length);//2
console.log(arr) //[ true, false ]


const arr1 = Array.of(3);
console.log(arr1.length);//1
console.log(arr1[0]);//3

3、Array.from 方法创建数组(数组内元素类型可以不一致)es6 新增

在 js 中将非数组对象转换为真正的数组是非常麻烦的。在 ES6 中,将可迭代对象或者类数组对象作为第一个参数传入,Array.from()就能返回一个数组。

function arga(...args) {  //...args剩余参数数组,由传递给函数的实际参数提供
    let arg = Array.from(args);
    console.log(arg);
}
let arr = [1,2]
arga( "asdf" , 26,  null ,arr); // [ 'asdf', 26, null, [ 1, 2 ] ]

4、通过常量(字面量)创建(数组内元素类型可以不一致)

var myCars=["Saab","Volvo","BMW"];

        console.log(typeof myCars);

        console.log(myCars);

var arr4 = [];   //创建一个空数组
var arr5 = [20];   // 创建一个包含1项数据为20的数组
var arr6 = ["lily","lucy","Tom"];   // 创建一个包含3个字符串的数组

二、遍历数组

1、普通for循环

arr = [1,2,3,4,5];
        for(let i = 0;i<arr.length;i++){
            console.log(arr[i]);
        }

2、实例方法forEach():该方法可以用来遍历数组

        forEach()方法需要一个函数作为参数,像这种函数,由我们创建但是不由我们调用的,我们称为回调函数。数组中有几个元素函数就会执行几次,每次执行时,浏览器会将遍历到的元素,以实参的形式传递进来,我们可以来定义形参,来读取这些内容,浏览器会在回调函数中传递三个参数:

  •     第一个参数:就是当前正在遍历的元素
  •     第二个参数:就是当前正在遍历的元素的索引
  •     第三个参数:就是正在遍历的数组

    注意:这个方法只支持IE8以上的浏览器,IE8及以下的浏览器均不支持该方法,所以如果需要兼容IE8,则不要使用forEach(),还是使用for循环来遍历数组。

arr.forEach(function (value, index, arr) {
    console.log(`值为${value},   索引为${index},   目前遍历的数组为${arr}`);
});
/**
 * 值为1,   索引为0,   目前遍历的数组为1,jzq,true
   值为jzq,   索引为1,   目前遍历的数组为1,jzq,true
   值为true,   索引为2,   目前遍历的数组为1,jzq,true
 */

3、for in 循环

for(let index in arr){
    const element = arr[index];
    console.log(element);
}

4、for of循环

for(let element of arr){
    console.log(element);
}

三、映射转换

        如果你想实行进一步的数组转换,你可以向 Array.from()方法传递一个映射用的函数作为第二个参数。此函数会将数组对象的每一个值转换为目标形式,并将其存储在目标数组的对应位置上。

function arga(...args) {  
    return Array.from(args, value => value + 1);
}
let arr1 = [1,2];
let arr = arga( arr1 , 26,  arr1.pop() );
console.log(arr);//[ arr1 ,27, pop1 ]

        如果映射函数需要在对象上工作,你可以手动传递第三个参数给 Array.from()方法,从而指定映射函数内部的 this 值。

const helper = {
    diff: 1,
    add(value) {
        return value + this.diff;
    }
}

function translate() {
    //arguments 是一个对应于传递给函数的参数的类数组对象
    return Array.from(arguments, helper.add, helper);
}

let arr = translate('liu', 26, 'man');
console.log(arr); // ["liu1", 27, "man1"]

四、数组常用属性

1、constructor属性演示:返回创建数组对象的原型函数

var arr = [1,2,3,4];
console.log(arr.constructor);

2、length属性演示:设置或返回数组元素的个数

var arr = [1,2,3,4];
console.log(arr.length);

五、数组常用方法

1、push():该方法可以向数组的末尾添加一个或多个元素,并返回数组的新的长度

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var result = arr.push("唐僧", "蜘蛛精", "白骨精", "玉兔精");
console.log(arr);
console.log(result);

2、pop():该方法可以删除数组的最后一个元素,并将被删除的元素作为返回值返回

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var result = arr.pop();
console.log(arr);
console.log(result);

3、unshift():该方法向数组开头添加一个或多个元素,并返回新的数组长度

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var result = arr.unshift("牛魔王", "二郎神");
console.log(arr);
console.log(result);

4、shift():该方法可以删除数组的第一个元素,并将被删除的元素作为返回值返回 

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var result = arr.shift();
console.log(arr);
console.log(result);

5、slice():该方法可以用来从数组提取指定元素,该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回 

参数:

  • 第一个参数:截取开始的位置的索引,包含开始索引
  • 第二个参数:截取结束的位置的索引,不包含结束索引,第二个参数可以省略不写,此时会截取从开始索引往后的所有元素

注意:索引可以传递一个负值,如果传递一个负值,则从后往前计算,-1代表倒数第一个,

-2代表倒数第二个。

var arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"];
var result = arr.slice(1, 4);
console.log(result);
result = arr.slice(3);
console.log(result);
result = arr.slice(1, -2);
console.log(result);

6、splice():该方法可以用于删除数组中的指定元素,该方法会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回 

参数:

  • 第一个参数:表示开始位置的索引
  • 第二个参数:表示要删除的元素数量
  • 第三个参数及以后参数:可以传递一些新的元素,这些元素将会自动插入到开始位置索引前边
var arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "白骨精"];
var result = arr.splice(3, 2);
console.log(arr);
console.log(result);
result = arr.splice(1, 0, "牛魔王", "铁扇公主", "红孩儿");
console.log(arr);
console.log(result);

7、concat():该方法可以连接两个或多个数组,并将新的数组返回,该方法不会对原数组产生影响 

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var arr2 = ["白骨精", "玉兔精", "蜘蛛精"];
var arr3 = ["二郎神", "太上老君", "玉皇大帝"];
var result = arr.concat(arr2, arr3, "牛魔王", "铁扇公主");
console.log(result);

8、join():该方法可以将数组转换为一个字符串,该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回,在join()中可以指定一个字符串作为参数,这个字符串将会成为数组中元素的连接符,如果不指定连接符,则默认使用,作为连接符 

var arr = ["孙悟空", "猪八戒", "沙和尚"];
var result = arr.join("@-@");
console.log(result);

9、reverse():该方法用来反转数组(前边的去后边,后边的去前边),该方法会直接修改原数组 

var arr = ["孙悟空", "猪八戒", "沙和尚"];
arr.reverse();
console.log(arr);

10、sort():该方法可以用来对数组中的元素进行排序,也会影响原数组,默认会按照Unicode编码进行排序

var arr = ["b", "c", "a"];
arr.sort();
console.log(arr);

注意:即使对于纯数字的数组,使用sort()排序时,也会按照Unicode编码来排序,所以对数字进排序时,可能会得到错误的结果。 

var arr = [1, 3, 2, 11, 5, 6];
arr.sort();
console.log(arr);

        我们可以自己来指定排序的规则,我们可以在sort()添加一个回调函数,来指定排序规则,回调函数中需要定义两个形参,浏览器将会分别使用数组中的元素作为实参去调用回调函数,使用哪个元素调用不确定,但是肯定的是在数组中a一定在b前边,浏览器会根据回调函数的返回值来决定元素的顺序,如下:

    如果返回一个大于0的值,则元素会交换位置
    如果返回一个小于0的值,则元素位置不变
    如果返回一个等于0的值,则认为两个元素相等,也不交换位置

经过上边的规则,我们可以总结下:

  •     如果需要升序排列,则返回 a-b
  •     如果需要降序排列,则返回 b-a
var arr = [1, 3, 2, 11, 5, 6];
arr.sort(function (a, b) {
    return a - b;
});
console.log(arr);

11、lastIndexOf():可返回一个指定的元素在数组中最后出现的位置的索引,从该字符串的后面向前查找。索引号是从左往右。

  • 如果要检索的元素没有出现,则该方法返回 -1。
  • var fruits = ["Banana", "Orange", "Apple", "Mango"];
    var a = fruits.lastIndexOf("Apple");
    console.log(a);  //输出 2
let arr = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
        console.log(arr[0]) //通过索引获取数组里的具体值
        console.log(arr.length) //获取数组的长度
        console.log(arr.indexOf("aa")) //获取内容所在的索引,找不到的话返回-1
        //数组进行合并
        let newArr=arr.concat(["liudehua","zhangxueyou"])  //会创建一个新的数组
        console.log(newArr)
        console.log(arr);
        // console.log(arr.join("-")) // 用数组的元素组成新的字符串,默认连接符为逗号
        console.log(arr.pop()) //删除数组最后一个元素,并返回这个元素
        console.log(arr)
        arr.push("zhangside") //数组最后添加一项
        console.log(arr)
        console.log(arr.reverse()) //把数组进行反序
        arr.shift() //删除数组第一项
        console.log(arr)
        console.log(arr.slice(1,3)) //从数组里获取内容,第一个参数是起始索引,第二个参数是终止索引(不包括)
        console.log(arr.sort()) //按照字母顺序排序

        let arrNum=[40,100,1,5,25,10];
        //数字排序(按数字顺序升序)
        console.log(arrNum.sort(function(a,b){return a-b}))
        //降序
        console.log(arrNum.sort(function(a,b){return b-a}))

        let colors = ["red", "green", "blue"];
        // 要删除的位置,和要删除的项数
        let removed = colors.splice(0,1);//删除第一项
        console.log(colors);  //green,blue
        console.log(removed);  //red

        //从位置1开始插入两项
        removed = colors.splice(1, 0, "pink", "purple");
        console.log(colors)

        // 删除一项,插入两项
        removed = colors.splice(1, 1, "pink", "purple");
        console.log(colors)

        console.log(arr.toString()) //数组转字符串
        arr.unshift("Lemon", "Pineapple");//在数组的开头添加新元素
        console.log(arr)

六、数组的map方法

JS中的Map方法_js map方法_骑马的汉子的博客-CSDN博客

七、数组去重

1、普通方法:

const array = [10, 10, 12, 12, 12, 15, 17] //有几个重复的元素
const ordinaryArray = []
for(let item of array){
    if(!ordinaryArray.includes(item)){
        ordinaryArray.push(item)
    }
}
 
console.log(ordinaryArray)

2、set方法

const array = [10, 10, 12, 12, 12, 15, 17] //有几个重复的元素
const newArray = [...new Set(array)];  //...为set解包
console.log(newArray);

第二十二章、日期 Date

JavaScript(JS) date.toUTCString()-CJavaPy

第二十三章、函数 Function

一、概述

        函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。

        通常情况下,JavaScript代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。

        这里要注意的是JavaScript中的函数也是一个对象,使用typeof检查一个函数对象时,会返回function。

二、函数创建

1、使用 函数对象几乎不用

语法格式:

const  函数名 = new Function("执行语句");

示例代码:

var fun = new Function("console.log('这是我的第一个函数');");

2、使用 函数声明比较常用

语法格式

function 函数名([形参1,形参2,...,形参N]) {
    语句...
};

示例代码:

function fun(){
    console.log("这是我的第二个函数");
}

可接受全局变量和局部变量

区别和特点:

  • 函数声明会被提升(hoisted),意味着可以在函数声明之前调用函数。
  • 函数声明可以在全局作用域或函数体内部进行声明。
  • 由于提升的特性,函数声明可以在其定义之前被调用,这使得在代码中的任何位置都可以使用函数。

3、使用 函数表达式比较常用

语法格式

var 函数名  = function([形参1,形参2,...,形参N]) {
    语句....
};

示例代码

var fun  = function() {
    console.log("这是我的第三个函数");
}

本身就是变量,只能接受局部变量

区别和特点:

  • 函数表达式不会被提升,所以必须在声明之后才能调用函数。
  • 函数表达式可以在全局作用域或函数体内部进行声明。
  • 可以根据需要将函数表达式赋值给不同的变量,实现函数的重命名或创建匿名函数。

4、使用 箭头函数(比较常用

        箭头函数是ES6引入的新语法,提供了一种更简洁的函数声明方式。使用箭头(=>)来定义函数,可以省略 function 关键字和花括号。

语法格式:

let 函数名 = ( 形参)  =>  { 

        语句......

}

var sum = (a, b) => a + b;

区别和特点:

  • 箭头函数具有更简洁的语法形式,适用于单行函数体。
  • 箭头函数没有自己的 this 值,它继承自包围它的最近的非箭头函数作用域。
  • 箭头函数不能用作构造函数,不能使用 new 关键字进行实例化。

5、三种常用声明方式的区别和联系

联系:

  • 这三种方式都用于声明函数,可以在代码中被调用和执行。
  • 它们都可以接受参数并返回一个值或执行一些操作。
  • 它们都可以在全局作用域或函数体内部进行声明,具有不同的作用域和生命周期。
  • typeof运算符结果都是function

        总结: 函数声明是一种常规的方式来定义函数,在整个作用域中都可以使用。函数表达式和箭头函数则更加灵活,可以根据需要赋值给变量,提供更多的编程风格和函数使用方式的选择。箭头函数还提供了更简洁的语法形式和继承外部作用域的特性。

区别:

        在JavaScript中,函数对象可以具有属性和方法,因为函数实际上是对象的一种特殊类型。

在JavaScript中,函数声明和函数表达式创建的函数是普通函数对象,因此可以像普通对象一样添加属性和方法。可以给它们分配属性。

        然而,箭头函数表达式创建的函数有一些不同的行为。箭头函数具有更简洁的语法形式和特殊的作用域规则,其中包括以下特点:

  1. 箭头函数没有自己的 this 值,它继承自包围它的最近的非箭头函数作用域。没有prototype原型
  2. 箭头函数没有 arguments 对象。
  3. 箭头函数不能做构造函数,不能使用new来调用
  4. 由于没有this,所以不能使用call,apply来改变this指向
  5. 箭头函数对象不能来添加属性
  6. 箭头函数不能使用yield,不能做生成器

        由于箭头函数没有自己的 this 值,它们也没有原型对象。这就是为什么无法给箭头函数分配属性的原因。

所以,总结一下:

  • 函数声明和函数表达式创建的函数都是普通函数对象,可以分配属性和方法。
  • 箭头函数创建的函数是特殊的函数对象,没有自己的 this 值和原型对象,因此无法添加属性和方法。

TS中使用函数表达式创建函数对象添加属性:

方式一:

使用const声明:

        使用 varlet 声明的函数表达式创建的函数对象是可变的(mutable),而使用 const 声明的函数表达式创建的函数对象是不可变的(immutable)。

const myFunction = function () {
    // 函数体
};

myFunction.someProperty = 'Hello';

方式二:

使用any进行类型断言

let myFunction = function () {
    // 函数体
} as any;

myFunction.someProperty = 'Hello';

5.3.3 函数和方法

函数:普通定义的函数就叫做函数

在对象中的函数:

        方法:在对象里面定义或者在对象外定义,但是在对象内未调用

        属性:在对象外定义,在对象内调用了

注意:函数本身也是对象,在函数内部定义的函数一律都是方法

5.3.4 函数调用(函数名加括号)

5.3.4.1 对于无参函数调用

// 函数声明
var fun = function () {
    console.log("哈哈,我执行啦!");
}

// 函数调用
fun();

5.3.4.2 对于有参函数调用

// 函数声明
var sum = function (num1, num2) {
    var result = num1 + num2;
    console.log("num1 + num2 = " + result);
}

// 函数调用
sum(10, 20);

        在 JavaScript 中,定义函数时可以指定函数参数,但是在调用函数时可以不传递参数,或者传递多个参数或少于定义的参数数量。
        当调用函数时传递的参数比定义的参数数量少时,未传递的参数会被设置为 undefined。当调用函数时传递的参数比定义的参数数量多时,额外的参数会被忽略。
例如,下面的代码定义了一个函数 add,该函数接受两个参数并返回它们的和:

javascript
function add(x, y) {
  return x + y;
}

在调用 add 函数时,可以传递两个参数:

add(2, 3); // 5
也可以只传递一个参数:
add(2); // NaN
当只传递一个参数时,第二个参数 y 将会被设置为 undefined,因此返回值为 NaN。
同样地,可以传递多个参数:
add(2, 3, 4); // 5
在这种情况下,第三个参数 4 将会被忽略,只有前两个参数 2 和 3 会被加起来并返回 5。
        需要注意的是,在函数的定义中指定的参数数量决定了函数的形式参数个数,而不是函数的实际参数个数。因此,可以在函数的定义中指定任意数量的参数,或者不指定任何参数,从而支持不同数量的参数。此外,也可以使用 arguments 对象在函数内部访问传递给函数的所有参数。

5.3.5 函数参数

  • JS中的所有的参数传递都是按值传递的,也就是说把函数外部的值赋值给函数内部的参数,就和把值从一个变量赋值给另一个变量是一样的,在调用函数时,可以在()中指定实参(实际参数),实参将会赋值给函数中对应的形参
  • 调用函数时,解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能,则需要对参数进行类型的检查,函数的实参可以是任意的数据类型(包括将函数当做实参)
  • 调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined

5.3.6 函数返回值

        可以使用 return 来设置函数的返回值,return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。

注意:在函数中return后的语句都不会执行如果return语句后不跟任何值就相当于返回一个undefined,如果函数中不写return,则也会返回undefined,return后可以跟任意类型的值

语法格式:return 值

还是只执行了log输出,但是return后面的语句将不执行 

//只要函数中执行了return语句,则return后面的语句就不会执行了
var aa = function (string) {
    if(true){
        return console.log(string);  //还是只执行了log输出,但是return后面的语句将不执行
    }
    console.log(666);
};

aa('123')

/* 执行结果
PS C:\Protractor> node .\demo1.js
123
PS C:\Protractor> 
*/

5.3.7 嵌套函数

嵌套函数:在函数中声明的函数就是嵌套函数,嵌套函数只能在当前函数中可以访问,在当前函数外无法访问。

5.3.8 匿名函数

匿名函数:没有名字的函数就是匿名函数,它可以让一个变量来接收,也就是用 “函数表达式” 方式创建和接收

var fun = function () {
    alert("我是一个匿名函数");
}

fun();

5.3.9 立即执行函数

立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数,立即执行函数往往只会执行一次。

(function () {
    alert("我是一个匿名函数");
})();

立即执行函数的多种写法

5.3.10 对象中的函数

  • 对象的属性值可以是任何的数据类型,也可以是个函数
  • 如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法,调用这个函数就说调用对象的方法(method)。

注意:方法和函数只是名称上的区别,没有其它别的区别

var person = {
    name: "zhangsan",
    age: 18,
    sayHello: function () {
        console.log(name + " hello")
    }
}

person.sayHello();

5.3.11 this和self和new方法

理解:把this理解成指向函数外部的对象,self理解成指向全局Windows,其实两个都指向调用的那个。把new理解成为实例后和对象绑定的方法

this永远指向调用其执行环境的那个对象

new操作符原理

function child(name) {
            // this.name = name;
            function aa (){
                console.log(name)
            }
            return aa;
        }

        function _new(f, ...args) {
            //创建一个空对象
            const obj = {};
            //改变隐式原型的指向
            obj.__proto__ = f.prototype;
            //改变构造函数this的指向
            const retrun_obj = f.apply(obj, args);
            // console.log(r);
            console.log(typeof r);
            //判断该构造函数有无返回对象
            const isTrue = retrun_obj && typeof retrun_obj === "function" || typeof retrun_obj === "object";
            console.log(isTrue);
            return isTrue ? retrun_obj : obj;

        }

        const a = new child(123);
        console.log(a);
        a();
        console.log("========================");
        const b = _new(child, 456);
        console.log(b);
        b();

javascript中this用法_十九万里的博客-CSDN博客_js this用法

js中 new,self和this的用法解释_小白啥时候能进阶成功的博客-CSDN博客

介绍一下 JS new运算符_段远山的博客-CSDN博客  

       解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象

  • 以函数的形式调用时,this永远都是window
  • 以方法的形式调用时,this就是调用方法的那个对象
  • 当以构造函数(类)的形式调用时,this就是新创建的那个对象
  • 使用call和apply调用时,this是传入的那个指定对象
  • 对于箭头函数是静态的,只能指向函数声明时的作用域

function BaseType(name,age){
    var self=this;  //self原来指向windows对象,此时的this指向BaseType,通过赋值后,self也指向BaseType
    // var this = self;   this是关键字,不允许声明变量
    self.aa= 111;  //对象可以调用
    self.cc = 123; //对象可以调用
    this.name=name; //对象可以调用
    this.age=age; //对象可以调用
    this.sayHello=function (){
    console.log("My name is "+this.name+", and i'm "+this.age+"years old.");
    }
    this.saySomething=function(){
        console.log(this.cc);
        console.log(this);  //会输出BaseType对象
        console.log(self.aa);
        this.sayHello();
    }
}
var b1=new BaseType("wede",30);
b1.saySomething();
console.log(b1.cc);

注意:this一般不在匿名函数中使用

5.3.12 arguments参数

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  1. 函数的上下文对象: this
  2. 封装实参的对象: arguments

        arguments是一个类数组对象,它也可以通过索引来操作数据,也可以获取长度,在调用函数时,我们所传递的实参都会在arguments中保存,比如:arguments.length 可以用来获取实参的长度,我们即使不定义形参,也可以通过arguments来使用实参,只不过比较麻烦,例如:

arguments[0]:表示第一个实参
arguments[1]:表示第二个实参
arguments对象演示:

function fun(a, b) {
    // 通过下标获取第一个参数
    console.log(arguments[0]);
    // 通过下标获取第二个参数
    console.log(arguments[1]);
    // 获取实参的个数
    console.log(arguments.length);
    // 看看它的函数对象
    console.log(arguments.callee);
    console.log(arguments.callee == fun);
}

fun("Hello", "World");

5.3.13 call()和apply()

        call()和apply()这两个方法都是函数对象的方法,需要通过函数对象来调用,当对函数调用call()和apply()都会调用函数执行,在调用call()和apply()可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this,call()方法可以将实参在对象之后依次传递,apply()方法需要将实参封装到一个数组中统一传递,如下演示:

call()方法演示:

function fun(a, b) {
    console.log("a = " + a);
    console.log("b = " + b);
    console.log("fun = " + this);
}

var obj = {
    name: "obj",
    sayName: function () {
        console.log(this.name);
    }
};

fun(2, 3);
console.log("===============");
fun.call(obj, 2, 3);

 注意:默认fun()函数调用,this指向的是window对象,你可以使用call()调用函数,在调用的时候传入一个对象,这个对象就是this所指向的对象,也就是说,可以自己指定this的指向,然后从第二个参数开始,实参将会依次传递

案例一:

function People(name) {
            this.name = name;
            //对象方法
            this.Introduce = function () {
                console.log("My name is " + this.name);
            };
        };

        var p1 = new People("Windking");
        const aa = {
            name: 666
        }
        p1.Introduce.call(aa);  //输出 My name is 666

案例二:

function People(name) {
    // this.name = name;
    //对象方法
    this.Introduce = function () {
        console.log("My name is " + name);
        function aa(){
            console.log(name)
        }
        return aa;
    };
};

var p1 = new People("Windking");
const aa = {
    name: 666
}
p1.Introduce.call(aa,123)();  //My name is Windking
                                //Windking

apply()方法演示:

function fun(a, b) {
    console.log("a = " + a);
    console.log("b = " + b);
    console.log("fun = " + this);
}

var obj = {
    name: "obj",
    sayName: function () {
        console.log(this.name);
    }
};

fun(2, 3);
console.log("===============");
fun.apply(obj, [2, 3]);

注意:默认fun()函数调用,this指向的是window对象,你可以使用apply()调用函数,在调用的时候传入一个对象,这个对象就是this所指向的对象,也就是说,可以自己指定this的指向,然后从第二个参数开始,需要制定一个实参数组进行参数传递 

5.3.14 函数作用域

函数作用域_dvhsf的博客-CSDN博客

只要函数在全局中声明,则就绑定为全局变量;函数在函数中声明,不管嵌套多少层,都绑定为最外层函数的作用域!

function aa(){
    var x=123;
    function bb(){
        function cc(){
            function dd(){
                function ee(){
                    function ff(){
                        console.log(x);
                    }
                    ff();
                }
                ee();
            }
            dd();
        }    
        cc();
    }
    bb();
    
}
aa();

ES6学习_es6延时计时器_weixin_48242257的博客-CSDN博客

5.3.15 箭头函数

JavaScript函数的使用_倚栏ζ听冷雨的博客-CSDN博客

若是省略函数体花括号,则默认返回return

ES6 允许使用「箭头」(=>)定义函数,通用写法如下:

let fn = (arg1, arg2, arg3) => {
    return arg1 + arg2 + arg3;
}

箭头函数的注意点:

  • 如果形参只有一个,则小括号可以省略
  • 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
  • 箭头函数 this 指向声明时所在作用域下 this 的值,箭头函数不会更改 this 指向,用来指定回调函数会非常合适
  • 箭头函数不能作为构造函数实例化
  • 不能使用 arguments 实参

省略小括号的情况:

let fn = num => {
    return num * 10;
};

省略花括号的情况:

let fn = score => score * 20;

this 指向声明时所在作用域中 this 的值:

// this 指向声明时所在作用域中 this 的值
let fn = () => {
    console.log(this);
}
fn();

let school = {
    name: "张三",
    getName() {
        let subFun = () => {
            console.log(this);
        }
        subFun();
    }
};
school.getName();

5.3.16 rest 参数

ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments 参数。

注意:

rest 参数非常适合不定个数参数函数的场景

个数可变的位置实参必须在函数形参最后面

// 作用与 arguments 类似
function add(...args) {
    console.log(args);
}
add(1, 2, 3, 4, 5);

// rest 参数必须是最后一个形参
function minus(a, b, ...args) {
    console.log(a, b, args);
}
minus(100, 1, 2, 3, 4, 5, 19);

对象展开

function connect({host, port, ...user}) {
    console.log(host);
    console.log(port);
    console.log(user);
}

connect({
    host: '127.0.0.1',
    port: 3306,
    username: 'root',
    password: 'root',
    type: 'master'
});

5.3.17 spread 扩展运算符

扩展运算符(spread)也是三个点(…),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。

展开数组:

// 展开数组
let tfboys = ["德玛西亚之力", "德玛西亚之翼", "德玛西亚皇子"];
function fn() {
    console.log(arguments);
}
fn(...tfboys);

展开对象:

// 展开对象
let skillOne = {
    q: "致命打击"
};
let skillTwo = {
    w: "勇气"
};
let skillThree = {
    e: "审判"
};
let skillFour = {
    r: "德玛西亚正义"
};
let gailun = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};
console.log(gailun);

5.3.18 构造函数和普通函数的区别

https://www.cnblogs.com/jing-tian/p/11220745.html

const a = new Array();
a.push(1,2);
console.log(a);
const b = new Array;
b.push(1,2,3);
console.log(b);

 实例化构造函数方法,加括号和不加括号是一样的

第二十四章、Set

JavaScript中Set的使用_js set_至尊绝伦的博客-CSDN博客

Set有点类似于Array数组,但是和数组的区别是Set的元素不能重复。

5.4.1 Set的创建和添加元素

我们需要通过Set的构造函数创建Set,不能通过字面值来创建set

  • 可以先创建一个空的Set对象,随后使用add方法添加元素;
  • 也可以直接添加元素;

Set() 中传参必须传一个可迭代对象,比如数组

// 方式一:创建Set对象,使用add方法添加元素
 
const obj = {name: "aaa"}
const set1 = new Set()
set1.add(10)
set1.add(10)
set1.add(12)
set1.add(14)
set1.add(obj)
console.log(set1)
 
// 方式二: 直接添加元素
 
const set2 = new Set([10, 10, 12, 14, {name: "zzz"}])
console.log(set2)

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:

  • size:返回集合的元素个数
  • add():增加一个新元素,返回当前集合
  • delete():删除元素,返回 boolean 值
  • has():检测集合中是否包含某个元素,返回 boolean 值
  • clear():清空集合,返回 undefined
//创建一个空集合
let s = new Set();
console.log(typeof s);
//创建一个非空集合
let s1 = new Set([1, 2, 3, 1, 2, 3]);
//集合属性与方法
//返回集合的元素个数
console.log(s1.size);
//添加新元素
console.log(s1.add(4));
//删除元素
console.log(s1.delete(1));
//检测是否存在某个值
console.log(s1.has(2));
//清空集合
console.log(s1.clear());

第二十五章、Math

5.5.1 常用方法

Math.max()

语法: Math.max(n1,n2,n3,...,nX)
返回值:max() 方法可返回指定的参数中带有较大的值的那个数

var a = Math.max(1,2,3,4);
console.log(a); //4

但是如果数据是放在一个数组里面,此时就不能这样调用了。这时就用到apply方法

Math.max.apply()

apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向,同时用指定数组替换函数的参数。

语法:fun.apply(thisArg, [argsArray])

  • thisArg:在fun函数运行时指定的 this 值 ,可以为null,就是不设置指向
  • argsArray:传递的值,必须包含在数组里面

这里额外补充一下,传递的值为数组形式,但是数组里是什么类型参数,返回的也是什么类型,比如输入的数组中是字符串这里取到的就是字符串,是数值取到的就是数值。比如传‘abc' 返回的也是‘abc'。(补充说明)

返回值:apply() 方法的返回值就是函数的返回值,因为它就是调用函数

var arr = [1, 66, 3, 99, 4];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max); //99
console.log(min); //1

特殊情况

var arr = Array.of('1','a');
console.log(arr);
var result = Math.max.apply(Math,arr);
console.log(result);  //输出3

Math.trunc()

Math.trunc:将数字的小数部分抹掉

console.log(Math.trunc(3.5));

Math.sign()

Math.sign:判断一个数到底为正数、负数、还是零

  • 整数返回1
  • 负数返回-1
  • 0返回0
console.log(Math.sign(100));
console.log(Math.sign(0));
console.log(Math.sign(-20000));

Math.pow() 幂运算

** 操作符的作用和 Math.pow 的作用是一样,请看代码

console.log(2 ** 10);
console.log(Math.pow(2, 10));

第二十六章、Map

        ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历是真正的键/值存储机制,在 ES6 之前通过对象存储“键值对”

5.6.1 构建Map

使用 new 关键字创建一个 Map 实例,可以接收一个二维数组,作为初始化的数据。

5.6.2 Map 的属性和方法:

size属性:返回 Map 的元素个数

const map1 = new Map([["key", "val"]]);
console.log(map1.size);// 1

set()方法:增加一个新元素,第一个参数是 ,第二个参数是 。可以连续调用,

返回当前 Map

const map1 = new Map();
console.log(map1,map1.size);

map1.set("key", "val");
console.log(map1,map1.size);

map1.set("key1", "val1").set("key2", "key2");
console.log(map1,map1.size);

get()方法:返回键名对象的键值,如果作为参数的键不在 Map 中则返回 undefined

const map1 = new Map();
map1.set("key", "val");
console.log(map1.get("key"))//val
console.log(map1.get("key1"))//undefined

has()方法:检测 Map 中是否包含某个元素,存在返回 true,不存在返回 false

const map1 = new Map([["key", "val"]]);
console.log(map1.has("key"))//true
console.log(map1.has("key1"))//false

delete()方法:删除键值对。传入要删除的键。要删除的键在实例中则删除并返回 true,否则返回 false。

const map1 = new Map([["key", "val"]]);
console.log(map1.delete("key"))//true
console.log(map1.size);
console.log(map1.delete("key1"))//false

clear()方法:清空集合,返回 undefined

const map1 = new Map([["key", "val"]]);
console.log(map1.size);//1
map1.clear();
console.log(map1.size);//0
//创建一个空 map
let m = new Map();
console.log(Object.prototype.toString.call(m));
//创建一个非空 map
let m2 = new Map([
    ["name", "张三"],
    ["gender", "女"]
]);
console.log(Object.prototype.toString.call(m2));
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set("age", 6));
console.log(m2.size);
//获取映射值
console.log(m2.get("age"));
//检测是否有该映射
console.log(m2.has("age"));
//清除
console.log(m2.clear());

5.6.3 对象作为键

Map 可以使用对象作为键。并且 Map 使用 SameValueZero(类似于严格对象相等)来确定键的匹配性。所以当对象作为键时,更改对象的属性并不会丢失映射关系

const obj = {}
const map1 = new Map([[obj, "val"]]);
console.log(map1.get(obj));//val
obj.name = "123";
console.log(map1.get(obj));//val

 5.6.4 迭代

Map 能够按照键值对的插入顺序迭代输出。并且 Map 实例提供了一个迭代器以便能够通过特特定方法按顺序迭代。

entries

调用本方法会返回一个迭代器,并且是默认迭代器。所以可以使用扩展操作符,使用 for of 迭代 Map 实例时可以省略显示调用 entries();

const map1 = new Map([[1, 1], [2, 2]]);
for (let item of map1.entries()) {
  console.log(item);
}
console.log("-----------");
for (let item of map1) {
  console.log(item);
}
console.log("-----------");
console.log([...map1]);

keys

调用本方法会返回一个按照插入顺序的键的迭代器。

const map1 = new Map([[1, 1], [2, 2]]);
for (let item of map1.keys()) {
  console.log(item);
}

values

调用本方法会返回一个按照插入顺序的值的迭代器

const map1 = new Map([[1, 1], [2, 2]]);
for (let item of map1.values()) {
  console.log(item);
}

forEach

当然也可以不使用迭代器而是用回调的方式迭代,示例如下。回调函数的第一个参数是值,第二个参数是键。

const map1 = new Map([[1, 1], [2, 2]]);
map1.forEach((val, key) => {
  console.log(key + ":" + val);
})

需要注意的是如果在迭代过程中修改给了每次迭代的结果,并不会影响映射关系,如下例

const map1 = new Map([[1, 1], [2, 2]]);
for (let item of map1.keys()) {
  item = 3;
  console.log(item);
}
console.log(map1.get(1));

由输出结果可知在遍历的过程中使 item 的值为 3,因此输出两次 3,最后通过原键仍然拿到了值。 

5.6.5 weakMap

weakMap 也是 ES6 新增的一种集合类型,它有如下特点:

键只能是 Object 类型或继承自 Object 类型,否则会抛出错误

  • weakMap 实例对该键 Object 是弱引用,不会影响 JS 的垃圾回收。
  • weakMap 的基本 API 比 Map 的少了 clear(清空)方法,因为特点2,所以没有必要再持有该方法。
  • weakMap 没有提供迭代其实例键值对的迭代器。因为特点2,所以 weakMap 实例中的键值对随时随地都有可能被销毁。
  • weakMap 实例没有 size 属性。
  • weakMap 也可以像构建 Map 那样构建,也有 set、get、has、delete方法。

5.6.6 弱引用

JS 有一种垃圾回收策略叫做引用计数通俗理解就是声明一个对象后,JS 就会使用一个变量用来保存该对象被使用的次数,如果被使用次数为 0 ,那么 JS 就会销毁该对象,释放内存

给个例子

const obj1 = { name: 1 }, obj2 = { name: 2 };
const map = new WeakMap([[obj1, 1], [obj2, 2]]);

        这里弱引用的意思是当我实例化了一个 weakMap 集合 map,并且使用 obj1 和 obj2 作为键值对的键存入 map 中,那么此时 map 使用 obj1 和 obj2,但是由于 map 是 weakMap 实例,所以 obj1 和 obj2的被使用次数并不会加 1,那么一旦 obj1 和 obj2 又没有在别的地方被使用,JS 就会回收 obj1 和 obj2,map 就失去了键,自然获取不到值。

        因此 weakMap 实例没有提供迭代器,因为也许你刚迭代完,该键就被回收了,内存都被释放了,那么此时的迭代就没有了意义。也正因此 weakMap 舍弃了 clear API。并且即使能够访问 weakMap 实例也无法看到其存储的键值对。

        但是需要注意的是 weakMap 的值并不是弱引用,只要键存在,那么它对应的值一定不会被 JS 回收。

        还需要注意 weakMap 的好兄弟 Map 就不是这样,Map 实例对于键的应用是强引用,只要 Map 实例没被回收,那么其存储键值对就会一直存在,不会被 JS 私自回收。

// 创建一个空Map对象
const myMap = new Map();

// 向Map对象中添加键值对
myMap.set('key1', 'value1');
myMap.set('key2', 2);

// 获取Map对象中的值
console.log(myMap.get('key1')); // 输出: "value1"
console.log(myMap.get('key2')); // 输出: 2

// 检查Map对象是否包含指定键
console.log(myMap.has('key1')); // 输出: true

// 删除Map对象中的键值对
myMap.delete('key2');

// 迭代Map对象中的键值对
for (const [key, value] of myMap.entries()) {
  console.log(`${key} = ${value}`);
}

第二十七章、数据类型转换

1、 强制转换为String类型

将其它数值转换为字符串有三种方式:toString()、String()、 拼串。

方式一:调用被转换数据类型的toString()方法,该方法不会影响到原变量,它会将转换的结果返回,但是注意:null和undefined这两个值没有toString()方法,如果调用它们的方法,会报错

        toString()函数用于将当前对象以字符串的形式返回。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法,所有主流浏览器均支持该函数。 

// 使用构造函数来创建对象
function Person(name, age) {
    // 设置对象的属性
    this.name = name;
    this.age = age;
}

//创建对象的一个实例对象
var p = new Person("张三", 20);
console.log(p.toString());

 JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。

// 字符串
var str = "Hello";
console.log(str.toString());

// 数字
var num = 15.26540;
console.log(num.toString());

// 布尔
var bool = true;
console.log(bool.toString());

// Object
var obj = {name: "张三", age: 18};
console.log(obj.toString());

// 数组
var array = ["CodePlayer", true, 12, -5];
console.log(array.toString());

// 日期
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
console.log(date.toString());

// 错误
var error = new Error("自定义错误信息");
console.log(error.toString());

// 函数
console.log(Function.toString());

方式二:调用String()函数,并将被转换的数据作为参数传递给函数,使用String()函数做强制类型转换时,对于Number和Boolean实际上就是调用的toString()方法,但是对于null和undefined,就不会调用toString()方法,它会将 null 直接转换为 “null”,将 undefined 直接转换为 “undefined”。

方式三:为任意的数据类型 +"" 

1.1 数字类型转字符串

//数字类型转字符串
(123).toString() // "123"
(100 + 23).toString()

1.2 布尔类型转字符串

//布尔类型转字符串
false.toString()     // 返回 "false"
true.toString()      // 返回 "true"

1.3 日期类型转字符串

//日期类型转字符串
let obj = new Date()
obj.toString() 

2、 强制转换为Number类型

有三个函数可以把非数值转换为数值:Number()、parseInt() 和parseFloat()。

Number()可以用来转换任意类型的数据,而后两者只能用于转换字符串。parseInt()只会将字符串转换为整数,而parseFloat()可以将字符串转换为浮点数。

2.1 使用Number()函数

字符串 --> 数字
        如果是纯数字的字符串,则直接将其转换为数字
        如果字符串中有非数字的内容,则转换为NaN
        如果字符串是一个空串或者是一个全是空格的字符串,则转换为0


布尔 --> 数字
        true 转成 1
        false 转成 0


null --> 数字
        null 转成 0


undefined --> 数字
        undefined 转成 NaN

//字符串转数字
Number("3.14")    // 返回 3.14
Number(" ")       // 返回 0
Number("")        // 返回 0
Number("99 88")   // 返回 NaN

2.2 使用parseInt()函数

这种方式专门用来对付字符串,parseInt() 把一个字符串转换为一个整数

2.3 使用parseFloat()函数

这种方式专门用来对付字符串,parseFloat() 把一个字符串转换为一个浮点数

 注意:如果对非String使用parseInt()或parseFloat(),它会先将其转换为String然后在操作

3、强制 转换为Boolean类型

将其它的数据类型转换为Boolean,只能使用Boolean()函数。

  • 使用Boolean()函数
    • 数字 —> 布尔
      • 除了0和NaN,其余的都是true
    • 字符串 —> 布尔
      • 除了空串,其余的都是true
    • null和undefined都会转换为false
    • 对象也会转换为true

4、 自动转换类型

当 JavaScript 尝试操作一个 "错误" 的数据类型时,会自动转换为 "正确" 的数据类型。

5 + null    // 返回 5         null 转换为 0           默认转为数字
"5" + null  // 返回"5null"   null 转换为 "null"   默认转为字符串
"5" + 1     // 返回 "51"      1 转换为 "1"     默认转为字符串
"5" - 1     // 返回 4         "5" 转换为 5     默认转为数字

5、 常见数据类型转换表

第二十八章、运算符,条件和循环语句

一、运算符

运算符也叫操作符,通过运算符可以对一个或多个值进行运算并获取运算结果。

比如:typeof就是运算符,可以来获得一个值的类型,它会将该值的类型以字符串的形式返回(number string boolean undefined object function 等)

1、算术运算符

算术运算符用于表达式计算。

y=5,下面的表格解释了这些算术运算符

 自增和自检详解

JavaScript中的自增和自减_js自增自减_前端小草籽的博客-CSDN博客

2、关系运算符

关系运算符在逻辑语句中使用,以测定变量或值是否相等。

例如:令x=5,下面的表格解释了比较运算符:

3、赋值运算符

赋值运算符用于给 JavaScript 变量赋值。

令x=10 和 y=5,下面的表格解释了赋值运算符:

4、逻辑运算符(返回true或false)

逻辑运算符用于测定变量或值之间的逻辑。

给定 x=6 以及 y=3,下表解释了逻辑运算符:

  • && 与:&&可以对符号两侧的值进行与运算并返回结果,运算规则如下:
    • 两个值中只要有一个值为false,就返回false,只有两个值都为true时,才会返回true
    • JS中的“与”属于短路的与,如果第一个值为false,则不会检查第二个值
    • 非布尔值时:如果两个都为true,则返回第二个值,如果两个值中有false,则返回靠前的false的值
  • || 或:||可以对符号两侧的值进行或运算并返回结果,运算规则如下:
    • 两个值中只要有一个true,就返回true,只有两个值都为false,才会返回false
    • JS中的“或”属于短路的或,如果第一个值为true,则不会检查第二个值
    • 非布尔值时:如果两个都为false ,则返回第二个值,如果两个值中有true,则返回靠前的true的值
  • ! 非:!可以用来对一个值进行非运算,所谓非运算就是对一个布尔值进行取反操作,true变false,false变true,运算规则如下:
    • 如果对一个值进行两次取反,它不会变化
    • 非布尔值时:先会将其转换为布尔值,然后再取反,所以我们可以利用该特点,来将一个其它的数据类型转换为布尔值,可以为一个任意数据类型取两次反,来将其转换为布尔值,原理和Boolean()函数一样

5、比较运算符(返回true或false)

比较运算符用来比较两个值是否相等,如果相等会返回true,否则返回false。

  • 使用 == 来做相等运算
    • 当使用==来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型,然后在比较
  • 使用 != 来做不相等运算
    • 不相等用来判断两个值是否不相等,如果不相等返回true,否则返回false,不相等也会对变量进行自动的类型转换,如果转换后相等它也会返回false
  • 使用 === 来做全等运算
    • 用来判断两个值是否全等,它和相等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false
  • 使用 !== 来做不全等运算
    • 用来判断两个值是否不全等,它和不等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回true

6、条件运算符

JavaScript 还包含了基于某些条件对变量进行赋值的条件运算符。

语法:variablename = (condition) ? value1 : value2;

举例:result=(age<18)?"年龄太小":"年龄合适";

执行流程:如果condition为true,则执行语句1,并返回执行结果,如果为false,则执行语句2,并返回执行结果

7、逗号运算符

使用逗号可以在一条语句中执行多次操作。

比如:var num1=1, num2=2, num3=3;

使用逗号运算符分隔的语句会从左到右顺 序依次执行。

8、in运算符

        在JavaScript中,in运算符用于检查对象是否具有指定的属性。

它的语法形式为   property in object

总结来说,in运算符用于检查对象是否具有指定的属性,返回一个布尔值。它可用于对象和数组,并考虑对象的原型链。

其中 property 是一个字符串表示的属性名,object 是要检查的对象。

当使用in运算符时,它会返回一个布尔值,表示对象是否具有指定的属性。如果对象中存在该属性,则返回true,否则返回false

以下是in运算符的一些使用示例和解释:

const person = {
  name: 'John',
  age: 30,
};

console.log('name' in person); // true,person对象具有名为'name'的属性
console.log('job' in person); // false,person对象没有名为'job'的属性

const fruits = ['apple', 'banana', 'orange'];

console.log(0 in fruits); // true,fruits数组具有索引为0的元素
console.log(3 in fruits); // false,fruits数组没有索引为3的元素

// 使用in运算符在迭代对象属性时进行判断
for (const key in person) {
  console.log(key); // 输出对象的属性名
}

in运算符不仅适用于对象,也适用于数组。在数组上使用in运算符时,它会判断给定的索引是否存在于数组中。

需要注意的是,in运算符会检查对象自身及其原型链上的属性。如果要判断一个属性是否是对象自身拥有的,可以使用Object.prototype.hasOwnProperty()方法。

console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false

1.8 运算符优先级

逗号运算符< 赋值运算符 < 条件运算符 < 逻辑运算符(not > and > or)< 比较运算符 < 关系运算符 < 算数运算符

下图运算符优先级从上到下,单行从左到右递减

2、条件语句

条件语句是通过判断指定表达式的值来决定执行还是跳过某些语句,最基本的条件语句:

  • if…else
  • switch…case

2.1 if…else

if…else语句是一种最基本的控制语句,它让JavaScript可以有条件的执行语句。

第一种形式:

if(expression){
    statement

}

var age = 16;
if (age < 18) {
    console.log("未成年");
}

第二种形式:

if(expression){
    statement

}
else{
    statement

}

var age = 16;
if (age < 18) {
    console.log("未成年");
} else {
    console.log("已成年");
}

第三种形式:

if(expression1){
    statement

}
else if(expression2){
    statement

}
else{
    statement    

}

var age = 18;
if (age < 18) {
    console.log("小于18岁了");
} else if (age == 18) {
    console.log("已经18岁了");
} else {
    console.log("大于18岁了")
}

2.2 switch…case(break)

switch…case是另一种流程控制语句。

switch语句更适用于多条分支使用同一条语句的情况。

语法格式:

switch (语句) {
    case 表达式1:
        语句...
    case 表达式2:
        语句...
    default:
        语句...
}

注意:需要注意的是一旦符合case的条件程序会一直运行到结束,所以我们一般会在case中添加break作为语句的结束。

案例演示1:根据today的数值,输出今天是星期几。

var today = 1;
switch (today) {
    case 1:
        console.log("星期一");
        break;
    case 2:
        console.log("星期二");
        break;
    case 3:
        console.log("星期三");
        break;
    case 4:
        console.log("星期四");
        break;
    case 5:
        console.log("星期五");
        break;
    case 6:
        console.log("星期六");
        break;
    case 7:
        console.log("星期日");
        break;
    default:
        console.log("输入错误");
}

只要有一条case满足条件,则会进入执行体,如果不加break则会顺序执行后续所有的case,如果条件都不满足,则都不会进入

案例演示2:根据month的数值,输出对应月份的天数,2月默认28天。

var month = 10;
switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        console.log("31天");
        break;
    case 4:
    case 6:
    case 9:
    case 11:
        console.log("30天");
        break;
    case 2:
        console.log("28天");
        break;
    default:
        console.log("输入错误");
}

多条case可以共用一个表达式

3、循环语句

        循环语句和条件语句一样,也是基本的控制语句,只要满足一定的条件将会一直执行,最基本的循环语句:

  • while
  • do…while
  • for

3.1 while

while语句是一个最基本的循环语句,while语句也被称为while循环。

语法格式:

while(条件表达式){
    语句...
}

案例演示:输出1-10。

var i = 1;
while (i <= 10) {
    console.log(i);
    i++; //此时i++和++i一样
}

3.2 do…while

        do…while和while非常类似,只不过它会在循环的尾部而不是顶部检查表达式的值,因此,do…while循环会至少执行一次。相比于while,do…while的使用情况并不 是很多。

语法格式:

do{
    语句...
}while(条件表达式);

案例演示:输出1-10。

var i = 1;
do {
    console.log(i);
    i++; //放在下面和++i是一样的
} while (i <= 10);

3.3 for

        for语句也是循环控制语句,我们也称它为for循环。大部分循环都会有一个计数器用以控制循环执行的次数, 计数器的三个关键操作是初始化、检测和更新。for语句 就将这三步操作明确为了语法的一部分。

语法格式:

for(初始化表达式 ; 条件表达式 ; 更新表达式){
    语句...
}

案例演示:输出1-10。

for (var i = 1; i <= 10; i++) { //此时i++和++i是一样的
    console.log(i);
}

for循环执行原理

        计数器初始化后,先进行条件判断,若符合条件,则执行语句,执行语句后进行计数器更新,然后在进行判断,直到不符合条件

3.4 break continue

  • break:结束最近的一次循环,可以在循环和switch语句中使用。
  • continue:结束本次循环,执行下一次循环,只能在循环中使用。

3.4.1 标识循环

        那如果我们想要跳出多层循环或者跳到指定位置该怎么办呢?可以为循环语句创建一个label,来标识当前的循环,如下例子

outer: for (var i = 0; i < 10; i++) {
            for (var j = 0; j < 10; j++) {
                if (j == 5) {
                    break outer;
                }
                console.log(j);
            }
        }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值