本文编写自 26 届同学:本人不存在
JS的组成
主要分为三部分:ECMAScript(核心),DOM(文档对象模型),BOM(浏览器对象模型)。
ECMAScript——JavaScript的核心
ECMAScript是一套标准,定义了一种语言的标准,规定了基本语法、数据类型、关键字、具体API的设计规范等,解析引擎设计的参考标准,但与具体实现无关,是JavaScript的核心。
BOM——浏览器对象模型
BOM是一套操作浏览器功能的API,通过BOM可以操作浏览器窗口,比如:弹出框、控制浏览器跳转、获取分辨率等。
DOM——文档对象模型
DOM是一套操作页面元素的API,DOM可以把HTML看做是文档树,通过DOM提供的API可以对树上的节点进行操作。
更详细的可以看这篇文章:js的组成
JS的使用方法
HTML 中的 Javascript 脚本代码必须位于 < script >
与 < /script >
标签之间。
Javascript 脚本代码可被放置在 HTML 页面的 < body >
和 < head >
部分中。
引入JS的方法:
- HTML标签中内嵌JS;
<button onclick="alert('hello javascript')">点击me</button>
- HTML页面中直接使用JS:
<script>
JS语句;
</script>
- 引用外部JS文件(推荐):
<script src="JS文件路径"></script>
JS注释
单行注释:
符号://
// 这是单行注释,会把这一行注释掉
多行注释:
符号:/* */
/*
这是多行注释,在里面的代码和文字都会注释掉
*/
变量
JavaScript 的变量是弱类型的,就是用来保存任何类型的数据。 在定义变量的时候不需要指定变量的数据类型。
JavaScript 定义变量有四种方法:const
、let
、var
以及直接定义:
var a = 0;// 老语法,已废弃
let b = 0;// ES6新增语法
const c = 0;// ES6新增语法
d = 0;// 不推荐
在继续了解变量之前,我们得先了解一下作用域和执行上下文这个概念。
执行上下文
概念
在 js 中代码执行需要一个环境,这个环境就是执行上下文。js 还有一个执行上下文栈(一种先进后出的数据结构),用来执行执行上下文。
执行上下文又分为全局执行上下文和函数执行上下文和eval执行上下文(不讲)
词法环境
一种存储变量,函数,作用域链的数据结构,允许访问外部变量和函数,执行完毕后被销毁
先上图:
执行上下文里面包含着词法环境,词法环境里面又包含环境记录器和外部环境的引用
环境记录器里面又包含声明式函数记录和对象环境记录
全局执行上下文
全局执行上下文是在 js 代码执行前创建的,所以它是最先入栈,最后出栈的。
全局代码执行前,会在 堆内存中创建一个全局对象:Global Object (GO)【浏览器中是 window ,node 中是 global】
全局执行上下文的词法环境是全局词法环境,全局词法环境中对象环境记录面的对象环境记录里的变量和函数会被挂载到GO(全局对象) 上,对象环境记录包含除了class
, const
, let
声明的变量和函数以外的所有变量和函数。
其中声明式函数记录包含class
, const
, let
声明的变量和函数。
全局执行上下文中,外部函数的引用为null(空对象)
函数执行上下文
函数执行上下文 会在函数执行时进入执行上下文栈,函数执行上下文的词法环境是函数词法环境,相比全局词法环境,它在对象环境记录中声明的变量不会挂载到全局对象上面。
外部环境引用通过作用域链可以访问父级词法环境,上图:
this
在执行上下文中,this
关键字表示当前执行上下文所属的对象。它的值取决于函数的调用方式。以下是一些常见的情况:
- 全局执行上下文:在全局执行上下文中,
this
的值通常是全局对象(例如浏览器环境中的window
对象)。
-
函数执行上下文:在函数执行上下文中,
this
的值取决于函数的调用方式。有以下几种情况:- 函数作为普通函数调用:当函数作为普通函数调用时,
this
的值通常是全局对象(非严格模式下)或undefined
(严格模式下)。 - 函数作为对象方法调用:当函数作为对象的方法调用时,
this
的值是调用该方法的对象。 - 函数作为构造函数调用:当函数作为构造函数调用时,
this
的值是新创建的对象。 - 使用
call
、apply
或bind
方法调用函数:通过这些方法调用函数时,可以显式地指定this
的值。
- 函数作为普通函数调用:当函数作为普通函数调用时,
- 箭头函数执行上下文:在箭头函数执行上下文中,
this
的值是在定义箭头函数时确定的,而不是在运行时确定的。它通常是包含箭头函数的最近的非箭头函数的this
值。
需要注意的是,this
的值在运行时确定,而不是在定义时确定。它的值是动态的,取决于函数的调用方式。
作用域
作用域是可访问的变量,对象,函数的结合,同时也决定了这些变量的可访问性
javascript 中分为全局作用域和函数作用域以及块级作用域
- 全局作用域在页面打开时创建,页面关闭时销毁
- 函数作用域是在函数内创建的作用域,函数执行完毕,局部作用域会销毁
- 块级作用域与函数作用域类似,块级作用域里的变量只在当前块中有效,在块外无法访问。
var a = 20
function fn() {
var a = 10;
console.log(a);// 10
}
console.log(a);// 20
fn()
全局作用域会创建全局执行上下文,函数作用域会创建函数执行上下文,块级作用域会创建一个新的词法环境,不会创建执行上下文。 全局执行上下文和函数执行上下文我们前面已经详细的了解过了,块级作用域创建新的词法环境是怎么样的呢?
全局作用域下的块级作用域
块级作用域创建的词法环境外部环境引用指向全局执行上下文的词法环境,并且对象环境记录上面的变量和函数也会挂载到全局对象下面
函数作用域下的块级作用域
块级作用域创建的词法环境外部环境引用指向函数执行上下文的词法环境,并且对象环境记录上面的变量和函数不会挂载到全局对象下面
块级作用域下的块级作用域
嵌套关系,如果顶级作用域是全局作用域则符合全局作用域下的块级作用域,如果顶级作用域是函数作用域则符合函数作用域下的块级作用域
var
语法:var 变量名 = 值
注:目前var
声明已经弃用,基本采用let
代替
用于声明一个可变变量,因为是“弱类型”声明,该变量可以赋为任意值。var 声明初始化可以在分离也可以同时完成,但是要注意的是,在未赋值时,变量默认为undefined。
var声明变量的特点
-
如果
var
在全局作用域下,会挂载到window
对象下面var a = 10; console.log(window.a)// 10
-
var
的声明范围是函数作用域var a = 10; function fn() { var a = 15; console.log(a)// 15,以函数作用域的a为准 } fn(); console.log(a)// 10 { var a = 20; console.log(a)// 20,块级作用域不起效果,a被重新赋值 } console.log(a)// 20
-
var
可以重复声明同一变量var a = 10; var a = 20; var a = 30;
var 的变量提升
以var为界以下到结束,为var声明变量的作用域,但函数体会自动提升到作用域顶层,变量可以先于声明语句调用。
console.log(a)// undefined
var a = 10;
console.log(a)// 10
为什么var
会有变量提升,这里就不得不聊到js的预解析了。
预解析
js 引擎运行 js 分为预解析和代码执行:
-
预解析会把所有的变量和函数提升到当前作用域的最前面
2. 代码执行,从上到下执行 预解析分为变量预解析和函数预解析:
-
变量预解析 把所有变量声明提升到当前作用域的最前面,不提升变量赋值操作
2. 函数提升,把所有的函数声明提升到当前作用域的最前面,不调用函数
let(ES6)
let
用法和var
很相似,初学者只需要记住这3点区别:
-
var
声明范围是函数作用域,let
声明范围是块级作用域let a = 10; function fn() { let a = 15; console.log(a)// 15,以函数作用域的a为准 } fn(); console.log(a)// 10 { let a = 20; console.log(a)// 20,以块级作用域的a为准 } console.log(a)// 10,块级作用域定义的a不影响全局的a
-
全局作用域下,
var
变量会挂载到window
,而let
变量则会挂载到全局scopelet a = 10; console.log(window.a)// undefined
-
var
可以重复声明同一变量,但是let
不能let a = 10; let a = 20;// Uncaught SyntaxError: Identifier 'a' has already been declared
const(ES6)
const
声明一个常量,常量不可修改,即无法被重新赋值,并且常量声明时必须初始化。
let
和 const
声明的变量也有变量提升。
let 和 const 暂时性死区
let
和 const
会进行变量提升,但不会进行变量赋值, var
提升之后还会赋值为undefined
,所以let
和const
在声明前调用会进行报错,这就是暂时性死区。
JavaScript 六大基本数据类型
js中有六大数据类型,包括五种基本数据类型number
、boolean
、null
、undefiined
、string
和复杂数据类型object
Number - 数值型
JS不像C/C++、Java、Python 等语言严格区分出浮点型和整型,JS会根据声明情况及运算调整精度位,浮点数和整数都归入数值型数据。
JS里面数字型还有几个特殊的值:
Infinity
表示无穷大 -Infinity
无穷小 NaN
表示非数字的
NaN
是执行数学运算没有成功,返回失败的结果,它不是一个数字,并且NaN 不等于 NaN。
let a = Number()
console.log(a);// 0
let b = 1
let c = 2.333
console.log(b);// 1
console.log(c);// 2.333
// typeof 变量,返回这个变量的类型名字符串
console.log(typeof a);// number
console.log(typeof b);// number
// Number.MAX_VALUE表示最大值,Infinity表示无穷大 -Infinity无穷小
console.log(typeof Infinity);// number
console.log(typeof NaN);// number
String - 字符串型
JS中不对字符串的长度做约束,可以声明空字符串。
创建一个字符串:
let str = "" // 字符串既可以使用双引号""包裹,也可以使用单引号''包裹
// typeof 变量,返回这个变量的类型名字符串
console.log(typeof str)// string
let str1 = '123'
console.log(typeof str1)// string
Boolean - 布尔型
布尔型表示变量的真值,只有true
和false
两种状态。
创建一个布尔值:
let boolTrue = true
let boolFalse = false
console.log(typeof boolTrue)// boolean
null - 空类型
空类型,顾名思义,没有类型指向。
创建一个空类型:
let a = null
console.log(typeof a)// object
我们看到上面的代码可以发现一个问题,typeof a
返回的是object
(对象类型),但按理来说应该返回null
才对,为什么会返回object
呢?
这是由于JS的一个无法修复的历史错误导致的,我们无法去改变它,那我们该如何判断一个值是否是null
类型的呢?
在JS里面是通过===
严格相等运算符进行判断的:
let a = null
if(a === null) {
console.log('是null类型');
} else {
console.log('不是null类型');
}
在下面我们会讲到==
与===
的区别
undefined - 未定义
创建一个undefined类型:
let hhh;
console.log(hhh); //undefined
注:undefined
比较特殊,不是JS里面的关键字,而是全局对象window
上面的一个属性,这意味着它是可以做变量名的,但一般肯定不会这么做。
Object - 对象类型
对象成员
ECMAScript中的对象是一组数据和功能的集合,集合内容可以是键值对也可以是函数。
在 javascript 中,数组(Array)与函数(function)都是特殊的对象。
// 1.通过new Object()创建对象
let obj1 = new Object();
// 2.通过{}创建对象,等同于 new Object();
let obj2 = {}
// 3.使用字面量创建对象
let person = {name: 'zhang', age:20}
// 4.使用工厂模式创建对象
function createObject(name){
var o = new Object();
o.name = name;
o.sayName = function(){
alert(this.name);
};
return o;
}
var o1 = createObject('zhang');
var o2 = createObject('li');
// 5.通过原型+构造函数的方式创建对象
function Animal(name){
this.name = name;
this.friends = ['dog','cat'];
}
Animal.prototype.sayName = function(){
alert(this.name);
};
var a1 = new Animal('d');
var a2 = new Animal('c');
let obj3 = []
let obj4 = function fn() {}
console.log(typeof obj2)// object
console.log(typeof obj3)// object
console.log(typeof obj4)// function,函数这种对象被单列了出来
Symbol 类型(ES6)
Symbol 指的是独一无二的值。每个通过 Symbol() 生成的值都是唯一的。
let s = Symbol("s");
Symbol("s") === s // false
BigInt(ES10)
整数类型只能表示2的1024次方以内的值, 而BigInt 没有位数限制,可以表示任意大小的整数
为了与整数区分,Bigint 类型的整数后面会带有n
,如1n
注意:bigint 与 整数类型不互通,不能进行运算,不能使用Math
let a = BigInt(1)
console.log(a === 1n)// true
==
,===
,Object.is()
,零值相等
的区别
JavaScript 提供三种不同的值比较运算:
===
——严格相等(三个等号)==
——宽松相等(两个等号)Object.is()
还有一种不提供的api零值相等
使用 ===
进行严格相等比较
在 JS 中, == = 叫做全等运算符,全等操作符由 3 个等于号( === )表示,不会进行隐式类型转换,即必须类型相同,值也相同,才会相等。 对于值类型:
如果数据的类型不相等,肯定是为 false
let result1 = ("55" === 55); // false,不相等,因为数据类型不同
let result2 = (55 === 55); // true,相等,因为数据类型相同值也相同
undefined 和 null 与自身严格相等
let result1 = (null === null) //true
let result2 = (undefined === undefined) //true
特殊情况:
- NaN !==NaN
- +0 === -0
console.log(NaN === NaN) // false
console.log(+0 === -0); // true
对于引用类型:
引用类型也叫复杂数据类型,像普通对象、Array、Function、Map、Set 都可以算作 object 类型的一种,他们的基类是 Object。
对于引用数据类型,比较的是他们的引用地址。
const obj1 = {
name: "张三",
};
const obj2 = {
name: "张三",
};
const obj3 = obj1;
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
console.log({} === {}); // false
使用 ==
进行宽松比较
等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true,等于操作符在比较中会进行隐式类型转换,再确定操作数是否相等。
valueOf
- JavaScript 中的 valueOf() 方法用于返回指定对象的原始值,若对象没有原始值,则将返回对象本身。通常由JavaScript内部调用,而不是在代码中显式调用。
- 默认情况下,valueOf 方法由 Object 后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。
- JavaScript 的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的 valueOf() 方法的返回值和返回值类型均可能不同。
不同类型对象的 valueOf() 方法的返回值:
Array:返回数组对象本身。
Boolean: 返回布尔值
Date:存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function: 返回函数本身。
Number: 返回数字值。
Object:返回对象本身。这是默认情况。
String:返回字符串值。
Math 和 Error 对象没有 valueOf 方法。
toString
toString()
方法将对象作为字符串返回。
toString()
方法不会更改原始对象。
每个 JavaScript 对象都有 toString()
方法。
当需要将对象显示为文本(如在 HTML 中)或需要将对象用作字符串时,JavaScript 在内部使用 toString()
方法。
通常,您不会在自己的代码中使用它。
Object.is()
Object.is() 是Object 这个内置对象的一个静态方法。它接收两个值,比较两个值是否相等,返回一个 boolean
值。
主要记住的一点:Object.is() 的表现基本和**全等运算符(===)**一致,除了以下两点:
- 两个
NaN
相等 +0
和-0
,0
和-0
不再相等
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
你可以把 Object.is()
看成是更加严谨的 ===
。
零值相等
类似于Object.is()
,但 +0
和 -0
被视为相等。其余与它一样。
用到零值相等的方法有Array.prototype.includes()
、TypedArray.prototype.includes()
及 Map
和 Set
方法用来比较键的相等性。
数据类型转换
转换成字符串型
- toString()转换为字符串
var num = 1;
var str = num.toString();
- String()强制转换成字符串
var num = 1;
var str = String(num);
- 加号拼接字符串
var num = 1;
var str = num + '';// 隐式转换,字符串+任何类型=拼接之后的字符串
转换成数字型
-
parselnt(string)
函数 将string转换为整数数值型 -
parseFloat(string)
函数 将string转换为浮点数数值型 -
Number()
强制转换函数 将string转换为数值型 -
js隐形转换( - , * , / )
var num = '12' - 0;
var num = '12' * 1;
var num = '12' / 1;
转换boolean型
Boolean()
函数 代表空的,否定的值都会被转换成false
如"", 0 , NaN , null , undefined 其余转换为true
数组转换成字符串
var arr = [1,2,3]
console.log(arr.toString())// 输出 "1,2,3"
// join(分隔符);
var arr = [1,2,3]
console.log(arr.join())// "1,2,3" 默认逗号分隔
字符串转数组
split(分隔符,最大返回值)
方法用于把一个字符串分割成字符串数组。
第一个参数表示用什么进行分割,第二个参数表示最大返回几个值
Javascript里面的数组
创建数组的方式
// 1.Array()构造函数,但它不能创建只有一个数组元素的数组
var arr = new Array();
// 2.数组字面量创建数组
var arr=[];
// 3.扩展运算符(ES6)
let original = [1,2,3]
let copy = [...original]
// 4.Array.of()
Array.of() //=>[]
Array.of(4) //=>[4]
Array.of(1,2,3,4) //=>[1,2,3,4]
// 5.Array.from() 将伪数组转为真数组
let trueArray = Array.from(arraylike)
数组里面可以放任意的数据类型 数组名.length获取数组长度
var arr = [1,2,3,4];
console.log(arr.length)// 4
变长数组
var arr = [1,2,3]
arr.length = 5;
// arr就变成了包含5个元素的数组
// 还有种方法
arr[3] = 4;
// arr就包含4个元素
instanceof
运算符 可以检测是否为数组
console.log(arr instanceof Array)// 返回true 或 false
数组常用方法
Array - JavaScript | MDN (mozilla.org)
伪数组
何为伪数组?
如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为**“类似数组的对象”(array-like object),即为伪数组**。
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function
上面代码中,对象obj就是一个类似数组的对象。但是“类似数组的对象”并不是数组,因为它们不具备数组特有的方法。对象obj没有数组的push方法,使用该方法就会报错。
有哪些是伪数组?
典型的“类似数组的对象”是函数的arguments
对象,以及大多数 DOM 元素集,还有字符串。
Javascript里面的函数
JS中的函数是一种特殊的对象,可以存储在Object类型数据内,也是最常用的对象。
创建函数的方式
通过function
创建函数
语法:function 函数名(参数1,参数2){ 函数体 }
function fn(参数) {
js语句;
}
通过变量方式创建函数
语法:let/const 变量 = function(参数1,参数2){ 函数体 }
let fn = function (参数) {} // function (参数) {},也是匿名函数的语法
箭头函数(ES6)
语法:let/const 变量 = (参数1,参数2)=>{ 函数体 }
let fn = () => {}
new Function()
创建函数
语法:let/const 变量 = new Function(参数1,参数2,...,返回值); (不常用,过多影响性能)
let fn = new Function("num1","num2","return num1+num2")
console.log(fn(1,2))// 3
函数传参个数不同会造成的影响
- 实参个数等于形参,正常输出结果
- 实参个数多余形参,会取到形参的个数
- 实参个数小于形参,多余的形参定义为
undefined
当不确定有多少个参数传递时,可通过arguments
获取
所有函数都内置了arguments
,arguments
里面有传所有的实参
function fn()
{
console.log(arguments);//存储了所有传递过来的实参
// arguments [1,2,3]
}
fn(1,2,3);
arguments
展示是一个伪数组,并不是真的意义上的数组,具有数组的length属性,按照索引方式进行存储,它没有真的数组的一些方法
函数的属性
每个函数都有两个属性,length 和 prototype ,符合对象的要求。
length表示函数形参的个数。
let fn = function (a,b) {}
console.log(fn.length)// 2
构造函数
JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。
一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。都可以使用这个构造函数的属性与方法。
构造函数的书写
function Person(){
this.name='Jack'
}
上面代码中,Person
就是构造函数。
为了与普通函数区别,构造函数名字的第一个字母通常大写。 构造函数的特点有两个:
- 函数体内部使用了this关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用new命令
new
命令的作用,就是执行构造函数,返回一个实例对象
let obj = new Person();
console.log(obj);
JavaScript输入输出语句
比较常用的有这些:
-
prompt() 浏览器弹出输入框,用户可输入
a = prompt("请输入一个数字")// 返回输入结果的字符串 console.log(a);
-
window.alert() 弹出警告框。
alert('hello js');
-
console.log() 写入到浏览器的控制台。
console.log(10) // 10
-
使用 confirm () 函数来弹出一个对话框,并且返回一个boolean值
const a = confirm("111") console.log(a);// 确定为true, 取消为false
下面大部分内容与C语言基础类似,供大一上还没有开C语言课的专业同学学习
JavaScript程序结构设计
顺序结构
赋值语句
变量名 = 表达式;
var a, b;
a = 1; //单赋值
a = b = 1; //传递赋值
({a, b} = {a: 1, b: 2});//无声明赋值
返回语句
- 在使用 return 语句时,函数会停止执行,并返回指定的值
- 如果函数没有 return ,返回的值是 undefined
- 返回值可以是六种基本数据类型,也可以是函数,但只能返回一条表达式。
选择结构
if-else分支语句
-
if 语句
-
if (condition) { 当条件为 true 时执行的代码 }
-
if-else语句
-
if (condition) { 当条件为 true 时执行的代码 } else { 当条件不为 true 时执行的代码 }
-
if-else-else if语句
-
if (condition1) { 当条件1为 true 时执行的代码 } else if (condition2) { 当条件2为 true 时执行的代码 } else { 当条件1、2不为 true 时执行的代码 }
switch-case分支语句
switch(n)
{
case 1:
执行代码块 1
break;
case 2:
执行代码块 2
break;
default:
与 case 1 和 case 2 不同时执行的代码
}
循环结构
- for循环
for (i = 0; i < 5; i++) {
console.log("Lanshan!")
}
- for-in循环
let arr = ['a', 'b', 'c']
//使用for-in遍历获得索引序号
for(let item in arr) {
//打印数组索引(数组的索引就是对象的键名)
console.log(item); //0 //1 //2
}
- for-of循环
let arr = ['a', 'b', 'c']
//使用for-of遍历获取元素
for(let item of arr) {
console.log(item); //a //b //c
}
- while循环
i = 0;
while (i<5){
i++;
console.log("Lanshan!")
}
- do-while循环
do{
i++;
console.log("Lanshan!")
}
while (i<5);
- break 和 continue
i = 0;
while (i<10){
i++;
if(i==2){
continue;
}
else if(i==8){
break;
}
console.log(i)
}
break 语句用于跳出循环。
continue 用于跳过循环中的一个迭代。
函数递归
什么是递归?
- 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数。
- 递归函数是一种高阶函数,后续课程会再展开。
基本结构:
function fun(n){
fun();
}
console.log(fun());
函数嵌套
什么是嵌套?
-
如果一个函数在内部定义或者调用了其他函数,叫做嵌套函数,JS中允许多个函数的嵌套。
-
基本结构:
function foo() {
var x = 1;
function bar() {
var y = x + 1;
console.log(y);
}
console.log(x);
}
函数闭包
什么是闭包?
- 当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的其它变量,如果返回的这个函数在外部被执行,就产生了闭包
函数闭包的特点
- 引用了外部的函数的作用域。
- 可以创建私有作用域和私有变量,且不会被回收内存。
与正常函数的区别
正常情况下,当一个函数调用结束后,函数内部定义的变量占用的内存会被清理掉,
而当函数内部存在闭包函数时,且在函数执行完,闭包函数在外部被使用的时候,那么在外部函数调用结束之后,由于闭包函数引用了外部函数的作用域,所以外部函数的作用域会一直在内存中,不会被处理掉。
闭包的弊端
- 容易导致内存泄露