JavaScript (简称 JS)
是世界上最流行的编程语言之一
JS最初只是为了进行前端页面开发.后来JS也被赋予了更多的功能.比如可以用来开发桌面程序,手机APP,服务器端的程序…
是一个脚本语言, 通过解释器运行
JS是一种动态类型, 弱类型的脚本语言, 通过解释器运行, 主要在客户端和浏览器上运行, 比如Chrome里面专门有一个模块, 就是JS引擎, 就相当于JVM一样, 能够解释执行js代码, 后来这个部分的代码就被大佬们单独拎了出来, 封装成了独立的程序, 称为V8引擎, 这就使JS的适用范围更广泛了, 可以使服务器也能解析JS代码, 完成交互.
主要在客户端(浏览器)上运行, 现在也可以基于 node.js 在服务器端运行.
JavaScript 之父 布兰登 * 艾奇 (Brendan Eich):
发明JS之前的布兰登&&发明JS之后的布兰登:
1995 年, 用 10 天时间完成 JS 的设计 (由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,Javascript
写出来的程序混乱不堪).最初在网景公司, 命名为 LiveScript,一般认为,当时 Netscape 之所以将 LiveScript
命名为 JavaScript
,是因为 Java 是当时最流行的编程语言,带有 “Java” 的名字有助于这门新生语言的传播。
其实 Java 和 JavaScript 之间的语法风格相去甚远.
JS主要在前端中可以完成用户在网页和Web服务器的交互, HTML
描述了网页的结构(骨), CSS
描述了网页的样式(皮), JavaScript
则描述了网页的行为(魂).
浏览器分成渲染引擎
+ JS 引擎
.
渲染引擎: 解析 html + CSS, 俗称 “内核”
JS 引擎: 也就是 JS 解释器. 典型的就是 Chrome 中内置的 V8
在html中引入JS代码主要有行内式,内嵌式和外部式三种.行内式直接将JS代码嵌入到了html匀速内部,内嵌式将代码写入到了script
标签中,外部式将代码写到了单独的.js
文件中.
在js中有两种注释风格,单行注释使用//
来表示,多行注释使用/**/
来表示.
注意:外部式的script标签中间不能写代码,必须空着.(即使写了也不会被执行)
在JS中可以使用alert()这个函数来弹出一个对话警示框, 我们搭配使用html中的button标签使用作为JS的第一个程序, 顺便演示一下上面的三种引入JS的方式.
首先采用行内式来写这个代码, 要注意JS中字符串常量可以使用单引号表示, 也可以使用双引号表示, 但更推荐的写法是在html中使用双引号, JS中使用单引号.
<!--行内式-->
<button onclick="alert('hello js')">一个按钮</button>
<!-- 内嵌式 -->
<script>
alert("hello js!");
</script>
<!-- 外部式 -->
<script src="./hello.js"></script>
//hello.js
alert('helloJS');
上面使用alert显示弹窗提示可以让用户看到程序的输出, 是一种很常见的交互方式, 但弹框操作不方便的地方在于, 有些对话框一弹出来用户就不能在操作页面的其他部分了, 必须把弹窗点掉才能继续其他操作.
在JS中可以使用console.log来进行调试输出, 在浏览器开发者工具中, 有一个控制台, 在这个控制台上就能看到console.log输出的内容, 如果出现了一些异常在控制台上也会有提示.
语法:
//第一种:
var 变量名 = 值;
//第二种:
let 变量名 = 值;
与java/c/c++不同的是, 关键字var/let
定义的变量可以接收多种类型的变量值, 可以是整数, 浮点数, 字符串, 数组等, 甚至还可以是函数变量(有点像C语言中的函数指针), JS代码中每个语句最后带有一个;结尾, 可以省略, 但是建议还是加上.
var a = 10;
console.log(a);
let b = 'hello';
console.log(b);
JS中定义变量不用声明类型并不意味着变量没有类型, 而是变量的类型是通过初始化操作的值来确定的, 比如值为10变量的类型就是number
类型, 值为字符串常量变量的类型就是string
类型.
虽然JS中var和let关键字都可以定义变量, 但更推荐的写法是无脑使用let, 这是因为var是老式的写法, 是有很多坑在里面的, 比如这样一个代码:
使用var
关键字定义两个相同的变量是可行的, 而在Java/c/c++中是定义两个相同的变量是会报错的, 而如果使用let
关键字这样定义也是会报错的:
let
这种写法是新式写法, 语法上更接近Java/C++等主流语言, 更符合我们的直觉, 直接使用即可, 具体细节不用区分.
JS是一种动态类型的语言, 一个变量在运行过程种类型可以发生改变, 就是动态类型(JS, Pychon, PHP, Lua…,); 反之, 类型不能发生改变就是静态类型了(c, c++, java, go…).
动态类型相比于静态类型更加的灵活, 但静态类型的语言在编译检查方面可以做的更好, 写出来的代码可维护性就比较高; 而动态变量当下到底是什么类型, 里面是什么样的值这都是不确定的, 这就使其的类型检查不是很严格, 代码维护起来相较于静态类型就比较麻烦.
number
: 数字, 不区分整数和小数.boolean
: true 真, false 假.string
: 字符串类型.undefined
: 只有唯一的值 undefined, 表示未定义的值.null
: 只有唯一的值 null, 表示空值.JS中的数字不区分整形和浮点型, 统一都是number类型, 与Java一样, 支持用二进制, 八进制, 十六进制的表示.
let a = 0o11; //八进制整数,以0o开头
let b = 0x22; //十六进制整数,以0x开头
let c = 0b01; //二进制整数,以0b开头
JS中的有关数字运算结果可能出现一些特殊的数字值, 有如下几种:
Infinity
: 无穷大, 大于任何数字, 表示数字已经超过了 JS 能表示的范围.Infinity
: 负无穷大, 小于任何数字. 表示数字已经超过了 JS 能表示的范围.NaN
: 表示当前的结果不是一个数字.字符串字面值需要使用引号引起来, 单引号双引号均可, JS中的字符串和Java一样, 可以使用+运算符进行不同类型的字符串拼接, 可以使用变量名.lenght求得字符串的长度.
JS中布尔类型和C语言类似, true和非0表示可以表示真, false和0可以表示假, JS的布尔类型支持与数字运算, 运算时, true表示1, false表示0, 本质上也是隐式类型转换, 这在Java中是不行的.
如果一个变量没有被初始化过, 那这个变量就具有唯一的值undefined
, 是undefined类型, 这个操作其实就是将其它语言中非法的行为合法化了, 我们知道在Java中未经初始化的变量直接访问是会报空指针异常的, 但在JS中不会报错, 它会给你返回一个undefined.
null
与undefined
不同, 它表示变量初始化了, 但值为null
, null
和undefined
都表示取值非法的情况, 但是侧重点不同, null
表示当前的值为空(相当于有一个空的盒子), undefined
表示当前的变量未定义(相当于连盒子都没有).
JavaScript 中的运算符和 Java 用法基本相同. 此处不做详细介绍.
算术运算符:
+ - * / %
赋值运算符 & 复合赋值运算符:
= += -= *= /= %=
自增自减运算符:
++
: 自增1
--
: 自减1
比较运算符:
< > <= >=
== 比较相等(会进行隐式类型转换)
!=
=== 比较相等(不会进行隐式类型转换)
!==
JS中a==b这个判断其实触发了隐式类型转换, 也就是说, JS中针对不相同的类型进行比较(==
)运算, 会尝试尽可能的转成相同类型, 而使用===
不会触发隐式类型转换了, 所以有了上面的结果.
像JS这种, 比较能支持隐式类型转换的语言, 称为 “弱类型” 语言; 反过来像Java这种, 不太支持隐式类型转换的语言就被称为 “强类型” 语言, 常见语言的强/弱, 动态/静态类型就如下图:
其中C++处在一个比较特殊的位置了, 这是因为C++即要保证和C语言兼容(C语言支持的隐式类型转换C++也得支持), 而同时C++又在努力的加强自身的类型系统, 设定一些新规则尽量规避旧的问题…
逻辑运算符:
用于计算多个 boolean 表达式的值.
&& 与
: 一假则假
|| 或
: 一真则真
! 非
位运算
&
按位与
|
按位或
~
按位取反
^
按位异或
移位运算
<<
左移
>>
有符号右移(算术右移)
>>>
无符号右移(逻辑右移)
首先来看数组的创建:
let arr1 = new Array(); //很少这样写
let arr2 = [];
let arr3 = [1,2,3,4,5,6,7,8,9,0];
let arr4 = [1, 'hello', this, [1,'hhh'], null, undefined];
JS中使用[]
来表示数组, 同时JS中数组的元素类型不要求是统一的, 可以是任意的类型, 动态类型的语言基本上都如此.
接下来看数组的遍历, 可以直接使用console.log
(数组名);输出整个数组, 如下:
JS中的数组也是通过下标的方式来访问的, 所以还可以使用下面几种for
循环来进行遍历.
方式一:
方式二:
方式三:
在JS中数组的越界访问是合法的, 不会像Java一样报空指针异常, 得到的结果是undefined
.
除了越界访问, 越界修改数组的值也是合法的, 此时数组的长度也会随之发生改变.
观察结果可以发现, 当数组越界访问将下标为100的位置的值修改为66时, 数组的长度就变成了101, 那么中间未初始化的元素值就都为undefined了.
如果说初次接触看到上面的内容还比较合理, 可以接受, 那么下面的内容对于第一次接触JS的来说就可能觉的有些打破认知了.
JS在数组中可以将任意类型作为数组的下标向其中添加元素, 比如负数, 字符串等作为下标, 如下代码.
观察结果可以看到, 此时虽然将两个值成功添加到了数组中, 但数组的长度并没有发生改变, 实际上, JS中的数组不仅仅只是一个传统意义的数组(只能按下标来访问元素), 当使用负数, 字符串这些去访问数组时, 会生成一个键值对添加到数组中, 它更像是数组+Map的结合体, 这就得数组也能够按照Map键值对的方式来组织数据.
可以使用push
方法给数组进行尾插式的添加元素.
此处介绍一个进行数组操作的万能方法splice
, 它可以完成对数组的插入, 修改, 删除等操作.
array.splice(index, howmany, item1, ....., itemX);
它有三个部分的参数, inded
是必须要有的, 表示在什么位置添加/删除元素, howmany
是可选参数, 表示要删除的元素的个数, item1, …, itemX
是可选的变长参数, 表示要添加到数组中的新元素.
这三个部分的参数,如果没有变长参数就表示这个函数起到的作用是删除元素
; 如果变长参数和前面指定的区间个数一样就表示, 此时就表示这个函数起到的作用是修改/替换元素
; 如果后面变长参数比前面指定的区间个数要大就表示这个函数起到的作用是新增/添加元素
.
JS中需要使用function关键字来声明一个函数, 结构包括函数名, 形参列表(不必写参数类型), 函数体; 不必写返回值类型, 但是可以返回值的.
函数的定义, 函数声明, 函数调用的格式如下:
// 创建函数/函数声明/函数定义
function 函数名(形参列表) {
函数体
return 返回值;
}
// 函数调用
函数名(实参列表) // 不考虑返回值
返回值 = 函数名(实参列表) // 考虑返回值
函数的调用可以出现在函数声明之前, 也可以出现在函数声明之后.
hello();
function hello() {
console.log("hello");
}
hello();
在JS中调用函数传入的实参个数比形参多或者是少都是没关系的, 只不过如果是少的话没有收到传入参数值的变量默认值就为undefined了, 此时进行数值运算结果就为NaN了; 而如果是多的话形参中只能拿到形参个数的值, 后面的就拿不到不会参与运算了.
但上面的代码中传入的实参个数比形参少运算后得到的结果是NaN, 这个设定是JS一个比较坑的地方, 这个结果明显是不符合用户期望的, 但它也不报错…
其实还有一种写法可以避免上面的问题, 其实在每个函数里面都会自动定义一个arguments变量, 它是个数组, 其中包含了所有的实参, 可以从arguments变量中拿到元素判断其的值是不是undefined, 然后再进行运算.
JS中函数和普通变量一样, 可以赋值给变量, 此时变量的类型就是function, 然后该变量可以调用该函数或者作为一个返回值, 就像C语言当中的函数指针一样, 这点在Java中无法做到, 因为在JS中函数拥有这种特性, 所以JS函数也被叫做 “一等公民”.
JS中还支持函数表达式的写法, 即定义一个匿名函数, 形如: function() { }, 然后将这个匿名函数赋值给一个变, 后面就可以通过这个变量来调用函数了.
将上面的例子改写成函数表达式如下:
在JS中函数里面是可以继续声明函数函数的, 可以无限的进行嵌套, 这点在Java当中也是不行的.
还有就是在JS中变量的作用域也是有一些特殊的, 当代码中访问某个变量的时候, 会先在当前作用域去寻找, 如果当前的没有, 就继续往上层作用域一级一级的找, 直到找至全局作用域, 如果还是找不到, 变量的值就为undefined了, 这个语法设定和Java中的变量捕获是类似的.
JS不是面向对象的语言但是存在对象的概念, JS中的对象设定和Java中的差异较大, JS中没有继承, 封装, 多态, 甚至没有类, JS中所有的对象的类型都是object, js的对象有属性也有方法, 不过JS中的方法本质上也是属性(一等公民), 下面介绍JS中对象的创建方式, JS里面的对象是通过键值对的方式来组织的.
第一种创建方式是可以直接使用{ }
来创建对象, { }
里面可以写对象的属性, 键值对之间使用,分割, 键和值之间使用:
分割, 方法的值是一个匿名函数.
let 变量名 = {
//属性
键:值,
...,
//函数
键:function (){
//函数语句块
},
...,
//最后一个属性逗号可以省略
}
举例:
访问属性可以使用.或[ ]
, 使用[ ]
访问属性, 此时属性需要加上引号, 调用方法要记的加上()
.
第二种方式是可以使用new Object
先创建对象, 然后再添加属性, 上面使用{ }
创建的对象也可以随时可以在声明的对象外新增属性.
第三种方式是使用构造函数来创建对象, 使用构造函数可以把相同的属性创建提取出来, 简化开发过程.
function 构造函数名(形参列表) {
this.属性 = 值;
this.函数 = function...;
}
//创建
let obj = new 构造方法名(实参);
在JS的ES6版本中, 引入了class关键字, JS中就可以定义 “类” 了, 然后可以通过类去创建实例对象, 这种规则就更接近Java了.