javascript函数怎么写,js函数的使用规则
本篇文章给大家谈谈javascript函数怎么写,以及js函数的使用规则,希望对各位有所帮助,不要忘了收藏本站喔。
一、初识 JavaScript
JavaScript (简称 JS)
是世界上最流行的编程语言之一
是一个脚本语言,通过解释器运行
虽然 js 主要是用于前端页面的开发,但是实际上,也可以进行服务器开发 / 客户端程序的开发
JavaScript 最初只是为了完成简单的表单验证(验证数据合法性),结果后来不小心就火了
当前 JavaScript 已经成为了一个通用的编程语言
JavaScript 的能做的事情:
- 网页开发(更复杂的特效和用户交互)
- 网页游戏开发
- 服务器开发(node.js)
- 桌面程序开发(Electron, VSCode 就是这么来的)
- 手机 app 开发
JavaScript 之父 布兰登·艾奇 (Brendan Eich)
曾经的布兰登:
发明 JavaScript 之后的布兰登:
1995 年,用 10 天 时间完成 JS 的设计 (由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,Java 写出来的程序混乱不堪)
最初在网景公司,命名为 LiveScript,
一般认为,当时 Netscape 之所以将 LiveScript 命名为 JavaScript,是因为 Java 是当时最流行的
编程语言,带有 “Java” 的名字有助于这门新生语言的传播。
其实 Java 和 JavaScript 之间的语法风格相去甚远
JavaScript 发展历史可以参考阮一峰大神的博客
http://www.ruanyifeng.com/blog/2011/06/birth_of_java.html
JavaScrip:
-
HTML
: 网页的结构(骨) -
CSS
: 网页的表现(皮) -
JavaScript
: 网页的行为(灵魂)
前端开发 JS 是一家独大的
挑战者:
- Dart (google 搞的,下一代移动端开发平台上面自带的编程语言)
- Web Assembly.类似于 “汇编语言”,在浏览器上定义了一组基础指令,可以通过一些第三方工具,把其他的主流编程语言,给转换成 WebAssemblyPython中的所有运算符号。这时候 C++,Java,Python… 都可以通过这个途径来实现页面的开发了…
- TypeScript [最有希望的挑战者],也是目前进展非常迅猛的一个大将
JS 和 TS 的关系,就类似于 C 和 C++ 的关系,“兼容”
TS 完全支持 JS 当下的语法,并且有引入了一些新的语法规则,让程序猿写的更爽,这个 TS 的吸引力,对于现有的这些前端开发程序猿来说还是非常大的 [挖墙脚]
但是虽然如此,TS 至今仍然还没有彻底的取代 JS,前端这个圈子里,仍然是 JS 为主
JavaScript 运行过程:
- 编写的代码是保存在文件中的,也就是存储在硬盘 (外存上)
- 双击
.html
文件浏览器 (应用程序) 就会读取文件,把文件内容加载到内存中 (数据流向:硬盘 => 内存) - 浏览器会解析用户编写的代码,把代码翻译成二进制的,能让计算机识别的指令 (解释器的工作)
- 得到的二进制指令会被 CPU 加载并执行 (数据流向:内存 => CPU)
浏览器分成渲染引擎 + JS 引擎:
- 渲染引擎:解析 html + CSS,俗称 “内核”
- JS 引擎:也就是 JS 解释器,典型的就是 Chrome 中内置的 V8
- JS 引擎逐行读取 JS 代码内容,然后解析成二进制指令,再执行
Node.js:
有了 JS 之后,所看到的网页,其实就相当于一 个web app (就和普通的客户端程序没啥区别了)
JS 和前面的 HTML / CSS 类似,都是运行在浏览器之上
在浏览器中,内置了一个 JS 的执行引擎 (所谓的引擎,就对标了咱们的 JVM)
其中 chrome 浏览器,内置的 JS 引擎,也是谷歌自己研发的,一个非常知名的引擎,叫做 V8
引擎
又有大佬,把这个 V8 引擎单独从浏览器里面摘取出来,重新又包装了一层,就成为了 Node.js
JS 是编程语言
要想执行 JS,需要有一个 JS 的执行引擎
Node.js 这是一个 JS 的运行平台 (对标的是浏览器)
浏览器是运行在客户端的,Node.js 既可以运行在客户端,也可以运行在服务器上 (单独的执行程序),就可以给 JS 赋予客户端开发 / 服务器开发的能力
vue.js 这个只是一个单纯的基于 JS 实现的在浏览器上运行的库 / 框架
JavaScript 的组成:
对于在浏览器上执行的 JS 来说,可以视为是分成三个部分
- ECMAScript (简称 ES):JavaScript 语法
- DOM API :浏览器提供的一组,操作页面元素的 API,页面文档对象模型,对页面中的元素进行操作
- BOM API :浏览器对象模型,浏览器提供的一组,对浏览器窗口进行操作的 API
对于在 Node.js 上执行的 JS 来说,就是分成两个部分:
- JS 核心语法
- Node js 提供的 API (这个 API 和 DOM / BOM 无关了)提供了一些其他的,比如文件操作,网络操作,系统级的操作…
光有 JS 语法,只能写一些基础的逻辑流程
但是要想完成更复杂的任务,完成和浏览器以及页面的交互,那么就需要 DOM API 和 BOM API
这主要指在浏览器端运行的 JS,如果是运行在服务端的 JS , 则需要使用 node.js 的 API,
就不太需要关注 DOM 和 BOM
重要概念:ECMAScript
这是一套 “标准”,无论是啥样的 JS 引擎都要遵守这个标准来实现
啥叫标准? 车同轨,书同文,秦始皇最大的贡献之一,就是制定了一套标准
三流公司做产品,一流公司做标准
二、第一个程序
1、
运行在浏览器中的 JS 是离不开 html 的,通常需要嵌入到 html 中 来进行编写
里面可以放很多种编程语言,但是现在 JS 就是默认的选项
JS 里面的语句后面的
;
不是必须的,可以写也可以不写,但是建议还是写上注意: JS 中字符串常量可以使用单引号表示,也可以使用双引号表示,HTML 中推荐使用双引号,JS 中推荐使用单引号
<>
alert('hello');
</>
2、JavaScript 的书写形式
内嵌式:
把 JS 写到 标签 中
<button onclick="alert('hello')">这是一个按钮</button>
行内式:
把 JS 写到 HTML 元素的内部
外部式:
把 JS 写到一个单独的 .js 文件中,在 HTML 里,通过 来引入
alert('hello app.js');
< src="app.js"></>
注意:这种情况下 标签中间不能写代码,必须空着(写了代码也不会执行),适合代码多的情况
< src="app.js">
alert('hello');
</>
这样只会有一个弹窗,内部的 hello 是没有的,需要分开写
3、注释
JS 的注释:
//
,多行注释 /* */ 不能嵌套CSS 的注释:
/**/
HTML 的注释:
<!---->
4、输入输出
JS 和用户交互,其实有很多种方式
输入:prompt
弹出一个输入框
alert 弹框,体验不好
// 弹出一个输入框
prompt("请输入您的姓名:");
输出:alert
弹出一个警示对话框,输出结果
// 弹出一个输出框
alert("hello");
输出:console.log
在控制台打印一个日志 (供程序员看) – 日志是程序员调试程序的重要手段
重要概念:
console 是一个 js 中的 “对象”
. 表示取对象中的某个属性或者方法. 可以直观理解成 “的”
console.log 就可以理解成: 使用 “控制台” 对象 “的” log 方法
// 向控制台输出日志
console.log("这是一条日志");
开发中,最常用的一种输出方式,会把日志给输出到控制台中
不是系统的黑框框,而是浏览器自己的控制台 (可以在开发者工具中看到),(F12) => Console 标签页 才能看到结果
如果 js 代码中,出现一些语法错误,或者运行时错误,也会在控制台中,显示出来了:没有定义,13 行
三、JavaScript 基础语法
1、类型
1.1、变量
定义变量:
var 变量名 = 初始值;
<>
var num = 10; // 数字类型的变量
var s = 'hello';
var arr = []; // 数组类型的变量
</>
不管什么类型的变量,此时都是统一使用 var
这个关键字来表示的,至于变量具体的类型,取决于初始化的值,是什么类型
JS 不区分 整型 和 浮点型
可以不初始化,不初始化的时候,变量的值是一个特殊的值:``undefined` ,但是不能创建一个未初始化的值,同时指定它是一个字符串类型…
其实,这种风格的写法,是更主流的 (更多的编程语言是这样设定的),包括像C++,Go 这些语言,也支持上述类似的写法
动态类型:
在变量的修改的时候,有个小问题:
如果本来 a 是一个数字类型,在赋值的时候,可以给它赋一个数字类型,也可以赋一个字符串类型,也可以赋任意类型。这个时候 a 变量的类型,也就随之发生改变了
<>
var a = 10;
a = 'hello'
console.log(a);
</>
变量的类型,可以在运行过程中,随着赋值而发生改变,这种行为,称为 “动态类型” (运行时)
Python,PHP,Ruby…
像 Java 这样的语言,不支持这种运行时类型发生改变。这种行为,称为 “静态类型”
C,C++,Java,Go,Rust…
Python 已经发展一些 “投敌” 的苗头了
a = 10
a:int = 10
在 2022 这样的视角来看待,静态类型阵营,完胜!!!
现在业界基本达成共识:认为静态类型比动态类型更好
静态类型,编译器就可以做更多的检查,让有些错误,提前被发现了,开发工具 (IDE),也可以基于类型做更多的分析,提供更加丰富的支持
动态类型也有自己的好处,写一个函数,就可以同时支持多种不同的类型参数 (完全不需要 “重载” / “泛型” 这样的语法)
但是虽然有这个优势,整体来说,还是弊大于利了
let:
现在更倾向于使用 let 来代替 var
var 是旧版本 (早期的设计),有很多地方,其实是违背直觉,确实,还是 let 要比 var 更符合直觉一些
<>
{
var num = 10;
let num2 = 10;
}
console.log(num); // 运行没有问题
console.log(num2);
</>
1.2、基本数据类型
JS 中内置的几种类型:
- number: 数字. 不区分整数和小数.
- boolean: true 真, false 假.
- string: 字符串类型.
- undefined: 只有唯一的值 undefined. 表示未定义的值.
- null: 只有唯一的值 null. 表示 空值
在日常使用二进制数字时往往使用 八进制 和 十六进制 来表示二进制数字
var a = 07; // 八进制整数, 以 0 开头
var b = 0xa; // 十六进制整数, 以 0x 开头
var c = 0b10; // 二进制整数, 以 0b 开头
特殊的数字值:
- Infinity: 无穷大, 大于任何数字. 表示数字已经超过了 JS 能表示的范围.
- Infinity: 负无穷大, 小于任何数字. 表示数字已经超过了 JS 能表示的范围.
- NaN: 表示当前的结果不是一个数字. Not a number
注意:
-
负无穷大 和 无穷小 不是一回事. 无穷小指无限趋近与 0, 值为 1 / Infinity
-
‘hehe’ + 10 得到的不是 NaN,而是 ‘hehe10’,会把数字隐式转成字符串,再进行字符串拼接
-
可以使用 isNaN 函数判定是不是一个非数字
var max = Number.MAX_VALUE;
// 得到 Infinity
console.log(max * 2);
// 得到 -Infinity
console.log(-max * 2);
// 如果运算结果,得到的不是数字的值,就会出现 NaN
console.log('hehe' - 10);
// 和 Java 类似,如果把字符串和数字相加,那么就会得到一个 "字符串拼接" 的效果
console.log('hello' + 10);
console.log(isNaN(10)); // false
console.log(isNaN('hehe' - 10)); // true
1.3、string 字符串类型
单引号 双引号 均可
如果字符串里本身就包含了引号,这个时候,就可以通过 单 双 引号 灵活搭配 的形式,来避免使用转义字符
console.log('my name is "zhangsan"'); // my name is "zhangsan"
求长度:
使用 String 的 length 属性即可。此处这里的长度,单位是"字符"
let s = 'hello world';
console.log(s.length); // 11
s = '你好';
console.log(s.length); // 2
字符串拼接:
注意,要认准相加的变量到底是字符串还是数字
console.log(100 + 100); // 200
console.log('100' + 100); // 100100
1.4、boolean 布尔类型
在 JS 中的布尔类型会被当成 1 和 0 来处理 (Java 里布尔就是布尔,绝对不会和数字混淆)
let a = true;
console.log(1 + a); // 2
这种设定方式,认为其实并不科学!! “隐式类型转换”
如果一个编程语言,越支持隐式类型转换,认为类型越弱 (C,JS,PHP 认为是弱类型的编程语言)
如果一个编程语言,越不支持隐式类型转换,认为类型越强 (Java,Go,Python 认为是强类型的编程语言)
强类型,意味着类型之间的差异更大,界限是更明确的
弱类型,意味着类型之间的差异不大,界限是更模糊的
静态类型 / 动态类型 vs 强类型 / 弱类型是不相关的概念 (正交)
1.5、undefined 未定义数据类型
undefined 这个类型,就只有 undefined 这一个取值
如果一个变量没有被初始化过,结果就是 undefined,是 undefined 类型
let num;
console.log(num); // undefined
未定义的情况 (非法的情况)
JS 未来完全可能设定成,变量不初始化,就直接编译报错
只不过当前 JS 要求的没那么严格,于是就搞了一个特殊的 undefined 这样的值,来表示这个非法情况
在实际开发中,不应该主动使用 undefined,更不应该依赖 undefined
let num2 = null;
console.log(num2); // null
null 表示当前这个值属于 “没有值” 这个概念
属于合法的情况
在实际开发中,使用 null 完全 ok
2、运算符
2.1、算术运算符
除法
/
,JS中,不区分整数和小数,都是 number
console.log(1 / 2); // 0.5
大部分的编程语言 —— 1/2 => 0
但是少数语言例外:JS,Python3 (Python2 不是) —— Dart 结果是 0.5
2.2、比较运算符
JS 中比较相等,有两种风格:
==
!=
(会进行隐式类型转换):只是比较两个变量的值,而不比较两个变量的类型,如果两个变量能够通过隐式类型转换,转成相同的值,此时就认为,也是相等的
=
!
(不会进行隐式类型转换):既要比较变量的值,也要比较类型,如果类型不相同,就直接认为不相等
let a = 10;
let b = '10';
console.log(a == b); // true
console.log(a === b); // false
谈到比较两个对象,有三个维度的比较:
- 比较身份 (是不是同一个对象)
- 比较值 (对象里存储的数据是否相同)
- 比较类型 (两个对象是否是同一个类型)
Java == 比较身份
equals 可以被重写,不重写默认也是比较身份,通过重写来设定成比较值 instanceof 比较类型
3、逻辑与 逻辑或
&& || 和 Java 中的差别挺大的
Java 中的 && 和 || 行为非常简单,就是返回一个 true 或者 false
JS 中的 && 和 || 返回的是其中的一个表达式
-
c = a || b
如果 a 的值,为真 (非0),此时 c 的值,就是表达式 a 的值
如果 a 的值,为假 (为0),此时 c 的值,就是表达式 b 的值 -
C = a && b
如果 a 的值,为假,此时 c 的值,就是表达式 a 的值,
如果 a 的值,为真,此时 c 的值,就是表达式 b 的值
衍生出了 JS 中的一种习惯用法,判定变量是否为 “空值”,如果是空值,则赋予一个默认值
4、条件语句
案例:判定一个数字是奇数还是偶数
let num = 10;
if (num % 2 == 0) {
console.log('num 是偶数');
} else {
console.log('num 是奇数');
}
注意! 不能写成 num % 2 == 1 就是奇数,负的奇数 % 2 结果可能是 -1
5、数组
5.1、数组的创建
这种写法,更像是 Java 中创建了一个对象 (很少使用)
let arr = new Array();
使用字面量方式创建 [常用]
let arr = [];
let arr2 = [1, 2, 3];
这里和 Java 差别很大,Java 这里定义数组的时候, int[] 类型,Java 针对数组初始化,使用的 {}
而 JS 使用 []
注意:JS 的数组不要求元素是相同类型 。这一点和 C,C++,Java 等静态类型的语言差别很大,但是 Python, PHP 等动态类型语言也是如此
在 Java 里面,int[] 整型数组,String[] 字符串数组。在 JS 中,就只有一种数组,就叫数组
let arr = [10, 'hello', true, undefined, null, []];
5.2、打印数组:
JS 中直接通过 console.log 就可以打印数组内容
let arr = [10, 'hello', true, undefined, null, []];
console.log(arr);
5.3、获取数组元素
通过下标来获取元素:下标也是从 0 开始计算的
注意:
1、按照之前的理解, Java 中要求数组下标,是 0 -> length - 1范围,如果超出范围,就会抛出一个异常
JS 并非如此,当我们访问一个超出范围的下标的时候,得到的结果是 undefined,并不会出现异常
let arr = [1, 2, 3];
arr[100] = 10;
console.log(arr);
长度变成了 101,然后数组的内容,前 3 个元素还是 1,2,3,下标为 100 的元素是 10,但是中间的空属性 * 96,意思就是中间这些元素仍然是 undefined
2、arr[-1] = 10;
此时 -1 与其说是一个下标,不如说是一个 “属性”,并没有影响到数组的长度
3、arr[‘hello’] = 10;
- JS 中的数组,不是一个正经数组!!!
- 除了能接数组的活,还能接 map 的活 (表示键值对) => 这里说数组是一个map,更准确的说,是一个"对象”
- 在 JS 里,是可以在运行时,给对象新增属性的
- arr[‘hello’] 就是在给 arr 这个对象,新增了一个属性,属性的名字是 hello,属性的值是 10
let arr = [1, 2, 3];
arr.hello = 30;
console.log(arr);
console.log(arr['hello']); // 1
console.log(arr.hello); // 2
这些语法都不是 JS 独有的,动态类型的语言都是这样设定的,(PHP 的设定方式几乎和 JS 一模一样,但是 Python 略有差别,本质不变)
上述这些代码,都是属于 “静态类型” vs “动态类型” 两个流派之间的碰撞
5.4、获取数组长度
- 通过
.length
就能获取到,- JS 里, length 属性是可以改的
let arr = [1, 2, 3];
arr.length = 5;
console.log(arr.length);
console.log(arr);
运行结果:5
test.html:32 (5) [1, 2, 3, empty × 2]
5.5、往数组中新增元素
最常见的插入操作:push 方法,能够给数组末尾追加一个元素 (Java ArrayList 里面的 add 操作)
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(i);
}
console.log(arr);
5.6、删除数组中的元素
splice
这个方法,针对数组中的某个区间进行替换,既可以用于删除,也可以用于插入元素
let arr = [1, 2, 3, 4, 5, 6];
arr.splice(2, 3); // 从下标为 2 的位置,删除 3 个元素
console.log(arr); // [1, 2, 6]
let fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2, 0, "Lemon", "Kiwi");
console.log(fruits); // ['Banana', 'Orange', 'Lemon', 'Kiwi', 'Apple', 'Mango']
6、函数
6.1、语法格式
函数(function),方法(method),同一个东西,不同的马甲
通常情况下,不去考量这两个概念的区别,但是如果非要考量,可以这样理解:
- 函数:是和 “对象” 独立开来的代码片段
-
方法:是依托于对象的代码片段, 方法=>成员函数
在 Java 中,由于本身这样的代码片段都是依托于类和对象的,因此 Java 中就都是 “方法”
JS 中的函数形参列表,不必写形参类型,也不必写返回值类型
// 创建函数/函数声明/函数定义
function 函数名(形参列表) {
函数体
return 返回值;
}
// 函数调用
函数名(实参列表) // 不考虑返回值
返回值 = 函数名(实参列表) // 考虑返回值
函数定义并不会执行函数体内容,必须要调用才会执行,调用几次就会执行几次
function hello() {
console.log("hello");
}
// 如果不调用函数, 则没有执行打印语句
hello();
6.2、关于参数个数
实参和形参之间的个数可以不匹配,但是实际开发一般要求形参和实参个数要匹配
function add(x, y) {
return x + y;
}
let result = add(10, 20);
console.log(result); // 30
result = add('hello', 'world');
console.log(result); // helloworld
result = add('hello', 10);
console.log(result); // hello10
- 给函数指定参数的时候,是不需要写参数类型的,传入的实参类型是可以任意的,只要能够支持函数内部的逻辑 (比如此处要求两个参数能进行相加即可)
- 正因为有这样的特性,JS 天然就是不需要 “泛型” / “重载” 这样的语法的
一般写代码的时候,都是需要调用时候传入的实参,个数和形参匹配。但是实际上,JS 并没有在语法上做出这样的限制
如果实参和形参个数不一样,也不是完全不行!!!
像上面的 add 方法,如果要想实现一个任意个数字的相加,其实不必写多个版本的代码
最多可以支持 7 个参数的相加:
function add(a, b, c, d, e, f, g) {
return a + b + c + d + e + f + g;
}
console.log(add(10, 20)); // NaN
console.log(add(10, 20, 30)); // NaN
console.log(add(1, 2, 3, 4, 5, 6, 7)); // 28
没被传参传到的形参,就是 undefined
如果你想让你的代码,既能支持数字相加,又能支持字符串相加,这个时候就少不了要判定了
判定第一个参数,是字符串还是数字 (JS,typeof),通过第一个参数的类型,来决定后续参数的默认值,是 0,还是 “”
如果实参比形参多,多出来的实参,相当于就没了 (函数内部就拿不到了)
此处的参数个数可变这种操作,不属于 “动态类型” 通用属性,而是属于 JS 的专属属性
这样做的目的,是为了让代码,更 “灵活”,但是大多数情况下,“灵活” 是贬义词
6.3、函数表达式
在 JS 中,函数 是 "一等公民” 函数,就像一个普通的变量一样,可以被赋值给其他的变量,也可以作为另一个函数的参数,还可以作为另一个函数的返回值… (函数和普通的变量,并没有本质区别,但是函数这样的变量相比于普通变量多了个功能:可调用) 。所谓的函数表达式,其实就是把一个函数赋值给一个变量了
function hello() {
console.log('hello');
}
let f = hello;
hello 是一个函数名,hello 后面没有 (),说明这个操作,不是函数调用,而只是一个单纯的函数的赋值
f 是 function 类型的变量
console.log(typeof(hello)); // function
console.log(typeof hello); // function
通过 f 也能够调用到上面的 hello 函数:
function hello() {
console.log('hello');
}
let f = hello;
f(); // hello
两个操作可以合为一个操作,既定义了 hello 这个函数,又把 hello 赋值给了 f
let f = function() {
console.log('hello');
}
再进一步,就可以把 hello 给省略,此时就是完全依赖 f 来找到该函数
let f = function() {
console.log('hello');
}
定义了一个没有名字的函数,并且把这个函数赋值给了 f 这个变量,后面就可以基于 f 来调用这个函数了
没有名字的函数,也称为 “匿名函数”,还有另一个名字:lambda 表达式
7、作用域
某个标识符名字在代码中的有效范围,在 ES6 标准之前,作用域主要分成两个:
- 全局作用域:在整个 标签中,或者单独的 js 文件中生效
- 局部作用域 / 函数作用域:在函数内部生效
ES6:
ES6 其实是 JS 的版本
JS 原名 ECMAScript,也可以简称为 ES,JS 的版本都是用 ES 数字来表示的
JS 中的最新版本,也是到 ES 十几,ES6 的地位就相当于 Java 8 在 java 中的地位
类似于 Java 的版本:Java 8. => Java 18
在 ES6 之前,没有块级作用域
在ES6之后,引入了 let,也就有了块级作用域,一个变量在 {} 内部定义,是无法被 {} 访问的
注意: 在 JS 里面,{} 内部的代码,是可以访问到 {} 外部的变量的
let num = 10;
function hello() {
console.log(num); // 10
}
hello(); // 10
let num = 10;
function hello2() {
function hello() {
console.log(num);
}
hello(); // 10
}
hello2();
JS 中的作用域,是支持 “逐级向上” 查找的
作用域链:内部函数可以访问外部函数的变量,采取的是链式查找的方式,从内到外依次进行查找
当前是在 hello 这个函数里,尝试打印 num,但是在 hello 中没有 num 变量
于是就往上级找,找到了 hello2 这里,但是在 hello2 里,也没有 num
于是继续往上找,找到了全局作用域,找到了 num
如果最后的全局作用域也没找到,就会报错 ——
如果在作用域链的不同节点上,存在多个同名的变量怎么办?
从里往外找,先找到谁就算谁
let num = 10;
function hello2() {
let num = 20;
function hello() {
console.log(num);
}
hello(); // 20
}
hello2();
基于 JS 实现二分查找
function binarySearch(arr, key) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
// JS 除法 向上取整
let mid = Math.ceil((left + right) / 2);
if (arr[mid] > key) {
right = mid - 1;
} else if (arr[mid] < key) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let key = 3;
let ans = binarySearch(arr, key);
if (ans != -1) {
console.log(key + ' 的下标是:' + ans); // 3 的下标是:2
} else {
throw new Error('没有此数');
}
</>
冒泡排序
<>
function bubbleSort(nums) {
for (let i = 0; i < nums.length - 1; i++) {
let flg = true;
for (let j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flg = false;
}
}
if (flg) {
break;
}
}
}
let nums = [2, 3, 15, 11, 21 ,7, 1];
bubbleSort(nums);
console.log(nums);
</>
快速排序
<>
function quickSort(array) {
quick(array, 0, array.length - 1);
}
function quick(array, left, right) {
if (left >= right) {
return;
}
let pivot = partition(array, left, right);
quick(array, left, pivot - 1);
quick(array, pivot + 1, right);
}
function partition(array, left, right) {
let tmp = array[left];
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];
}
array[left] = tmp; // left right 相遇 就是基准
return left;
}
let array = [2, 3, 15, 11, 21, 7, 1];
quickSort(array);
console.log(array);
</>
四、对象
1、使用 字面量 创建对象 [常用]
- 对象,就是一些属性和方法的集合
- 在 Java 中,对象,需要先有类,然后针对类的实例化才产生对象
Java 中,类可以视为是一种自定义的类型,(Cat类 和 Dog类 两个不同的类型)- JS 中,对象是不依托于类的,在 JS 中所有的对象,都是一个类型 .object
在 JS 中,直接通过 {} 的方式,就可以创建对象:
let a = {}; // 创建了一个空的对象
let student = {
name: '张三',
age: 20,
height: 180,
weight: 120,
sing: function() {
console.log('sing()');
},
jump: function() {
console.log('jump()');
}
}
- 使用 { } 创建对象
- 属性和方法使用键值对的形式来组织
- 键值对之间使用
,
分割, 最后一个属性后面的,可有可无 - 键和值之间使用
:
分割 - 方法的值是一个匿名函数
后续就可以根据 student.属性名 方式来进行访问了
// 1. 使用 . 成员访问运算符来访问属性 `.` 可以理解成 "的"
console.log(student.name);
// 2. 使用 [ ] 访问属性, 此时属性需要加上引号
console.log(student['height']);
// 3. 调用方法, 别忘记加上 ()
student.sayHello();
2、使用 new Object 创建对象
注意,使用 {} 创建的对象也可以随时使用 student.name = "...";
这样的方式来新增属性
var student = new Object(); // 和创建数组类似
// JS 中一个对象中有哪些成员,也都是动态可以改变的
student.name = "zhangsan";
student.height = 175;
student['weight'] = 170;
student.sayHello = function () {
console.log("hello");
}
console.log(student.name);
console.log(student['weight']);
student.sayHello();
3、使用 构造函数 创建对象
前面的创建对象方式只能创建一个对象,而使用构造函数可以很方便地创建多个对象
例如: 创建几个猫咪对象
var mimi = {
name: "咪咪",
type: "中华田园喵",
miao: function () {
console.log("喵");
}
};
var xiaohei = {
name: "小黑",
type: "波斯喵",
miao: function () {
console.log("猫呜");
}
}
var ciqiu = {
name: "刺球",
type: "金渐层",
miao: function () {
console.log("咕噜噜");
}
}
此时写起来就比较麻烦,使用构造函数可以把相同的属性和方法的创建提取出来,简化开发过程
基本语法:
当前咱们所说的构造函数,其实是一种特殊的函数为了批量的创建出一组类似的对象
function 构造函数名(形参) {
this.属性 = 值;
this.方法 = function...
}
var obj = new 构造函数名(实参);
当看到函数内部,通过 this. 这样的方式来创建属性的时候,此时这个函数大概率就是构造函数了
new 后面的名字,是构造函数的名字,不是 “类” 的名字
注意:
- 在构造函数内部使用 this 关键字来表示当前正在构建的对象
- 构造函数的函数名首字母一般是大写的
- 构造函数的函数名可以是名词
- 构造函数不需要 return
- 创建对象的时候必须使用 new 关键字
使用构造函数重新创建猫咪对象:
function Cat(name, type, sound) {
this.name = name;
this.type = type;
this.miao = function () {
console.log(sound); // 别忘了作用域的链式访问规则
}
}
var mimi = new Cat('咪咪', '中华田园喵', '喵');
var xiaohei = new Cat('小黑', '波斯喵', '猫呜');
var ciqiu = new Cat('刺球', '金渐层', '咕噜噜');
console.log(mimi);
mimi.miao();
4、JavaScript 的对象和 Java 的对象的区别
其实 JS 中后两种创建对象的方式,JS 自己也是觉得比较别扭的,以第一种写法为主
JS 没有 class 这个概念,适用于 ES6 之前
ES6 之后,也引入了 class 关键字,可以让定义对象,就更接近于 Java 了,但是这种方式仍然没有第一种方法简单
而且第一种对象的定义写法,后来就火了,出圈了,不仅仅 JS 会用,其他各种语言,在各种场景下,都会用到 .JSON (数据的组织格式)
因此,在 JS 中也是有一些现成的对象的
像数组,本质上也可以视为是一个对象
JS 虽然有对象,但是 JS 算是一个面向对象的编程语言嘛?咱们一般认为,不算!!
JS 里没有封装,继承,多态,这样的一些面向对象的典型特性
JS 中没有原生提供继承这种机制的
但是 JS 里有一个曲线救国的方法:“原型”,基于原型机制,可以模拟实现一个类似于继承的效果 :把一个对象的所有属性,给自动的加入到一个新的对象中……
一、初识 JavaScript
JavaScript (简称 JS)
是世界上最流行的编程语言之一
是一个脚本语言,通过解释器运行
虽然 js 主要是用于前端页面的开发,但是实际上,也可以进行服务器开发 / 客户端程序的开发
JavaScript 最初只是为了完成简单的表单验证(验证数据合法性),结果后来不小心就火了
当前 JavaScript 已经成为了一个通用的编程语言
JavaScript 的能做的事情:
- 网页开发(更复杂的特效和用户交互)
- 网页游戏开发
- 服务器开发(node.js)
- 桌面程序开发(Electron, VSCode 就是这么来的)
- 手机 app 开发
JavaScript 之父 布兰登·艾奇 (Brendan Eich)
曾经的布兰登:
发明 JavaScript 之后的布兰登:
1995 年,用 10 天 时间完成 JS 的设计 (由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,Java 写出来的程序混乱不堪)
最初在网景公司,命名为 LiveScript,
一般认为,当时 Netscape 之所以将 LiveScript 命名为 JavaScript,是因为 Java 是当时最流行的
编程语言,带有 “Java” 的名字有助于这门新生语言的传播。
其实 Java 和 JavaScript 之间的语法风格相去甚远
JavaScript 发展历史可以参考阮一峰大神的博客
http://www.ruanyifeng.com/blog/2011/06/birth_of_java.html
JavaScrip:
-
HTML
: 网页的结构(骨) -
CSS
: 网页的表现(皮) -
JavaScript
: 网页的行为(灵魂)
前端开发 JS 是一家独大的
挑战者:
- Dart (google 搞的,下一代移动端开发平台上面自带的编程语言)
- Web Assembly.类似于 “汇编语言”,在浏览器上定义了一组基础指令,可以通过一些第三方工具,把其他的主流编程语言,给转换成 WebAssemblyPython中的所有运算符号。这时候 C++,Java,Python… 都可以通过这个途径来实现页面的开发了…
- TypeScript [最有希望的挑战者],也是目前进展非常迅猛的一个大将
JS 和 TS 的关系,就类似于 C 和 C++ 的关系,“兼容”
TS 完全支持 JS 当下的语法,并且有引入了一些新的语法规则,让程序猿写的更爽,这个 TS 的吸引力,对于现有的这些前端开发程序猿来说还是非常大的 [挖墙脚]
但是虽然如此,TS 至今仍然还没有彻底的取代 JS,前端这个圈子里,仍然是 JS 为主
JavaScript 运行过程:
- 编写的代码是保存在文件中的,也就是存储在硬盘 (外存上)
- 双击
.html
文件浏览器 (应用程序) 就会读取文件,把文件内容加载到内存中 (数据流向:硬盘 => 内存) - 浏览器会解析用户编写的代码,把代码翻译成二进制的,能让计算机识别的指令 (解释器的工作)
- 得到的二进制指令会被 CPU 加载并执行 (数据流向:内存 => CPU)
浏览器分成渲染引擎 + JS 引擎:
- 渲染引擎:解析 html + CSS,俗称 “内核”
- JS 引擎:也就是 JS 解释器,典型的就是 Chrome 中内置的 V8
- JS 引擎逐行读取 JS 代码内容,然后解析成二进制指令,再执行
Node.js:
有了 JS 之后,所看到的网页,其实就相当于一 个web app (就和普通的客户端程序没啥区别了)
JS 和前面的 HTML / CSS 类似,都是运行在浏览器之上
在浏览器中,内置了一个 JS 的执行引擎 (所谓的引擎,就对标了咱们的 JVM)
其中 chrome 浏览器,内置的 JS 引擎,也是谷歌自己研发的,一个非常知名的引擎,叫做 V8
引擎
又有大佬,把这个 V8 引擎单独从浏览器里面摘取出来,重新又包装了一层,就成为了 Node.js
JS 是编程语言
要想执行 JS,需要有一个 JS 的执行引擎
Node.js 这是一个 JS 的运行平台 (对标的是浏览器)
浏览器是运行在客户端的,Node.js 既可以运行在客户端,也可以运行在服务器上 (单独的执行程序),就可以给 JS 赋予客户端开发 / 服务器开发的能力
vue.js 这个只是一个单纯的基于 JS 实现的在浏览器上运行的库 / 框架
JavaScript 的组成:
对于在浏览器上执行的 JS 来说,可以视为是分成三个部分
- ECMAScript (简称 ES):JavaScript 语法
- DOM API :浏览器提供的一组,操作页面元素的 API,页面文档对象模型,对页面中的元素进行操作
- BOM API :浏览器对象模型,浏览器提供的一组,对浏览器窗口进行操作的 API
对于在 Node.js 上执行的 JS 来说,就是分成两个部分:
- JS 核心语法
- Node js 提供的 API (这个 API 和 DOM / BOM 无关了)提供了一些其他的,比如文件操作,网络操作,系统级的操作…
光有 JS 语法,只能写一些基础的逻辑流程
但是要想完成更复杂的任务,完成和浏览器以及页面的交互,那么就需要 DOM API 和 BOM API
这主要指在浏览器端运行的 JS,如果是运行在服务端的 JS , 则需要使用 node.js 的 API,
就不太需要关注 DOM 和 BOM
重要概念:ECMAScript
这是一套 “标准”,无论是啥样的 JS 引擎都要遵守这个标准来实现
啥叫标准? 车同轨,书同文,秦始皇最大的贡献之一,就是制定了一套标准
三流公司做产品,一流公司做标准
二、第一个程序
1、
运行在浏览器中的 JS 是离不开 html 的,通常需要嵌入到 html 中 来进行编写
里面可以放很多种编程语言,但是现在 JS 就是默认的选项
JS 里面的语句后面的
;
不是必须的,可以写也可以不写,但是建议还是写上注意: JS 中字符串常量可以使用单引号表示,也可以使用双引号表示,HTML 中推荐使用双引号,JS 中推荐使用单引号
<>
alert('hello');
</>
2、JavaScript 的书写形式
内嵌式:
把 JS 写到 标签 中
<button onclick="alert('hello')">这是一个按钮</button>
行内式:
把 JS 写到 HTML 元素的内部
外部式:
把 JS 写到一个单独的 .js 文件中,在 HTML 里,通过 来引入
alert('hello app.js');
< src="app.js"></>
注意:这种情况下 标签中间不能写代码,必须空着(写了代码也不会执行),适合代码多的情况
< src="app.js">
alert('hello');
</>
这样只会有一个弹窗,内部的 hello 是没有的,需要分开写
3、注释
JS 的注释:
//
,多行注释 /* */ 不能嵌套CSS 的注释:
/**/
HTML 的注释:
<!---->
4、输入输出
JS 和用户交互,其实有很多种方式
输入:prompt
弹出一个输入框
alert 弹框,体验不好
// 弹出一个输入框
prompt("请输入您的姓名:");
输出:alert
弹出一个警示对话框,输出结果
// 弹出一个输出框
alert("hello");
输出:console.log
在控制台打印一个日志 (供程序员看) – 日志是程序员调试程序的重要手段
重要概念:
console 是一个 js 中的 “对象”
. 表示取对象中的某个属性或者方法. 可以直观理解成 “的”
console.log 就可以理解成: 使用 “控制台” 对象 “的” log 方法
// 向控制台输出日志
console.log("这是一条日志");
开发中,最常用的一种输出方式,会把日志给输出到控制台中
不是系统的黑框框,而是浏览器自己的控制台 (可以在开发者工具中看到),(F12) => Console 标签页 才能看到结果
如果 js 代码中,出现一些语法错误,或者运行时错误,也会在控制台中,显示出来了:没有定义,13 行
三、JavaScript 基础语法
1、类型
1.1、变量
定义变量:
var 变量名 = 初始值;
<>
var num = 10; // 数字类型的变量
var s = 'hello';
var arr = []; // 数组类型的变量
</>
不管什么类型的变量,此时都是统一使用 var
这个关键字来表示的,至于变量具体的类型,取决于初始化的值,是什么类型
JS 不区分 整型 和 浮点型
可以不初始化,不初始化的时候,变量的值是一个特殊的值:``undefined` ,但是不能创建一个未初始化的值,同时指定它是一个字符串类型…
其实,这种风格的写法,是更主流的 (更多的编程语言是这样设定的),包括像C++,Go 这些语言,也支持上述类似的写法
动态类型:
在变量的修改的时候,有个小问题:
如果本来 a 是一个数字类型,在赋值的时候,可以给它赋一个数字类型,也可以赋一个字符串类型,也可以赋任意类型。这个时候 a 变量的类型,也就随之发生改变了
<>
var a = 10;
a = 'hello'
console.log(a);
</>
变量的类型,可以在运行过程中,随着赋值而发生改变,这种行为,称为 “动态类型” (运行时)
Python,PHP,Ruby…
像 Java 这样的语言,不支持这种运行时类型发生改变。这种行为,称为 “静态类型”
C,C++,Java,Go,Rust…
Python 已经发展一些 “投敌” 的苗头了
a = 10
a:int = 10
在 2022 这样的视角来看待,静态类型阵营,完胜!!!
现在业界基本达成共识:认为静态类型比动态类型更好
静态类型,编译器就可以做更多的检查,让有些错误,提前被发现了,开发工具 (IDE),也可以基于类型做更多的分析,提供更加丰富的支持
动态类型也有自己的好处,写一个函数,就可以同时支持多种不同的类型参数 (完全不需要 “重载” / “泛型” 这样的语法)
但是虽然有这个优势,整体来说,还是弊大于利了
let:
现在更倾向于使用 let 来代替 var
var 是旧版本 (早期的设计),有很多地方,其实是违背直觉,确实,还是 let 要比 var 更符合直觉一些
<>
{
var num = 10;
let num2 = 10;
}
console.log(num); // 运行没有问题
console.log(num2);
</>
1.2、基本数据类型
JS 中内置的几种类型:
- number: 数字. 不区分整数和小数.
- boolean: true 真, false 假.
- string: 字符串类型.
- undefined: 只有唯一的值 undefined. 表示未定义的值.
- null: 只有唯一的值 null. 表示 空值
在日常使用二进制数字时往往使用 八进制 和 十六进制 来表示二进制数字
var a = 07; // 八进制整数, 以 0 开头
var b = 0xa; // 十六进制整数, 以 0x 开头
var c = 0b10; // 二进制整数, 以 0b 开头
特殊的数字值:
- Infinity: 无穷大, 大于任何数字. 表示数字已经超过了 JS 能表示的范围.
- Infinity: 负无穷大, 小于任何数字. 表示数字已经超过了 JS 能表示的范围.
- NaN: 表示当前的结果不是一个数字. Not a number
注意:
-
负无穷大 和 无穷小 不是一回事. 无穷小指无限趋近与 0, 值为 1 / Infinity
-
‘hehe’ + 10 得到的不是 NaN,而是 ‘hehe10’,会把数字隐式转成字符串,再进行字符串拼接
-
可以使用 isNaN 函数判定是不是一个非数字
var max = Number.MAX_VALUE;
// 得到 Infinity
console.log(max * 2);
// 得到 -Infinity
console.log(-max * 2);
// 如果运算结果,得到的不是数字的值,就会出现 NaN
console.log('hehe' - 10);
// 和 Java 类似,如果把字符串和数字相加,那么就会得到一个 "字符串拼接" 的效果
console.log('hello' + 10);
console.log(isNaN(10)); // false
console.log(isNaN('hehe' - 10)); // true
1.3、string 字符串类型
单引号 双引号 均可
如果字符串里本身就包含了引号,这个时候,就可以通过 单 双 引号 灵活搭配 的形式,来避免使用转义字符
console.log('my name is "zhangsan"'); // my name is "zhangsan"
求长度:
使用 String 的 length 属性即可。此处这里的长度,单位是"字符"
let s = 'hello world';
console.log(s.length); // 11
s = '你好';
console.log(s.length); // 2
字符串拼接:
注意,要认准相加的变量到底是字符串还是数字
console.log(100 + 100); // 200
console.log('100' + 100); // 100100
1.4、boolean 布尔类型
在 JS 中的布尔类型会被当成 1 和 0 来处理 (Java 里布尔就是布尔,绝对不会和数字混淆)
let a = true;
console.log(1 + a); // 2
这种设定方式,认为其实并不科学!! “隐式类型转换”
如果一个编程语言,越支持隐式类型转换,认为类型越弱 (C,JS,PHP 认为是弱类型的编程语言)
如果一个编程语言,越不支持隐式类型转换,认为类型越强 (Java,Go,Python 认为是强类型的编程语言)
强类型,意味着类型之间的差异更大,界限是更明确的
弱类型,意味着类型之间的差异不大,界限是更模糊的
静态类型 / 动态类型 vs 强类型 / 弱类型是不相关的概念 (正交)
1.5、undefined 未定义数据类型
undefined 这个类型,就只有 undefined 这一个取值
如果一个变量没有被初始化过,结果就是 undefined,是 undefined 类型
let num;
console.log(num); // undefined
未定义的情况 (非法的情况)
JS 未来完全可能设定成,变量不初始化,就直接编译报错
只不过当前 JS 要求的没那么严格,于是就搞了一个特殊的 undefined 这样的值,来表示这个非法情况
在实际开发中,不应该主动使用 undefined,更不应该依赖 undefined
let num2 = null;
console.log(num2); // null
null 表示当前这个值属于 “没有值” 这个概念
属于合法的情况
在实际开发中,使用 null 完全 ok
2、运算符
2.1、算术运算符
除法
/
,JS中,不区分整数和小数,都是 number
console.log(1 / 2); // 0.5
大部分的编程语言 —— 1/2 => 0
但是少数语言例外:JS,Python3 (Python2 不是) —— Dart 结果是 0.5
2.2、比较运算符
JS 中比较相等,有两种风格:
==
!=
(会进行隐式类型转换):只是比较两个变量的值,而不比较两个变量的类型,如果两个变量能够通过隐式类型转换,转成相同的值,此时就认为,也是相等的
=
!
(不会进行隐式类型转换):既要比较变量的值,也要比较类型,如果类型不相同,就直接认为不相等
let a = 10;
let b = '10';
console.log(a == b); // true
console.log(a === b); // false
谈到比较两个对象,有三个维度的比较:
- 比较身份 (是不是同一个对象)
- 比较值 (对象里存储的数据是否相同)
- 比较类型 (两个对象是否是同一个类型)
Java == 比较身份
equals 可以被重写,不重写默认也是比较身份,通过重写来设定成比较值 instanceof 比较类型
3、逻辑与 逻辑或
&& || 和 Java 中的差别挺大的
Java 中的 && 和 || 行为非常简单,就是返回一个 true 或者 false
JS 中的 && 和 || 返回的是其中的一个表达式
-
c = a || b
如果 a 的值,为真 (非0),此时 c 的值,就是表达式 a 的值
如果 a 的值,为假 (为0),此时 c 的值,就是表达式 b 的值 -
C = a && b
如果 a 的值,为假,此时 c 的值,就是表达式 a 的值,
如果 a 的值,为真,此时 c 的值,就是表达式 b 的值
衍生出了 JS 中的一种习惯用法,判定变量是否为 “空值”,如果是空值,则赋予一个默认值
4、条件语句
案例:判定一个数字是奇数还是偶数
let num = 10;
if (num % 2 == 0) {
console.log('num 是偶数');
} else {
console.log('num 是奇数');
}
注意! 不能写成 num % 2 == 1 就是奇数,负的奇数 % 2 结果可能是 -1
5、数组
5.1、数组的创建
这种写法,更像是 Java 中创建了一个对象 (很少使用)
let arr = new Array();
使用字面量方式创建 [常用]
let arr = [];
let arr2 = [1, 2, 3];
这里和 Java 差别很大,Java 这里定义数组的时候, int[] 类型,Java 针对数组初始化,使用的 {}
而 JS 使用 []
注意:JS 的数组不要求元素是相同类型 。这一点和 C,C++,Java 等静态类型的语言差别很大,但是 Python, PHP 等动态类型语言也是如此
在 Java 里面,int[] 整型数组,String[] 字符串数组。在 JS 中,就只有一种数组,就叫数组
let arr = [10, 'hello', true, undefined, null, []];
5.2、打印数组:
JS 中直接通过 console.log 就可以打印数组内容
let arr = [10, 'hello', true, undefined, null, []];
console.log(arr);
5.3、获取数组元素
通过下标来获取元素:下标也是从 0 开始计算的
注意:
1、按照之前的理解, Java 中要求数组下标,是 0 -> length - 1范围,如果超出范围,就会抛出一个异常
JS 并非如此,当我们访问一个超出范围的下标的时候,得到的结果是 undefined,并不会出现异常
let arr = [1, 2, 3];
arr[100] = 10;
console.log(arr);
长度变成了 101,然后数组的内容,前 3 个元素还是 1,2,3,下标为 100 的元素是 10,但是中间的空属性 * 96,意思就是中间这些元素仍然是 undefined
2、arr[-1] = 10;
此时 -1 与其说是一个下标,不如说是一个 “属性”,并没有影响到数组的长度
3、arr[‘hello’] = 10;
- JS 中的数组,不是一个正经数组!!!
- 除了能接数组的活,还能接 map 的活 (表示键值对) => 这里说数组是一个map,更准确的说,是一个"对象”
- 在 JS 里,是可以在运行时,给对象新增属性的
- arr[‘hello’] 就是在给 arr 这个对象,新增了一个属性,属性的名字是 hello,属性的值是 10
let arr = [1, 2, 3];
arr.hello = 30;
console.log(arr);
console.log(arr['hello']); // 1
console.log(arr.hello); // 2
这些语法都不是 JS 独有的,动态类型的语言都是这样设定的,(PHP 的设定方式几乎和 JS 一模一样,但是 Python 略有差别,本质不变)
上述这些代码,都是属于 “静态类型” vs “动态类型” 两个流派之间的碰撞
5.4、获取数组长度
- 通过
.length
就能获取到,- JS 里, length 属性是可以改的
let arr = [1, 2, 3];
arr.length = 5;
console.log(arr.length);
console.log(arr);
运行结果:5
test.html:32 (5) [1, 2, 3, empty × 2]
5.5、往数组中新增元素
最常见的插入操作:push 方法,能够给数组末尾追加一个元素 (Java ArrayList 里面的 add 操作)
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(i);
}
console.log(arr);
5.6、删除数组中的元素
splice
这个方法,针对数组中的某个区间进行替换,既可以用于删除,也可以用于插入元素
let arr = [1, 2, 3, 4, 5, 6];
arr.splice(2, 3); // 从下标为 2 的位置,删除 3 个元素
console.log(arr); // [1, 2, 6]
let fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2, 0, "Lemon", "Kiwi");
console.log(fruits); // ['Banana', 'Orange', 'Lemon', 'Kiwi', 'Apple', 'Mango']
6、函数
6.1、语法格式
函数(function),方法(method),同一个东西,不同的马甲
通常情况下,不去考量这两个概念的区别,但是如果非要考量,可以这样理解:
- 函数:是和 “对象” 独立开来的代码片段
-
方法:是依托于对象的代码片段, 方法=>成员函数
在 Java 中,由于本身这样的代码片段都是依托于类和对象的,因此 Java 中就都是 “方法”
JS 中的函数形参列表,不必写形参类型,也不必写返回值类型
// 创建函数/函数声明/函数定义
function 函数名(形参列表) {
函数体
return 返回值;
}
// 函数调用
函数名(实参列表) // 不考虑返回值
返回值 = 函数名(实参列表) // 考虑返回值
函数定义并不会执行函数体内容,必须要调用才会执行,调用几次就会执行几次
function hello() {
console.log("hello");
}
// 如果不调用函数, 则没有执行打印语句
hello();
6.2、关于参数个数
实参和形参之间的个数可以不匹配,但是实际开发一般要求形参和实参个数要匹配
function add(x, y) {
return x + y;
}
let result = add(10, 20);
console.log(result); // 30
result = add('hello', 'world');
console.log(result); // helloworld
result = add('hello', 10);
console.log(result); // hello10
- 给函数指定参数的时候,是不需要写参数类型的,传入的实参类型是可以任意的,只要能够支持函数内部的逻辑 (比如此处要求两个参数能进行相加即可)
- 正因为有这样的特性,JS 天然就是不需要 “泛型” / “重载” 这样的语法的
一般写代码的时候,都是需要调用时候传入的实参,个数和形参匹配。但是实际上,JS 并没有在语法上做出这样的限制
如果实参和形参个数不一样,也不是完全不行!!!
像上面的 add 方法,如果要想实现一个任意个数字的相加,其实不必写多个版本的代码
最多可以支持 7 个参数的相加:
function add(a, b, c, d, e, f, g) {
return a + b + c + d + e + f + g;
}
console.log(add(10, 20)); // NaN
console.log(add(10, 20, 30)); // NaN
console.log(add(1, 2, 3, 4, 5, 6, 7)); // 28
没被传参传到的形参,就是 undefined
如果你想让你的代码,既能支持数字相加,又能支持字符串相加,这个时候就少不了要判定了
判定第一个参数,是字符串还是数字 (JS,typeof),通过第一个参数的类型,来决定后续参数的默认值,是 0,还是 “”
如果实参比形参多,多出来的实参,相当于就没了 (函数内部就拿不到了)
此处的参数个数可变这种操作,不属于 “动态类型” 通用属性,而是属于 JS 的专属属性
这样做的目的,是为了让代码,更 “灵活”,但是大多数情况下,“灵活” 是贬义词
6.3、函数表达式
在 JS 中,函数 是 "一等公民” 函数,就像一个普通的变量一样,可以被赋值给其他的变量,也可以作为另一个函数的参数,还可以作为另一个函数的返回值… (函数和普通的变量,并没有本质区别,但是函数这样的变量相比于普通变量多了个功能:可调用) 。所谓的函数表达式,其实就是把一个函数赋值给一个变量了
function hello() {
console.log('hello');
}
let f = hello;
hello 是一个函数名,hello 后面没有 (),说明这个操作,不是函数调用,而只是一个单纯的函数的赋值
f 是 function 类型的变量
console.log(typeof(hello)); // function
console.log(typeof hello); // function
通过 f 也能够调用到上面的 hello 函数:
function hello() {
console.log('hello');
}
let f = hello;
f(); // hello
两个操作可以合为一个操作,既定义了 hello 这个函数,又把 hello 赋值给了 f
let f = function() {
console.log('hello');
}
再进一步,就可以把 hello 给省略,此时就是完全依赖 f 来找到该函数
let f = function() {
console.log('hello');
}
定义了一个没有名字的函数,并且把这个函数赋值给了 f 这个变量,后面就可以基于 f 来调用这个函数了
没有名字的函数,也称为 “匿名函数”,还有另一个名字:lambda 表达式
7、作用域
某个标识符名字在代码中的有效范围,在 ES6 标准之前,作用域主要分成两个:
- 全局作用域:在整个 标签中,或者单独的 js 文件中生效
- 局部作用域 / 函数作用域:在函数内部生效
ES6:
ES6 其实是 JS 的版本
JS 原名 ECMAScript,也可以简称为 ES,JS 的版本都是用 ES 数字来表示的
JS 中的最新版本,也是到 ES 十几,ES6 的地位就相当于 Java 8 在 java 中的地位
类似于 Java 的版本:Java 8. => Java 18
在 ES6 之前,没有块级作用域
在ES6之后,引入了 let,也就有了块级作用域,一个变量在 {} 内部定义,是无法被 {} 访问的
注意: 在 JS 里面,{} 内部的代码,是可以访问到 {} 外部的变量的
let num = 10;
function hello() {
console.log(num); // 10
}
hello(); // 10
let num = 10;
function hello2() {
function hello() {
console.log(num);
}
hello(); // 10
}
hello2();
JS 中的作用域,是支持 “逐级向上” 查找的
作用域链:内部函数可以访问外部函数的变量,采取的是链式查找的方式,从内到外依次进行查找
当前是在 hello 这个函数里,尝试打印 num,但是在 hello 中没有 num 变量
于是就往上级找,找到了 hello2 这里,但是在 hello2 里,也没有 num
于是继续往上找,找到了全局作用域,找到了 num
如果最后的全局作用域也没找到,就会报错 ——
如果在作用域链的不同节点上,存在多个同名的变量怎么办?
从里往外找,先找到谁就算谁
let num = 10;
function hello2() {
let num = 20;
function hello() {
console.log(num);
}
hello(); // 20
}
hello2();
基于 JS 实现二分查找
function binarySearch(arr, key) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
// JS 除法 向上取整
let mid = Math.ceil((left + right) / 2);
if (arr[mid] > key) {
right = mid - 1;
} else if (arr[mid] < key) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let key = 3;
let ans = binarySearch(arr, key);
if (ans != -1) {
console.log(key + ' 的下标是:' + ans); // 3 的下标是:2
} else {
throw new Error('没有此数');
}
</>
冒泡排序
<>
function bubbleSort(nums) {
for (let i = 0; i < nums.length - 1; i++) {
let flg = true;
for (let j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
let tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flg = false;
}
}
if (flg) {
break;
}
}
}
let nums = [2, 3, 15, 11, 21 ,7, 1];
bubbleSort(nums);
console.log(nums);
</>
快速排序
<>
function quickSort(array) {
quick(array, 0, array.length - 1);
}
function quick(array, left, right) {
if (left >= right) {
return;
}
let pivot = partition(array, left, right);
quick(array, left, pivot - 1);
quick(array, pivot + 1, right);
}
function partition(array, left, right) {
let tmp = array[left];
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];
}
array[left] = tmp; // left right 相遇 就是基准
return left;
}
let array = [2, 3, 15, 11, 21, 7, 1];
quickSort(array);
console.log(array);
</>
四、对象
1、使用 字面量 创建对象 [常用]
- 对象,就是一些属性和方法的集合
- 在 Java 中,对象,需要先有类,然后针对类的实例化才产生对象
Java 中,类可以视为是一种自定义的类型,(Cat类 和 Dog类 两个不同的类型)- JS 中,对象是不依托于类的,在 JS 中所有的对象,都是一个类型 .object
在 JS 中,直接通过 {} 的方式,就可以创建对象:
let a = {}; // 创建了一个空的对象
let student = {
name: '张三',
age: 20,
height: 180,
weight: 120,
sing: function() {
console.log('sing()');
},
jump: function() {
console.log('jump()');
}
}
- 使用 { } 创建对象
- 属性和方法使用键值对的形式来组织
- 键值对之间使用
,
分割, 最后一个属性后面的,可有可无 - 键和值之间使用
:
分割 - 方法的值是一个匿名函数
后续就可以根据 student.属性名 方式来进行访问了
// 1. 使用 . 成员访问运算符来访问属性 `.` 可以理解成 "的"
console.log(student.name);
// 2. 使用 [ ] 访问属性, 此时属性需要加上引号
console.log(student['height']);
// 3. 调用方法, 别忘记加上 ()
student.sayHello();
2、使用 new Object 创建对象
注意,使用 {} 创建的对象也可以随时使用 student.name = "...";
这样的方式来新增属性
var student = new Object(); // 和创建数组类似
// JS 中一个对象中有哪些成员,也都是动态可以改变的
student.name = "zhangsan";
student.height = 175;
student['weight'] = 170;
student.sayHello = function () {
console.log("hello");
}
console.log(student.name);
console.log(student['weight']);
student.sayHello();
3、使用 构造函数 创建对象
前面的创建对象方式只能创建一个对象,而使用构造函数可以很方便地创建多个对象
例如: 创建几个猫咪对象
var mimi = {
name: "咪咪",
type: "中华田园喵",
miao: function () {
console.log("喵");
}
};
var xiaohei = {
name: "小黑",
type: "波斯喵",
miao: function () {
console.log("猫呜");
}
}
var ciqiu = {
name: "刺球",
type: "金渐层",
miao: function () {
console.log("咕噜噜");
}
}
此时写起来就比较麻烦,使用构造函数可以把相同的属性和方法的创建提取出来,简化开发过程
基本语法:
当前咱们所说的构造函数,其实是一种特殊的函数为了批量的创建出一组类似的对象
function 构造函数名(形参) {
this.属性 = 值;
this.方法 = function...
}
var obj = new 构造函数名(实参);
当看到函数内部,通过 this. 这样的方式来创建属性的时候,此时这个函数大概率就是构造函数了
new 后面的名字,是构造函数的名字,不是 “类” 的名字
注意:
- 在构造函数内部使用 this 关键字来表示当前正在构建的对象
- 构造函数的函数名首字母一般是大写的
- 构造函数的函数名可以是名词
- 构造函数不需要 return
- 创建对象的时候必须使用 new 关键字
使用构造函数重新创建猫咪对象:
function Cat(name, type, sound) {
this.name = name;
this.type = type;
this.miao = function () {
console.log(sound); // 别忘了作用域的链式访问规则
}
}
var mimi = new Cat('咪咪', '中华田园喵', '喵');
var xiaohei = new Cat('小黑', '波斯喵', '猫呜');
var ciqiu = new Cat('刺球', '金渐层', '咕噜噜');
console.log(mimi);
mimi.miao();
4、JavaScript 的对象和 Java 的对象的区别
其实 JS 中后两种创建对象的方式,JS 自己也是觉得比较别扭的,以第一种写法为主
JS 没有 class 这个概念,适用于 ES6 之前
ES6 之后,也引入了 class 关键字,可以让定义对象,就更接近于 Java 了,但是这种方式仍然没有第一种方法简单
而且第一种对象的定义写法,后来就火了,出圈了,不仅仅 JS 会用,其他各种语言,在各种场景下,都会用到 .JSON (数据的组织格式)
因此,在 JS 中也是有一些现成的对象的
像数组,本质上也可以视为是一个对象
JS 虽然有对象,但是 JS 算是一个面向对象的编程语言嘛?咱们一般认为,不算!!
JS 里没有封装,继承,多态,这样的一些面向对象的典型特性
JS 中没有原生提供继承这种机制的
但是 JS 里有一个曲线救国的方法:“原型”,基于原型机制,可以模拟实现一个类似于继承的效果 :把一个对象的所有属性,给自动的加入到一个新的对象中……