JavaScript基础
JavaScript 是一门动态的,弱类型的,解释型的,基于对象的脚本语言。
动态: 程序执行的时候才会确定数据类型。 静态: 书写代码的时候提前确定数据类型。
弱类型: 数据类型可以自动转换。 强类型: 数据类型无法自动转换。
解释型: 边解释,边运行,开发效率更高。 编译型: 编译后运行二进制文件。
脚本: 一般都是可以嵌在其它计算机语言当中执行。
◆javaScript 的运行环境(解释器):
- 浏览器,如Chrome浏览器中的V8引擎。
- Node.js。
◆浏览器端的 JavaScript 组成部分:
- 基本代码语法, ECMAScript,ECMA指定。
- BOM, 浏览器提供的API,W3C指定。
- DOM, 文档提供的API,W3C指定。
◆其他特点:
- 指令结束符(语句结束符)是分号或者换行。
- 严格区分大小写。

开始
JavaScript 在 HTML 中使用的三种方式:
① 行内式(内联脚本)
1 | <元素 onclick="代码.." ondblclick="代码.."></元素> |
② 内嵌式(嵌入脚本)
1 | <script> |
③ 外链式(外部脚本)
1 | <script src="js文件的地址"></script> |
JavaScript如何输出内容:
① 输出到弹框:alert(内容)。
② 输出到页面中:document.write(内容)。
③ 输出到控制台:console.log(内容)。
变量
JS变量名的命名规范:
- 变量名可以由数字、字母、下划线、$ 组成且不能以数字开头。
- 变量名不能是关键字或保留字。
var
var 是 JS 早期的变量声明关键字,存在诸多设计缺陷,现在几乎被 let/const 完全替代。
◆核心特点:
1.变量提升:变量声明会被提升到当前作用域顶部,提升后自动初始化为 undefined,可以在声明前使用变量。
全局代码执行之前会预处理, 查找全局代码中的var关键字,提前创建好变量,不赋值; 当正式执行到变量声明语句的时候,仅仅进行赋值操作。
1 | console.log(n); //var提升,n为undefined |
2.允许重复声明:同一作用域内,可以多次用 var 声明同一个变量,不会报错,后续声明会覆盖前面的。
3.无块级作用域:仅支持全局作用域和函数作用域,if、for、{} 等块级代码块无法形成独立作用域,变量会泄露到块外。
1 | // for循环经典坑:变量泄露,回调访问全局变量 |
这里还涉及到同步事件与异步事件,后面再学。
let
let 是 ES6 为解决 var 的缺陷而新增的关键字,专门用于声明「值会动态变化」的变量。
◆特点:变量值可修改。不存在变量提升。在变量声明之前的区域,变量不可访问。let声明的范围是块作用域。允许先声明,后赋值。
1 | // let:可变变量,块级作用域(只在{}内生效) |
let也不允许同一个块作用域中出现冗余声明。不过,JavaScript引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明。
1 | var name; |
◆与var关键字不同,使用let在全局作用域中声明的 变量不会成为window对象的属性(var声明的变量则会)。
◆条件声明:因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量。
不要使用“如果某个条件成立,就let 某某”形式来声明变量,一个是因为let没法检查上方是否声明过“某某”这个变量,会报错(实际上在if语句之外也要注意这一点,这是个一般问题),另一个是因为“if(){}”“try{}/catch(){}”“typeof”等结束后,这个“某某”就被销毁了,对下方代码来说,“某某”跟未定义没有区别。
◆for循环中的let声明:(包括for-in/for-of循环)
在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以console.log输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
1 | for (let i = 0; i < 5; ++i) { |
const
常量,不允许重复声明。声明时必须同时初始化。
1 | // const:常量,声明后不可修改(引用类型内部可改) |
“
const只锁定引用,不锁定对象内部”这一点类似Java:final关键字对于对象引用,这意味着引用本身不可变(即不能指向另一个对象),但对象的内容仍然可以修改。
◆关于循环迭代:不能用const来声明迭代变量,因为迭代变量会自增。不过,如果只想用const声明一个不会被修改的for循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。 比如for-of和for-in循环。
1 | for (const key in {a: 1, b: 2}) { |
💡声明风格及最佳实践:
- 不使用var。
- const优先,let次之。优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。
数据类型
| 类型 | 包含的具体类型 | 核心特点 |
|---|---|---|
| 基本数据类型(值类型) | Number、String、Boolean、Null、Undefined、Symbol(ES6 新增)、BigInt(ES6 新增) | 1. 存储在栈内存中,直接存储值本身。 2. 赋值 / 传参时,传递的是值的副本,修改副本不会影响原数据。 3. 类型简单,大小固定。 |
| 引用数据类型(复杂数据类型) | Object(包含子类型:Array、Function、Date、RegExp 等) | 1. 值存储在堆内存中,栈内存只存储堆内存的地址引用。 2. 赋值 / 传参时,传递的是地址引用的副本,修改新变量会影响原数据。 3. 类型复杂,大小不固定,可动态扩展属性 / 方法。 |
判断数据的类型:typeof(数据)。不加括号可也行,因为typeof是一个操作符而不是函数。
- 调用typeof null返回的是”object”。这是因为特殊值null被认为是一个对空对象的引用。
Null 和 undefined
◆null:Null类型同样只有一个值,即特殊值null。null值表示一个空对象指针,这也是给typeof传一个null会返回”object”的原因。
1 | let car = null; |
◆undefiend :未定义,没有赋值的变量在使用的时候会自动得到undefined。增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。
使用typeof操作符时,如果返回undefiend,仅代表该变量当前是否存在,而不能说明该变量一定是undefiend类型。
1 | let message; // 这个变量被声明了,只是值为undefined |
二者联系:undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等。用等于操作符比较null和undefined始终返回true。但要注意,这个操作符会为了比较而转换它的操作数。
1 | console.log(null == undefined); // true |
Boolean 布尔类型
有两个字面值:
true 表示是、肯定、正确
false 表示否、否定、错误
💡注意,布尔值字面量true和false是区分大小写的,因此True和False是有效的标识符,但不是布尔值。
◆要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数。Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。转换规则如下:
| 数据类型 | 转化为true的值 | 转化为false的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | “ “(空字符串) |
| Number | 非零数值 | 0,NaN |
| Object | 任意对象 | null |
| Undefined | N/A(不存在) | undefined |
if等流控制语句会自动执行其他类型值到布尔值的转换。
Number 数值类型
表示整数和浮点数(小数),JS 不区分整数和浮点型,统一用 Number 表示。
①浮点数的运算存在精度问题:
1 | 0.1 + 0.2; // 0.30000000000000004 |
我们日常使用的是十进制(满 10 进 1),而计算机底层只能识别二进制(满 2 进 1)。
- 有些十进制整数或小数,能精确转换为二进制:比如
10(十进制)=1010(二进制),0.5(十进制)=0.1(二进制); - 但有些十进制小数,无法被二进制精确表示,会变成无限循环的二进制小数比。如
0.1、0.2,就像十进制无法精确表示1/3 = 0.333333...一样。
② 科学计数法
1 | 1.3e4; // 13000 (1.3 * 10^4) |
③ NaN
1.什么是 NaN
NaN,全称 Not a Number,是 number 类型的一种。比如,用0除以0就会得到NaN。、
如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity。
2.NaN 的特点
NaN 与任何数字进行任何计算结果都是 NaN。
NaN 与任何数字都不相等,包括自己。
3.isNaN() 函数
把一个值传给isNaN()后,该函数会尝试把它转换为数值。任何不能转换为数值的值都会导致这个函数返回true。
④JavaScript 中数字的有效范围
- JS 中能表示的最大的数字
Number.MAX_VALUE:1.7976931348623157e+308。 - JS 中能表示的最小的正数:5e-324。
- 如果超出有效范围,用 Infinity、-Infinity 表示
- 函数
isFinite()可以判断一个数字是否是有效数字,如果是有效数字结果是true。
无效数字: Infinity、-Infinity、NaN。
数值转换:Number()、parseInt()、parseFloat()
Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。
◆Number()函数基于如下规则执行转换。
- 布尔值,true转换为1,false转换为0。
- 数值,直接返回。
- null,返回0。
- undefined,返回NaN。
◆parseInt()函数更专注于字符串是否包含数值模式。第二个参数用于指定进制数。
- 字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。
这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。
- 如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。
1 | let a = parseInt('123abc',10);//按十进制转换 |
◆parseFloat(),第一次出现的小数点是有效的,但第二次出现的小数点就无效了。比如”22.34.5”将转换成22.34。parseFloat()只解析十进制值,因此不能指定底数。
String 字符串类型
字符串是不可变的。
表示:
1 | let str1 = 'Hello World'; // 单引号 |
➢为什么字符串不可变还能使用let?
1 | let str = "Hello"; |
转义字符:
1 | \n 换行 |
字符串的长度可以通过其length属性获取。
转换为字符串:使用几乎所有值都有的toString()方法。null和undefined值没有toString()方法。
1 | let a = 10; |
与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串。
字符串插值通过在${}中使用一个JavaScript表达式实现。所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值。
Object 对象
广义:一切皆对象,数组、函数都是对象的一种。
狭义:Object 数据类型,是对象类型中的一种,与Array、Function 是平级的。
- Object类型的数据是值的无序集合。
- Object类型的数据由属性组成,属性由属性名和属性值组成。
- 属性值可以是任何类型的数据; 属性名用字符串表示,如果符合标识号规范,可以省略引号。
- 如果属性的值是一个函数,该属性可以被称为方法
创建对象
1.直接量方式(最常用)
1 | const obj1 = { |
2.Object()是一个内置函数,传入参数时会将参数转换为对应类型的对象,无参数时返回一个空对象。
1 | // 无参数:返回空对象 |
3.使用new Object()构造函数
1 | let obj1 = new Object(); |
对象属性的读写
1.点号,简洁易用。
2.方括号,适合属性名不符合标识符规范、或使用变量表示属性名的场景。
1 | let obj1 = new Object(); //创建对象 |
- 读取不存在的属性,自动得到 undefined。
- 给不存在的属性赋值,自动添加该属性。
对象属性的遍历
1 | let obj1 = { |
◆方式 1:for...in 循环(传统方式,遍历可枚举属性,包含继承属性),最常用的对象遍历方式,会遍历对象自身的可枚举属性,以及从原型链继承的可枚举属性。
1 | //遍历对象的属性prop |
hasOwnProperty():判断属性是否是对象自身的属性(过滤继承属性)
◆方式 2:Object.keys(对象名)(返回对象自身的可枚举属性名数组,不包含继承属性),返回一个数组,包含对象自身的所有可枚举属性名(字符串类型),不包含继承属性和不可枚举属性。
1 | // Object.keys():返回属性名数组 |
◆方式 3:Object.entries()(返回对象自身的可枚举属性键值对数组,不包含继承属性)
返回一个二维数组,每个子数组是 [属性名, 属性值],方便同时获取属性名和属性值。
1 | [ |
1 | // Object.entries():返回键值对数组 |
删除对象中的属性
使用 delete 运算符可以删除对象的自身属性(无法删除继承属性、不可配置属性),删除成功返回 true,删除失败返回 false。
1 | let deleteprop = delete obj1.getInfo; |
判断对象中是否存在某个属性
◆方式 1'属性名' in 对象,返回布尔值,存在返回 true,不存在返回 false,会包含从原型链继承的属性。
◆方式 2:对象.hasOwnProperty('属性名'),返回布尔值,仅判断对象自身的属性,不包含继承属性,更精准。
◆方式3:通过判断属性名是否在 Object.keys() 返回的数组中,间接判断属性是否存在。
1 | console.log('name' in obj1); // true |
Array数组
- 什么是稀疏数组?
如果数组中存在没有值的元素,该数组就是稀疏数组。 - 哪些方式可能会产生稀疏数组?
① 给数组添加新元素,索引与前面不连续。
② 使用 Array函数或构造函数方式创建数组,只有一个参数且是数字。
③ 修改数组的 length 属性,值比原来的大。
◆如果数组的元素还是数组,该数组可以称为多维数组。
◆字符串具有一部分数组特性,有length属性,可以读取到字符串的长度; 但是length的值不能像数组一样修改。可以通过索引读取到某个字符,但是不能修改单个字符。字符串这种具有一部分数组特性但又不是数组的数据,统称为类数组(伪数组 Like-Array)
创建二维数组:
1 | const ans = Array.from({ length: n }, () => Array(m)); |
数组的遍历
①普通 for 循环
②for...in 循环
1 | const arr = [10, 20, 30]; |
③forEach(最常用,遍历所有元素,不可中断)
1 | const arr = [10, 20, 30, 40]; |
无法中断循环(
break报错,continue无效,return仅跳过当前次)。
④数组的内置方法
| 方法名 | 核心功能 | 示例 |
|---|---|---|
map |
遍历数组,返回一个新数组(新数组长度与原数组一致,元素为回调函数返回值) | const newArr = arr.map(item => item * 2); |
filter |
遍历数组,返回一个新数组(包含所有满足回调函数条件的元素) | const newArr = arr.filter(item => item > 20); |
find |
遍历数组,返回第一个满足回调函数条件的元素(无满足条件的元素返回 undefined) |
const target = arr.find(item => item === 30); |
findIndex |
遍历数组,返回第一个满足回调函数条件的元素索引(无满足条件的元素返回 -1) |
const targetIndex = arr.findIndex(item => item === 30); |
every |
遍历数组,判断所有元素是否都满足回调函数条件(返回布尔值) | const isAllBig = arr.every(item => item > 0); |
some |
遍历数组,判断是否存在至少一个元素满足回调函数条件(返回布尔值) | const hasBig = arr.some(item => item > 30); |
数组的添加与删除
① 添加元素
1 | 1. 使用 数组.length 作为索引添加元素 |
② 删除元素
1 | 1. 数组.length -= n; 删除后n个元素 |
Function 函数
创建函数
① function 关键字方式
1 | function 函数名(参数列表) { |
② 表达式方式
1 | var 函数名 = function(参数列表) { |
◆关于return:
- 当函数遇到return后,函数会立即停止执行并推出,因此return语句后面的代码不会执行。
- return语句也可以不带返回值。这时候,函数会立即停止执行并返回undefined。这种用法最常用于提前终止函数执行,并不是为了返回值。
函数形参与实参
无默认值的参数是必传(调用时必须传入对应值,否则为 undefined),带默认值的参数是可选的,可选参数放在必选参数后面,符合正常的调用逻辑。
1 | // 有默认值参数放在末尾 |
◆形参和实参的数量问题:
- 如果实参数量>形参数量,实参按照顺序给形参赋值,多出的实参没有作用。
- 如果实参数量<形参数量,实参按照顺序给形参赋值,后面的形参没有被赋值,使用的时候自动undefined。
◆arguments
- arguments 是系统创建的变量,只能在函数中使用。
- arguments 的值是一个伪数组,由调用函数时所传递的实参组成。
- 可以使用 arguments 实现可变参数数量的函数。
1 | // 创建函数 该函数计算所有参数的和 |
函数内的形参、argument 都是局部变量。
函数提升
- 全局代码执行之前会预处理, 查找全局代码中的function关键字,提前创建好变量并赋值完整函数体; 当正式执行到函数声明语句的时候,直接跳过。
- 函数调用的时候,执行函数体语句前也会预处理, 查找函数代码中的function关键字,提前创建好变量并赋值; 当正式执行到函数声明语句的时候,直接跳过。
1 | function testFunc() { |
运行结果:
- 函数内执行前访问 funcVar: undefined
- 函数内赋值后访问 funcVar: 我是函数内变量
- 函数外部访问 funcVar: undefined
回调函数
匿名函数就是没有名字的函数,匿名函数适合用于立即调用的函数和回调函数。
满足以下三个条件的函数就是回调函数:
1)函数是我定义的。
2)我没有调用(没有直接调用)。
3)函数最终执行了。
回调函数的使用场景:
- 数组的一些方法需要回调函数当参数,如 forEach、sort、filter、map、reduce 等等
- 定时器的回调函数
- DOM事件的回调函数
- Ajax 的回调函数
- Promise 的回调函数
…大部分回调函数的形式都是作为其他函数的参数!
构造函数
它是一个特殊的函数,专门用于批量创建具有相同结构和方法的对象,相当于对象的模板。每个对象都有对应的构造函数,不同数据类型的对象,构造函数不同。
- 数组对象 → 构造函数
Array - 函数对象 → 构造函数
Function - 普通对象 → 构造函数
Object - 字符串对象 → 构造函数
String
1 | const obj1 ={name:"March", age:18}; |
◆构造函数与对象之间的关系
- 构造函数是模板。描述了对象的共同结构(有哪些属性、哪些方法)。
- 对象是构造函数的实例。是根据模板创建出来的具体对象,每个实例都拥有模板描述的属性和方法。
◆判断对象的构造函数
instanceof运算符,对象是否是某个构造函数的实例。constructor属性,直接获取对象的构造函数。
◆实例化(创建对象的过程)
用 new 关键字调用构造函数,创建对象实例的过程,就叫实例化。new 构造函数(参数)。
{}→ 等价于new Object()。[]→ 等价于new Array()。'hello'→ 等价于new String('hello')。
每实例化一次,就会创建一个全新的对象,每个对象都有独立的内存空间,互不影响。
◆自定义构造函数
规则:
- 构造函数名首字母大写(约定俗成,区分普通函数)。
- 函数内部用
this关键字,指向即将被创建的对象实例,给this添加属性和方法,就是给实例添加属性和方法。 - 必须用
new关键字调用,否则this会指向全局(浏览器中是window)。
1 | function User(name, age, address){ |
◆原始类型数据的对象特性
1.原始类型(Number、String、Boolean)的两种状态。
- 值状态:直接量创建(如
var num = 10;),存储的是原始值,占用内存小,是默认状态。 - 对象状态(包装对象):用
new关键字创建(如var numObj = new Number(10);),存储的是对象,占用内存大,拥有构造函数的原型方法(如toString()、length)。
2.自动转换(隐式包装)。 - 当对值状态的原始类型调用对象方法(如
num.toString())时,JavaScript 会自动将其包装为对象状态,执行方法后再销毁包装对象,恢复为值状态。这就是为什么原始值可以调用对象方法(如'hello'.length),我们无需手动创建包装对象。
Date 日期
要创建日期对象,就使用new操作符来调用Date构造函数:
1 | let now = new Date(); |
在不给Date构造函数传参数的情况下,创建的对象将保存当前日期和时间。
指定日期创建时,使用new Date()构造函数时需明确指定目标日期时间,参数格式为(年,月,日,时,分,秒),注意,月份参数从0开始计数,1表示二月。
时分秒参数未指定时默认为0(可省略不写)。
计算日期时间差:
- 时间戳原理:通过getTime()方法获取1970年1月1日至今的毫秒数。
- 差值计算:目标日期时间戳减去当前日期时间戳得到剩余毫秒数。
- 动态更新:需要在定时器回调函数内重新计算当前时间。
将seconds转换成x天x时x分x秒:
1 | var days = Math.floor(seconds / (1000 * 60 * 60 * 24)); |
倒计时案例:
1 | (function(){ |
原始值与引用值
在把一个值赋给变量时,JavaScript引擎必须确定这个值是原始值还是引用值。
1.保存原始值的变量是按值访问的,因为我们操作的就是存储在变量中的实际值。
2.引用值是保存在内存中的对象。对于引用值而言,可以随时添加、修改和删除其属性和方法。
💡注意,原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字,则JavaScript会创建一个Object类型的实例,但其行为类似原始值。
1 | let name1 = "Nicholas"; |
◆复制值:除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。
1.在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。它们独立使用,互不干扰。
2.在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。
◆传递参数:ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。
1 | function addTen(num) { |
💡如果num是按引用传递的,那么count的值也会被修改为30。
◆关于栈内存堆内存
引用类型参数的值是栈内存里面的「堆内存地址」,这是容易混淆的核心。
图源:March
下面这个例子,如果person是按引用传递的,那么person应该自动将指针改为指向name为”marchfood”的对象。
‼️实际情况:当我们再次访问person.name时,它的值是 “March” ,这表明函数中参数的值改变之后,原始的引用仍然没变。当你执行 obj = new Object() 时,obj 就抛弃了原来的副本地址,转而指向函数内部新建的一个临时对象;这个临时对象只存在于函数执行的过程中,函数执行完后,JavaScript 垃圾回收机制会把它销毁。
1 | function setName(obj) { |
而之前修改的堆对象并不会消失,外部
person依然牢牢指向它,这就是为什么name还是March,而不是undefined。
确定类型
前面学过typeof操作符最适合用来判断一个变量是否为原始类型。更确切地说,它是判断一个变量是否为字符串、数值、布尔值或undefined的最好方式。如果值是对象或null,那么typeof返回”object”。
typeof虽然对原始值很有用,但它对引用值的用处不大。我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。
1 | console.log(person instanceof Object); // 变量person是Object吗? |
如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象。
第二个操作数是对象原型链上的某个对象的构造函数也成立。
垃圾回收机制
JavaScript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。通过自动内存管理实现内存分配和闲置资源回收。
基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的。
比如,函数。函数中的局部变量会在函数执行时存在。此时,栈(或堆)内存会分配空间以保存相应的值。函数在内部使用了变量,然后退出。此时,就不再需要那个局部变量了,它占用的内存可以释放,供后面使用。这种情况下显然不再需要局部变量了,但并不是所有时候都会这么明显。
垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收内存。如何标记未使用的变量有不同的实现方式。在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
◆标记清理
垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。
◆引用计数
其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为0的值的内存。
这种策略很快就遇到了严重的问题:循环引用。所谓循环引用,就是对象A有一个指针指向对象B,而对象B也引用了对象A。
运算符
=== 操作数的类型和值都相等才是true。(首选)
== 先尝试将两个操作数转换成相同类型,然后进行值比较。
转换规则:
- 数字和字符串比较:字符串转换成数字,再比较。
- 布尔值和其他类型比较:布尔值转换成数字(
true → 1,false → 0),再比较。 null和undefined比较:返回true(彼此相等,且不等于其他任何值)。
关于自增自减:
1 | var n = 100; |
常见语句
if同c语言
for同c语言
switch同c语言:
1 | switch (表达式) { |
关键字 this
- 在函数外面使用(全局下使用):Node.js 中全局对象是 globalThis,浏览器中是 window。
- 在构造函数内部使用:this 的值是构造函数的实例(实例化构造函数所创建的对象)。
- 在函数(方法)中使用:this 的值是调用该函数(方法)的对象。
注意:不要看函数声明语句所在的地方,看调用函数的语句,看.前面是哪个对象。
关于window:
- window 表示浏览器窗口, 运行在浏览器上的js,window 作为全局对象。
- 在打开浏览器的时候 window 对象就自动创建了。
- 所有的全局变量都是 window 的属性, 使用 window 的属性可以省略 window。
内置对象
ECMA-262对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。
其实前面已经接触了大部分内置对象,包括Object、Array和String。
Global
Global对象是ECMAScript中最特别的对象,因为代码不会显式地访问它。ECMA-262规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成Global对象的属性。比如isNaN()、isFinite()、parseInt()和parseFloat(),实际上都是Global对象的方法。除了这些,Global对象上还有另外一些方法。
◆URL编码方法
encodeURI()和encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器。这两个方法的主要区别是,encodeURI()不会编码属于URL组件的特殊字符,比如冒号、斜杠、问号、井号,而encodeURIComponent()会编码它发现的所有非标准字符。
1 | let uri = "http://www.wrox.com/illegal value.js#start"; |
前者会将空格被替换为%20。
◆eval()方法
通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。
💡在严格模式下,在eval()内部创建的变量和函数无法被外部访问。
Math
ECMAScript提供了Math对象作为保存数学公式、信息和计算的地方。Math对象提供了一些辅助计算的属性和方法。
◆Math对象属性
| 属性 | 说明 |
|---|---|
| Math.PI | 圆周率 |
◆min()和max()方法
min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数。
1 | let values = [1, 2, 3, 4, 5, 6, 7, 8]; |
◆舍入方法
- Math.ceil()方法始终向上舍入为最接近的整数。
- Math.floor()方法始终向下舍入为最接近的整数。
- Math.round()方法执行四舍五入
◆random()方法
Math.random()方法返回一个01范围内的随机数,其中包含0但不包含1。
例如,从110范围内随机选择一个数:
1 | let num = Math.floor(Math.random() * 10 + 1); |
原型与原型链
原型
原型的概念与特点
每个函数都会创建一个prototype属性,这个属性是一个Object对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。原型本身也是普通对象,对象可以直接借用原型上的属性 / 方法(这个过程叫原型继承)。
◆原型特点:
- 每个对象都有原型。
- 原型也是对象。
- 对象可继承原型属性。
使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
1 | function Person() {} |
分析:这里,所有属性和sayName()方法都直接添加到了Person的prototype属性上,构造函数体中什么也没有。但这样定义之后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此person1和person2访问的都是相同的属性和相同的sayName()函数。
如何获取对象的原型?
- 隐式:通过对象获取原型。
- 显式:通过对象的构造函数获取原型。
- 使用Object的方法。
1 | console.log(person1.__proto__); |
默认情况下,所有原型对象自动获得一个名为
constructor的属性,指回与之关联的构造函数。
对象、构造函数、原型之间的关系
① 对象和构造函数
- 构造函数是对象的描述,对象是构造函数的实例。
- 一个构造函数可以有无数个对象,一个对象只能有一个构造函数。
② 对象和原型
- 每个对象都有原型,可以使用原型上的属性。
- 一个对象只能有一个原型,一个原型可以作为多个对象的原型。
③ 构造函数和原型
- 可以通过构造函数获取到对象的原型。
- 构造函数相同的对象,原型也是相同的; 相同数据类型的原型,原型相同。
1 | // 不同数组 |
◆判断属性是否属于对象本身
1 | 对象.hasOwnProperty('属性名'); |
只有属性在对象本身上才返回true,否则都是false(即使在原型不在本身也是false)。
创建对象的同时设置原型
1.var obj1 = {}:是创建普通对象的字面量语法,默认原型是 Object.prototype(JavaScript 所有普通对象的默认原型);
2.Object.create(proto):ES5 提供的自定义原型创建对象的方法,核心作用是:
- 创建一个空对象(自身无任何属性);
- 将这个空对象的
[[Prototype]](原型)强制设置为传入的proto参数; - 如果
proto是null,则创建无原型的纯净对象。
1 | // 创建对象 原型是提取准备好的 实例化的时候将对象与原型关联 |
原型链
每个对象都有原型,原型还是个对象,原型也有原型,原型的原型也有原型,组成了原型链。原型链终点是Object.prototype,其__proto__为null。
1 | var arr1 = [0,1,2,3]; |
◆作用:
- 对象在查找找属性的时候,先从自身去找看有没有这个属性,如果有,直接使用这个属性的值。
- 如果没有,会沿着原型链向上找,如果找到就使用这个属性的值且停止查找,如果没找到继续向上找直到原型链的终点。
- 如果找到原型链的终点还没有找到,就返回 undefined 。
题目思考
- 下面代码中的对象 f 有方法 a 和方法 b 吗?
1 | var F = function () {} |
- 实例 f 的原型链(普通对象)f → F.prototype → Object.prototype → null
- 函数 F 的原型链(函数对象)F → Function.prototype → Object.prototype → null
其他
绝对值:Math.abs()
平方:Math.pow()
toString() 转化为字符串



