JS ES5+ES6知识总结
基础知识
JS代码特点
Javascript是一门:动态的 弱类型的 解释型 脚本语言(面向对象的语言); 解释性: js代码从上往下执行 一行一行的去执行; 编译型语言:程序在运行之前需要整体先编译(翻译); 解释型语言:程序运行的时候,不会编译,拿一行解释执行一行;
动态: 程序执行的时候才会确定数据类型; 弱类型: 数据类型不固定,可以随时改变; 脚本: 一般都是可以嵌在其它编程语言当中执行;
三种书写方式以及优缺点
行内js: 局限性很大,只能针对事件进行添加, 用的很少;它的代码分离性最差,可读性不强;
内嵌js: 在body的最下面 script标签内去写我们的js代码,教学和项目用的最多
外链js: 在外部js文件当中去写js代码,最后通过script标签src引入到html当中,项目最终都会把文件进行分离;
注意: 在外链的script标签之间不能去写代码
Ⅰ变量
什么是变量:本质是在栈内存中开辟了一个内存空间,保存数据
①var关键字:
1.先声明再赋值;var a;a=0
2.声明的同时赋值;var a=0
3.不带var声明变量 ①会自动隐式的创建为全局变量,②变量不会提升
②function fn(){}:function 一个变量
③let关键字:
1.let 关键字不能重复声明变量 (不论变量使用 var 声明的还是 let 声明的,let 都不能重复声明)
2.let 声明的全局变量不会作为window的属性
3.let 声明的变量不会提升
4.let 声明的变量除了有全局作用域、函数作用域。还有块级作用域
④const关键字:
const关键字作用
1. const 关键字可以用来声明常量。
2. 常量是一种特殊的变量,一旦赋值无法修改。
const 声明的变量与 let 声明的变量的区别:
1. const 声明的变量,值不能修改
2. let 声明变量可以先不赋值,自动得到 undefined; const 声明变量的同时必须赋值。
const 声明的变量也具备 let 声明的变量的 4 个特点:
1.let 关键字不能重复声明变量 (不论变量使用 var 声明的还是 let 声明的,let 都不能重复声明)
2.let 声明的全局变量不会作为window的属性
3.let 声明的变量不会提升
4.let 声明的变量除了有全局作用域、函数作用域。还有块级作用域
⑤ES6新增关键字
let、const、class、import 是ES6新增的,新增的这4种关键字定义的变量,都具有 以上let 4种特点
class相关: Es6语法,声明一个类 class Person{...};
import相关:import myModel from './module2.js';
⑥块级作用域
1. 分支语句、循环语句等带有 {} 的语句可以产生块级作用域
⑦声明变量的写法
var a=1,
b=2 等价于 var a=1;var b=2
var a=b=3 等价于var a=3;b=3 b声明没带var
⑧变量的命名规范
大驼峰写法 const PersonName = '张三'
小驼峰写法 const personName = '张三' 常用
下划线写法 const person_name = '张三'
⑨交换两个变量的值
方式一:使用第三方变量
var num1 = 1;
var num2 = 2;
var num3;
//想要num1 里装num2的值 可先“腾空num1”;但是在js代码里,num1其实是复制了一份给num3,而不是自己为空;
num3 = num1;
num1 = num2;
num2 = num3;
方式二:不使用第三方
var a = 1
var b = 2
a = a + b //a ==> 3 b ==> 2
b = a - b //a ==> 3 b ==> 1
a = a - b //a ==> 2 b ==> 1
Ⅱ内置数据类型ECMAScript 中的
JS 中八种内置类型(null,undefined,boolean,number,string,symbol,bigint,object)又分为两大类型
- 基本类型:
null,undefined,boolean,number,string,symbolbigintboolean,number,string也称为基本包装类型- 引用类型Object:
Array,Function,Date,RegExpSet、WeakSet、Map、WeakMap等 也可以说这些都是Object的“子类型”,一切皆对象
①基本数据类型
Number
Number是一个数据类型 那么这个数据类型中可以由无数个数字
在控制台蓝色显示
使用场景:整数 小数 科学记数法 2进制(0b) 8进制(0) 16进制(0x) num = 2e3;//e3代表是的三次方 2*10*10*10 科学计数法
ES6新增二进制八进制表示:八进制=》0o100;二进制=》0b101001010
String
string 是一个数据类型 这个数据类型当中可以有无数个值
字符串string 黑色
使用场景: 单引号或者双引号包含 空字符串和空白字符串 引号嵌套(外双内单外单内双)
字符串拼接 a+''
ES6新增模板字符串 `${}str`
Boolean
布尔值是一种数据类型 他当中只有两个值 true(真) false(假)
在控制台显示蓝色
undefined
浅灰色
undefined 是一种数据类型 并且这个数据类型只有一个值 值也是undefined
除const外 其他声明变量不赋值 默认undefined
null
浅灰色
null是一种数据类型 在这个数据类型当中只有一个值 这个值为null
null通常用来删除或者初始化一个对象(有意不包含任何对象)
/*null作为一个基本数据类型为什么会被typeof运算符识别为object类型呢?
答:javascript中不同对象在底层都表示为二进制,而javascript 中会把二进制前三位都为0的判断为object类型,而null的二进制表示全都是0,自然前三位也是0,所以执行typeof时会返回 'object'。*/
bigint (ES2020)
表现方式:bigint 类型的数据只能是整数 5325345n; 整数后面直接加n
与其他基本类型一样,也可以使用构造函数创建。传递给BigInt(12)返回一个bigint类型的12n
数据特点:1. bigint 不能与其他类型的数据进行数学运算。
2. bigint 可以与其他类型的数据比较大小,比较大小的时候会自动类型转换
作用:number 类型表示的数字,如果超过安全整数的范围,无法保证计算精度,此时建议使用 bigint 来表示较大的整数 ,表示高分辨率的时间戳,使用大整数id等等
symbol(ES6新增)
1. 可以作为对象的属性名,对象的属性名可以是字符串的形式也可以是symbol的形式
2. 作为原始类型数据,使用 typeof 判断,返回 symbol
3. 通过调用函数 Symbol() 可以创建一个 symbol 类型的数据,每调用一次 Symbol() 函数都会创建一个独一无二的 symbol 类型的数据。
基本数据类型共同特点
数据判断:typeof 可以检查出数据类型 除了null 和引用类型,都会返回object的
基本数据类型保存在栈内存(stack)中FILO,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。
(闭包种的数据放在堆内存(Heap)中的)
② 引用数据类型
Array
Function
Object
Date
、Set、WeakSet、Map、WeakMap ...等很多种
引用数据类型共同特点
a instanceof A 检查实例a是否是构造函数A的实例对象,或者构造函数A是否在a的原型链上;但是数组或者函数 都可以是对象,所以不准确
Object.prototype.toString.call(this)
为什么放堆内存?答:因为引用数据类型占据空间大、大小不固定。 如果存储在栈中,将会影响程序运行的性能
引用数据类型的值保存在堆内存(Heap)中,在栈(Stack)中保存的是指向该数据的地址,js没有指针的概念但类似
JS的栈内存和堆内存 暂时这么理解
数据类型转换
①强制转换
Number()
将一个字符串转为数字的方法:
Number(‘23’)
parseInt(‘23’)
parseFloat(‘23’)
+'23'
隐式转换
'23'-0
'23'*1
'23'/1
````````````````````````````````
num = '123 456'//NaN 需整体式一个数字
num = ''//空串 也是0
num = ' '//空白窜也是0
num = undefined; //NaN
num = null; // 0
num = NaN // NaN
Number(num)
String()
Boolean()
Boolean(NaN) //false
Boolean(undefined) //false
Boolean(null) //false
②隐式转换
隐式转换: 数据之间运算 比较 和判等中
// 规则 数据之间的运算和比较
1.运算符为+:则看有没有字符串 只要一侧有就是拼接字符串
2.如果为比较大小((> < >= <=):两侧都是字符串则 比较ascll码表的位置(0 48 A 65 a 97),若不是则转数字
3.其余情况运算符两边都转数字
//数据之间的判等
1.看两边是不是同一种数据类型 如果是 那么比较值一样不一样
2.如果不是同种数据类型 两边同时转数字
// 判等的特殊情况 null
//null就是人为设定一个待定义的空对象,所以null == undefined, true; null == null,true (助于记忆而已)
console.log(null == 0);//false
console.log(null == false); //false
console.log(null == ''); //false
console.log(null == ' '); //false
console.log(null == null); //ture
console.log(null == undefined); //ture
//NaN 六亲不认
NaN与任何人运算 结果都是NaN 但是除了拼接字符串
NaN和任何人比较大小 结果都是false
NaN和任何人判等 结果都是不等
③手动转换
parseInt(): 只要整数部分
parseFloat(): 不光要整数部分还要小数点部分
分析: 是window对象的一个方法,主要是用来把各数据类型转化为十进制整数数字。 语法:parseInt(数值,(进制)),其中第一个参数为数值可以是任意类型值如果不能转化为十进制的就会返回NaN, 第二个参数是进制是可选 的,默认的为十进制
Ⅲ运算符和表达式
运算符: 参与运算的符号 无意义
表达式: 由变量或者常量与运算符组成的式子 表达式一定有意义和有值的
1.算数运算符和表达式
算数运算符 : + - * / %
1.除法如果不能整除 会强制除 有小数点也要除完
2.取余:可以判断一个数能不能整除另一个数,如:25%5 余数为零代表一定整除了
可以去求各个位上的数字 如下:
678
百位 678 / 100 ==> 6.78 ==> 取整==> 6
十位 678 / 10 % 10 ==> 67.8 % 10 ==> 7.8 ==> 取整 ==> 7
个位 678 % 10 ==> 8
求一个范围内的数 如下:
求0-99这个范围内的某一个数字 对谁取余 100
求1-99这个范围内得某一个数字 x % 99 + 1
对n求余 余数范围就是 0~n-1
3.+:注意字符串拼接
2.自增自减运算符和表达式
这两个运算符只能和变量组成表达式 a++;a是变量,a++才是表达式
a++ 先给表达式赋值,然后a自增
++a 先自增1,然后增加后的a的值赋值给表达式
a--.--a 同理
3.复合赋值运算符和表达式
所有的运算符本质都是为了让咱们去探索由这个运算符组成的表达式
=右侧:常量 变量 表达式
=左侧:变量 对象的属性
复合赋值表达式
+=, -= ,*= ,/=, %=
3.条件运算符和表达式
> 大于 , < 小于
>= 大于或者等于 , <= 小于或者等于
== 相等 , != 不相等
=== 全等 , !== 不全等
#条件表达式 出来的结果一定是布尔值 (true or false)
4.逻辑运算符和表达式-正常情况
&&(与 并且的意思) , || (或 或者的意思), !(非 取反的意思)
正常情况:左右两边连接得是条件表达式
&& 一假则假 全真才真
|| 一真则真 全假才假
特殊情况:只要两边有一侧不是条件表达式
&& 前面为真,则取后面的值作为整个表达式的值
前面为假,则取前面的值作为整个表达式的值 (后面的不会执行)
|| 前面为真,则取前面的值作为整个表达式的值 (后面的不会执行)
前面为假,则取后面的值作为整个表达式的值
#注意点:取值取的是相应表达式的值而不是布尔值
! 非 取反的意思
!后面一定是一个布尔值 如果不是布尔值 会强转为布尔值
5.三元运算符和表达式
语法: 1 ? 2 : 3
执行过程: 先拿到?左边的结果 这个结果如果不是布尔值 会被强转为布尔值
如果为真 则执行:左边的式子 :右边的不执行
如果为假 则执行:右边的式子 :左边的不执行
如果双分支语句中只有一行代码,则可用三元表示
Ⅳ语句
1.分支语句
①单分支语句
语法:
if(值,如果不是布尔值,强转为布尔值){
代码块
}
// 执行过程
// 1. 他会先拿到()中的值
// 2. 来看这个值是不是布尔值 如果不是布尔值 会强制转成布尔值
// 3. 这个值如果为真 执行{}的代码 如果为假 不执行{}的代码
// 注意点:不管if{}中的代码执行还是没有执行 if之外的代码都会执行
②双分支语句
语法:
if(值,这个值如果不是布尔值,会强转为布尔值){
代码块
}else{
代码块
}
// 执行过程
// 1. 拿到if()中的值
// 2. 看这个值是不是布尔值 如果不是 会强转为布尔值
// 3. 这个值为true 会执行if{}中的代码 为假 执行else{}的代码
// 理解:不管是单分支还是双分支 其实还是从一个事情的双面性考虑的
// 只不过单分支只处理为真的情况 为假不处理 双分支 真假两面都会处理
③多分支语句
语法:
if(值,这个如果不是布尔值,会强转为布尔值){
代码块
}else if(值,这个如果不是布尔值,会强转为布尔值){
代码块
}else if(值,这个如果不是布尔值,会强转为布尔值){
代码块
}else{
代码块
}
// 执行过程
// 1.拿到if()当中的值
// 2.这个值如果不是布尔值 会强转为布尔值
// 3.为真执行if{}中的代码 为假 去看下一个if()当中的值 同上
// 4.如果都匹配不上 执行else的代码块
// 注意点:多分支当中 只可能执行一个
④switch case分支语句
语法:
switch(值){
case 值:
代码块
break;
case 值:
代码块
break;
case 值:
代码块
break;
default :
代码块
break
}
// 执行过程
// 1. 先拿到switch()中的值
// 2. 拿这个值 去挨个对比case:值 对比的时候是全等对比 ===
// 3. 对比成功执行对应case的代码
// 4. 所有的case都没对比上 那么执行default的代码
// break 在switch语句中 break的作用是用来跳出switch语句的
// switch case 理解: switch case可以理解为if多分支的一个变种
// 为了解决if多分支情况比较多 不利于阅读的情况 出现了switch case
2.循环语句
①循环基础
语法:
for(一般放置初始化表达式;一般放置条件表达式;一般放置自增自减表达式){
// 循环体 (代码块)
}
// 执行过程 *************
// 1.首次执行
// 1-1 先执行初始化表达式
// 1-2 条件表达式(为真往下执行 为假跳出循环)
// 1-3 执行循环体
// 1-4 执行自增自减表达式
// 2.非首次执行
// 2-1 执行条件表达式(为真才会往下执行 为假跳出循环)
// 2-2 执行循环体
// 2-3 执行自增自减表达式
// 理解
// 1.首次执行和非首次执行 区别在于是否会执行初始化表达式
// 2.循环的次数看i 并且i在每次循环中值都是不一样的
// 3.循环结束的标志 在于条件表达式为false
②for循环二般情况和死循环
var i = 0
for (; i < 5;) {
console.log('我爱你')
i++
}
// for循环二般情况 咱们一般不用 ()中的两个;不可以省略
// for循环二般情况是由for循环的正常情况演变过来的
// for的死循环
// for循环结束关键看条件表达式为真还是为假
// 进去执行循环出不来了 叫做死循环
for (var i = 0; true; i++) {
console.log(i)
}
// 简写
// for(;true;);
// for(;;);
③嵌套循环
// 1,输出1 - 100之间所有的素数(质数)(只能被1和自身整除的数, 但是1不是质数 质数一定是正整数)
// 1.标志位思想
var flag = true
// 拿被除数
for (var i = 1; i <= 100; i++) {
// 内层循环拿除数
for (var j = 2; j < i; j++) {
if (i % j == 0) {
// 说明一定不是质数
flag = false
break
}
}
// 不是质数的来到了这里 遇到break跳到这来的
// 是质数 老老实实执行完内层for循环 没有遇到break 也来到了这
// 数字1还有数字2来到了这 内层for循环条件不满足
if (flag && i != 1) {
console.log(i)
}
// 重置标志位
flag = true//当i等于4的时候,flag执行了if语句 导致flag=false了,那么就不满足下面if的输出了,后面的全部都无法执行,这时候 只有再flag=true
// //,然后去外层循环执行i++,每次遇到不是质数的时候都要重新true一下,,如果第一次true写循环里面也可以,不过执行效率低,会执行100次。
}
// 2.计数器
var num = 0 //在记录整除了多少次 是两次的话 说明这个数是质数
for (var i = 1; i <= 100; i++) {
// 取得是1-这个数字之间所得的
for (var j = 1; j <= i; j++) {
if (i % j == 0) {
num++
}
}
if (num == 2) {
console.log(i)
}
// // 重置num
// num = 0
}
//3.等腰三角形
// *
// ***
// *****
// *******
// *********
// ***********
// *************
// 外层循环控制行 内层循环控制列
for (var i = 0; i < 7; i++) {
// 这个for循环在控制每行当中打印几个空格
for (var j = 0; j < 6 - i; j++) {
document.write(' ')
}
// 这个for循环在控制每行中打印的*个数
for (var j = 0; j < 2 * i + 1; j++) {
document.write('*')
}
document.write('</br>')
}
Ⅴ数组
是什么: 是由同种数据类型或不同数据类型数据组成的有序集合
为什么: 让咱们一次性的存储多个数据
1.数组定义两种方式
#本质都是在new Array()
1.字面量定义数组
const arr = [] //看见[],代表创建了一个新的数组,在Heap中开辟了新内存
2.构造函数定义数组(基本不用)
const arr = new Array(2) //2个undefined
const arr = new Array('2')//一个元素为2
2.数组重要概念
只要定义了一个数组 那么就会自动出现数组的长度和索引(下标)
数组之所以称为有序的集合 有序就体现在下标 始终从0开始
3.数组方法和案例
Ⅵ函数
是什么: 是具备特定功能的代码块 工具
为什么要有函数: 解决代码冗余 模块化封装项目
1.函数组成部分
函数组成部分 : 函数声明 函数调用
两个都有 函数声明里面的代码块 才会执行
有声明 没调用 相当于函数中写了一堆垃圾代码 不会报错
没声明 去调用 会报错
2.函数定义方式
// 1.函数声明定义(用的多)
function fn() { //fn是函数的名字
// 代码块
console.log(111)
}
// 2.表达式定义函数(也会用 较少)
var fn1 = function () {
console.log(222)
}
// 函数声明和表达式定义区别: 函数声明定义的函数在控制台会带名字
// 函数表达式定义的函数在控制台不带名字
// 预解析中方式一函数提升,方式二按照带var的变量的方式提升!!!!!!!!!!!!!
// 3.构造函数定义(基本不用)
var fn2 = new Function("console.log('333')") //(参数是函数体里的内容)
3.函数三要素
//函数声明三要素:
// 函数名:函数名字要见名知义 本质是代码块(函数体) 但是之后可能有几百行代码 看起来不方便
// 参数:形参 全称形式参数 (可写可不写 要用就写 不用就不写)
// 本质:在函数内部声明了两个变量
// 返回值:1.看函数体内部return后面的值
// return 关键字没写 返回值为undefined
// return 写了关键字 关键字后面面没有写值 返回值也是undefined
// return 写了关键字 关键字后面也有值 返回值就是return后面的值
// 2.return 后面的代码不会执行 在函数体内部看到return 代表函数结束了
//函数调用三要素
// 功能: 函数调用的名字必须和函数声明的名字一样
// 参数 :实参 全称实际参数 ,本质是在给函数声明的形参赋值
// 返回值:函数调用本质是一个表达式 那么她一定是有值的, 值看函数声明return的值
若实参多余行参,则不影响
若行参多余实参,则形参undefined
4.函数封装
// 1.编写函数实现返回某个数组的最大值,最小值
// function arrMaxAndMin(arr) {
// var max = arr[0];
// var min = arr[0];
// for (var i = 1; i < arr.length; i++) {// 因为没必要和自己比 所以 i=1,开始
// if (max < arr[i]) {
// max = arr[i];
// }
// if (min > arr[i]) {
// min = arr[i];
// }
// }
// return [max, min]; //return后只能写一个值,所以用数组来接收多个值!!!!
// }
// var result = arrMaxAndMin([2, 4, 6, 8, 10])
// console.log('这是最大值' + result[0], '这是最小值' + result[1])
------------------------------------------------------------------------------
// 2.封装函数实现打印1到N的质数;
function printPrimeNumbers(n) {
var flag = true;
for (var i = 1; i <= n; i++) {//1-n的质数 就要执行n次
for (var j = 2; j <= Math.sqrt(i); j++) {// 1(1不是质数)和他本身之外 还能整除肯定不是质数,
//用Math.sqrt()开平方 提高性能,减少循环次数,因为:如果它不是质数,那么它一定可以表示成两个数(除了1和它本身)相乘,这两个数必然有一个小于等于它的平方根。只要找到小于或等于的那个就行了 如:16=1*16,2*8,4*4,8*2,16*1----》只要判断2—4
if (i % j == 0) {
flag = false;
break;
}
}
if (flag && i !== 1) {
console.log(i);
}
flag = true;
}
}
printPrimeNumbers(100)
// 还要一种累加器的写法
------------------------------------------------------------------------------
// 3.封装函数返回对数组排序后的结果;冒泡(sort())
// function arrSort(arr) {
// for (i = 0; i < arr.length - 1; i++) {//轮数
// for (j = 0; j < arr.length - 1 - i; j++) { //次数
// if (arr[j] > arr[j + 1]) { //从小到大
// var temp = arr[j]; // if (arr[j] < arr[j + 1]) 从大到小
// arr[j] = arr[j + 1]
// arr[j + 1] = temp;
// }
// }
// }
// return arr;
// }
// var minMax = arrSort([4, 7, 3, 9]);
// console.log(minMax);
------------------------------------------------------------------------------
// 4.封装函数返回对数组翻转后的结果 ,revers()
// function flipArr(arr) {
// for (i = 0; i < arr.length / 2; i++) {
// var temp = arr[i];
// arr[i] = arr[arr.length - 1 - i];
// arr[arr.length - 1 - i] = temp;
// }
// return arr;
// }
// var resoult = flipArr([2, 4, 6, 8, 110]);//把这个赋值给一个变量
// console.log(resoult);
-------------------------------------------------------------------------------
// 5.封装函数返回对数组去重后的结果
// function removeDuplicates(arr) {
// let newArr = [];//定义一个新数组接收去重后的数组元素
// let flag = true;//标志位思想
// for (i = 0; i < arr.length; i++) {
// for (j = 0; j < arr.length; j++) {
// if (arr[i] == newArr[j]) {//只要有一个等的就代表有重复,但是用不等的话 没法确定,因为必须每次一轮里全走完才能确定是否全部不相等
// flag = false;
// break;//跳出该循环,不用往下判断了
// }
// }
// i=0时,内层循环不满足条件不执行,直接到这里
// 内层循环有重复,break后,走到这里 flag变false;
// 执行完内层for循环后,没有碰到break,到这里
// if (flag) {
// newArr[newArr.length] = arr[i];
// }
// flag = true;//重置flag
// }
// return newArr;//返回新数组
// }
// var result = removeDuplicates([1, 3, 5, 5, 6, 6, 7, 9]);
// console.log(result);
5.作用域
是什么: 变量起作用的范围,是虚拟的,相对的概念
为什么: 隔离变量
何时确定: 函数只要声明了 作用域就确定死了 ,和函数何时调用无关!!!
函数声明的时候会在属性里放入[[Scope]]:[fn1,Global]属性里就以数组的方式存放了当前作用域链,每个元素都是一个 作用域
ES5中有全局作用域(函数外部)和局部作用域(函数内部)以函数声明为界 ES6中 增加了块级作用域,{},const ,let 声明的变量具有
6.全局变量和局部变量
在全局作用域当中的变量叫做 全局变量
在局部作用域打中的变量叫做 局部变量
// 总结:
// 第一种:全局变量和局部变量都有 那么各自用各自的
// 第二种:全局变量有 局部变量没有 局部是可以使用全局的
// 第三种:全局没有 局部有 全局不可以用局部的 会报错 引用错误 a没有定义
---------------------------------------------------------------------
// 局部变量不带var:
1.先看 /*函数内部*/ 有没有声明这个变量 如果有 就是局部变量
2.如果函数内部没有var这个变量 /*找形参*/ 形参有就是局部变量
3.如果函数没声明形参也没有,那么看全局中有没有这个变量,有的话就是在操作全局变量//(会改全局变量的值)
4.全局也没有这个变量 那么会在全局当中声明一个变量
7.作用域链
是什么: 是查找变量的过程
作用: 查找变量
查找规则;首先会在自身作用域找,找到就用,若没有,去上级作用域找,有就用,没有就再找直到全局,再没有就报错
//作用域链的顶端一定是全局作用域!
//作用域是函数声明时候确定的 , 作用域链是函数 调用 的时候才有的 若该函数没调用则该作用域链中没有该作用域
8.预解析
// 谁会做预解析 :带var 的变量 函数声明定义的函数
// 预解析发生在什么时候:在js代码开始执行之前
// 规则:
// 预解析完成的代码 一行一行从上至下去执行!!!!
// 1.先预解析函数声明定义的函数,
// 如过提升的时候发现函数同名了,那么后面的函数会覆盖前面的函数
// 函数提升的时候整体提升
// 2.再去提升带var的变量,
// 提升变量时候同名,会忽略
// 只提升变量名(var a) 变量的值是不会提升的
// 3.函数表达式定义的函数 预解析规则按照带var变量对待
// var fn = function(){}
// 4.变量不带var 不会做预解析
// 5.函数内部也会做预解析
9.js中的栈内存和堆内存
1.基本数据类型变量名和值都放在栈内存当中
2.对象数据类型(数组 函数 对象...)变量名在栈内存中 数据在堆内存当中
10.匿名函数
// 语法
(function(形参){
代码块
})(实参)
//箭头函数同样适用
(()=>{})()
特点:1.只会执行一次,没法再调用,除非复制一份,但是只是长的一样,又会开一个执行上下文环境
2.匿名函数写完记得加分号 不然可能会报错
3.匿名函数自身不会预解析 但是这个函数的内部是会做预解析的!
场景:1.一般写项目初始化的时候会用
2.防止外部变量命名污染
//查资料看到的
// 局部变量退出作用域之后会销毁;全局变量关闭网页或浏览器才会销毁。
// 自执行函数,即定义和调用合为一体。我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,
// 因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
//如果变量使用前未声明,ECMAScript会給它定义一个全局变量,突破函数级作用域
//b站上看懂的一个题
var a = 1;
(function a() {
a = 2;//这个a是全局的a 并不是函数内部的
console.log(a); //函数体
})()
// 匿名立即执行函数
//1 在作用域空间寻找变量
//2 函数声明 和变量声明 相比 函数声明优先于变量声明
//3 a->fn
//4 非匿名立即执行函数,函数变量只读,不能进行赋值!!
//题二
function fn() {
(function () { // 这里是我们的块级作用域(私有作用域)
var zxx = 'good girl!' // 此变量在外部并未定义
console.log(zxx) // good girl!
})()
console.log(zxx) // 报错Uncaught ReferenceError: zxx is not defined
}
fn()
11.arguments
// arguments 实参伪数组
// 是什么:长得像数组 但不是数组 本质是对象
// 存在去哪里:每一个函数内部
// 作用:让咱们拿到函数调用时所有的实参
// 对于一个函数来说 形参可有可无 因为咱们都可以通过arguments拿到所有的实参,但是基本不用(没有语义化)
arguments。length
12.rest(ES6)
// 定义函数 rest 参数可以得到纯数组
// function fn(num,...args) {
// // 读取所有的实参
// console.log(arguments);
// console.log(args); 一个由剩余实参组成的数组
// }
// // 调用函数
// fn(100, 200, 300, 400);
13.回调函数
定义:
1.函数是你定义的
2.函数你没调用
3.但是函数执行了
场景:
1.事件
2.定时器相关
3.ajax
4.生命周期
14.递归函数
//合理使用递归(两者必须同时满足)
// 1.你要有一个结束条件
// 2.还要有一个趋近于结束条件的趋势
案例一:
function fn(x) {
if (x == 1) {
return 16
}
return fn(x - 1) - 2
}
console.log(fn(3))
//fn(3)调用并传参进去 得到fn(2)-2,这时候表达式没有得到一个确定的值,,这是fn(3)不会死,
//继续去执行fn(2),得到f(1)-2,同上
//去执行f(1),返回16,fn(1)出栈 值给fn(2),16-2=14,fn(2)把值给fn(3),fn(2)出栈,f(3)拿到确定返回值,12 出栈
案例二:
15.箭头函数(ES6)
//1.箭头函数语法:
const fn=()=>{函数体}
const fn=param=>{函数体} //若只一个参数,则可省略()
const fn=param=>return+表达式 // 若只有一条return语句 则可省略{}和return
//2.箭头函数特点:
1. 箭头函数中this指向: 箭头函数中没有自己的this,如果在箭头函数中使用this,沿着作用域链向上查找
2. 箭头函数中不能使用arguments,可以使用 rest 参数代替
3. 箭头函数只能调用,不能作为构造函数被实例化
4. 箭头函数不能作为生成器函数
Ⅶ对象
是什么: 无序的键值对(key value)集合
为什么要有对象: 描述一个具体的事或者物
对象的组成部分: 属性(键 key)和属性值(值 value)
属性:本质上都是一个字符串 可以写单引号 双引号 不写引号都可以
特殊情况:属性名不符合标识符规则 那么必须加引号(单引号双引号都行)
属性值:值(常量 变量 表达式 数组 函数(方法) 对象)
1.对象的定义方式
1.字面量定义
const a={...};
2.构造函数定义
const a=new Object({...})
#理解:
1.两种定义方式本质都是new Object(),数组和函数同理,
2.创建的对象都叫做Object的实例化对象,因为都是其new出来的
3.通过Object创建出来的对象,类别不固定,可以是任何类,是个泛类,类别不固定
2.对象的操作
.语法特殊情况
1. 如果你要用到的属性名不符合标识符规则 .语法不行
2. 他不能操作某个变量做为他的属性名,因为默认点语法只能是字符串 ,会把变量看成字符串的
[]语法总结 (通用)
1.[]当中可以放置变量 取到变量的值做为对象的属性名
2.[]语法里面可以写不符合标识符规则的名字
3.对象遍历
for (var key in obj) {
...
}
//数组和对象都可以用该方法,数组得到的key是下标,对象得到的key是属性
mdn说:不建议用在数组,它最常用的地方应该是用于调试,可以更方便的去检查对象属性(通过输出到控制台或其他方式)。
for(var v of iter){..} //遍历可遍历对象,v为值
4.数组及函数及对象理解
//1.数组是数组 数组是对象(特殊 有序)
遍历数组的时候 不会遍历到length属性,这时为数组
arr.length 这时就是对象了
//2.函数是函数 函数是对象(特殊 可以去执行)
fn() //当函数调用
fn.a //当对象用
在js中,一切皆对象
5.windows对象
//window对象是js当中所谓的顶级对象 浏览器打开的一刹那 所有的一切都会放在window对象身上,叫浏览器窗口对象
全局 当中的所有的变量或者函数作为windows的属性或者方法
//window身上的属性或者方法 可以直接当作变量来用
6.this
1.this代表一个函数的执行者 谁调用这个函数 this就指向谁 本质是一个对象
2.this只会出现在函数内部,(在全局中this指向window)
3.一个函数的调用方式不同this指向就不同:1.普通函数调用this指向window 如fn()
2.对象方法当中this指向这个对象 如obj.fn()
4.构造函数当中this指向实例化对象
5.使用call apply bind可以任意修改this指向
6.事件回调当中this指向事件源
7.类的概念
1.es5当中没有类,只是用构造函数模拟类的概念
2.对象 函数 数组本质都是构造函数new出来的,理解为各自的类别,new出来就是对应的对象 函数 数组
8.自定义构造函数
//自己去定义一个类别 让之后生下来的孩子(对象)一定属于某个类
构造函数也是一个函数 之外和其他函数一样
1.构造函数命名一般首字母大写
2.调用前加 new 关键字
9.普通函数与构造函数的区别
1.两者只不过是调用方式不同,那么this指向就不同,return结果也不一样的
2.一个函数可以当作普通函数用,也可以当作构造函数用
3.如和区分普通函数和构造函数?调用的时候才能区分,看是否有new
普通函数:
1.this指向window ,函数内部的this.name相对于给window添加或修改name属性
2.return:只写return不写值和不写return 返回值都是undefined;写了return并且写值了,那么就按值返回
构造函数:
1.this指向实例化对象,函数内部的this代码相当于给实例化对象身上增加属性和方法=》实例化过程
2.return:写了return关键字后面没值或者没写return或者return了一个基本数据类型 返回值都是实例化对象
如果return了一个引用数据类型的数据 返回值就是这个引用类型数据了 不再是实例化对象
10.new过程
1. 在堆当中开辟了一个空间(为你的实例化对象准备的) 2. 会让函数当中的this指向于你刚才开辟的空间 3. 老老实实执行函数内部的代码 把相应的属性或者方法加到实例化对象身上(实例化过程) 4. 把实例化对象的空间地址返回出来
11.call apply bind
//他们是函数对象身上的三个方法
使用call apply bind可以任意去修改this指向
1.call
第一个参数是你要修改的this 之后的参数代表要传递的实参
作用:修改this指向 会自动执行前面的函数
2.apply
第一个参数是你要修改的this 之后要传递的参数必须是一个数组
作用:修改this指向 会自动执行前面的函数
#call和apply的区别:传递参数方式不同
3.bind
也是让咱们修改this指向的,只不过他不会自动执行前面的函数
他会返回一个新的函数 需要你手动执行(自己调用)
传参 : 第一个是你要修改的this ,之后传递实参按照call的来 和call的传参方式一样
在返回的新函数当中去传参也是可以的
// call apply和bind的区别
// call apply 会自动执行前面的函数 bind不会自动执行 他会返回一个新的函数 需要咱们自己手动执行
// bind传参可以有两种方式 一个是在bind方法中 还有一个是在bind返回的新函数当中
11.判断数据类型的方法
1.typeof: 只能判断死六种情况 数字 字符串 布尔值 undefined 函数 Symbol(); // symbol ES6新增数据类型
null 对象 和数组 判读不出来 =》都是‘Object’
2.A instanceof B:A是实例化对象,B是构造函数,判断A是不是B的实例,(本质用于检测构造函数的 prototype 属性是否出现在某个实例对 象的原型链上)
3.Object.prototype.toString.call(obj):toString() 方法被每个 Object 对象继承。如果此方法在 自定义对象中未被覆盖, toString() 返回 "[object type]",其中 type 是对象的类型。Array、Date Function等内部toString方法被重写了
4.Array.isArray(obj):console.log(Array.isArray({ name: 1 }));//false
console.log(Array.isArray([1, 2, 3]));//是数组为true
12.原型对象
//作用:节省内存 资源共享
prototyp 显示原型对象(民间说法)
在哪里;只要是函数 那么函数对象身上就会有prototype属性
__proto__ 隐式原型对象(民间说法)
在哪里:只要是对象 对象身上就会有__proto__ [[Prototype]]谷歌浏览器显示的是这样 本质__proto__
1.只要是函数就有prototype,同时也有__proto__
2.只要是对象就要__proto__
13.原型链总结
1
1.1 只要是函数都有prototype(显示原型)
1.2 只要是对象就有__proto__(隐式原型)
1.3 原型对象身上都有constructor属性(构造器) 指向这个原型所在的那个函数
2
2.1 所有的函数都是Function的实例
2.2 所有的原型对象都是Object的实例
2.3 实例的隐式原型(__proto__)指向其构造函数的显示原型(prototype)
3 特殊
3.1 Object的显示原型的隐式原型指向null 代表找到头了
3.2 Function自己new自己 Function的显示原型和隐式原型都指向同一个地址
//开小灶:对象都有原型(隐式也是原型),隐式显示民间说法,只是两种获取原型的方式,构造函数获取就是显示,实例获取就是隐式
// 因为对象都有原型,所以 Object.getprototypeOf(arr) 新增方法 拿到的就是原型 不分显示隐式
//__proto__只在附录里规定,只是这么要求了浏览器去这么做,去通过隐式获取原型,,但是换了执行环境时 比如node.js,就不支持了
/*
Javascript中 var a = new A();执行完毕后,a和A就不再有任何的强绑定关系了,
只有a.__proto__ === A.prototype这个弱关系(而且还不一定成立!),你覆盖掉了A.prototype之后,
自然a和A就不再有任何关系
一开始a.__proto__ 和A.prototype 都指向同一个地址,但是修改A.prototype后, 已经new的实例a 还是会指向之前的原型对象
新new的实例 又共享同一个A.prototype
之前狭义错误理解:认为a.__proto__和A.prototype 指向同一个内存,并且会伴随着A.prototype 的改变而改变 是错误的,已经new的实例不会改了 ,除非手动修改
*/
14.原型方法和对象方法区分
构造函数显示原型身上的属性或者方法 是给实例化对象用的 构造函数不可以用!!!!
15.JSON内置对象
// JSON 内置对象(js自带的 你直接用就可以)
// JSON当中的两个方法 stringify parse
// stringify 把一个数据转成json
// parse 把一个json串当数据解出来
// json 数据格式本质是字符串,json串
// 这个数据格式是用来让咱们前后端传递或接收数据的统一格式
16.Math内置对象
#console.log(Math.floor(num)) //向下取整 *****
console.log(Math.ceil(num)) //向上取整
console.log(Math.round(num)) //四舍五入取整
console.log(Math.max(1, 123, 5, 7)) //取最大值
console.log(Math.min(1, 123, 5, 7)) //取最小值
console.log(Math.min(-1, 0, 99, 8))
console.log(Math.PI) //取圆周率
console.log(Math.pow(2, 3)) //相当于是2的三次方 2*2*2
console.log(Math.sqrt(9)) // 求平方根,在求质数的时候可以用此减少循环次数,提高性能!!!
console.log(Math.abs(num)) //取绝对值
console.log(Math.abs(num))
#console.log(Math.random()) //取0-1之间的随机数 包含0但是不包含1 *****
cosole.log(Math.random())
// 0-100之间的整数 包括100了 所有要101 ,,,用100的话 需要向上取整
console.log(Math.floor(Math.random() * 101)) //
// 1-100之间的整数 包括100的 *100的话都是0-99, 整体+1,就可以了
console.log(Math.floor(Math.random() * 100 + 1))
// 3-100之间的整数
console.log(Math.floor(Math.random() * 98 + 3))
// 公式
Math.floor(Math.random() * (b - a + 1) + a)
// Math.floor(Math.random()*(b-a+1)+a)
// 5-20随机数
console.log(Math.floor(Math.random() * 16 + 5))
// 封装一个函数来返回N位的验证码
function randomCode(n) {
var str = '4r65r7e8w9r7e98wre2w412316545r6w6^^%^&*(&(*%##$@$^&&*(^'
var code = ''
for (var i = 0; i < n; i++) {
var index = Math.floor(Math.random() * str.length)
var item = str[index]
code += item
}
return code
}
var result = randomCode(4)
console.log(randomCode(8))
17.Date内置对象
// Date 时间
var date = new Date()
console.log(date.getFullYear()) //年
console.log(date.getMonth() + 1) //月 有小坑 0-11 需要自己手动加1
console.log(date.getDate()) //日
console.log(date.getHours()) //时
console.log(date.getMinutes()) //分
console.log(date.getSeconds()) //秒
console.log(date.toLocaleDateString()) //本地日期的字符串格式
console.log(date.toLocaleTimeString()) //本地时间的字符串格式
console.log(date.getTime()) //从1970年1月1日至今的毫秒数
//以上都是实例化对象的方法 属性
// console.log(Date.now()) //从1970年1月1日至今的毫秒数 这个用的多
// 之后会用Date.now() 来生成唯一id
//倒计时
(function () {
// 获取 #content 元素
var contentNode = document.getElementById('content');
// 创建目标日期时间的 date 对象
var targetDate = new Date(2022, 8, 1, 0, 0, 0); //月份0-11
// 调用,初始值
countDown()
// 开启循环定时
setInterval(countDown, 1000);
// 定时器的回调函数,倒计时操作
function countDown() {
// 创建当前日期时间的 date 对象
var currentDate = new Date();
// 计算当前日期时间 距离目标日期时间 所差的毫秒数 除1000 得到秒数
var seconds = (targetDate.getTime() - currentDate.getTime()) / 1000; //getTime() 方法可返回距 1970 年 1 月 1 日之间的毫秒数。
// 从 seconds 中提取出完整的天数 1d=24h ,1h=60m, 1m=60s,
var d = Math.floor(seconds / (60 * 60 * 24)) //将剩余的秒数转换为天数
// console.log(d);
// 计算去除了完整天数后剩下的秒数、
var s = seconds % (60 * 60 * 24); //s范围 0~60*60*24-1 也就是1天以内
// 从剩下秒数中取完整的小时数
var h = Math.floor(s / 60 / 60);
// 计算除了完整小时后剩下的秒数
s %= 3600;//s=s%3600 0~3599秒 也就是1h内
// 从剩下秒数中取完整的分钟数
var m = Math.floor(s / 60);
// 计算最终剩下的秒数
s = Math.floor(s % 60); //0-59 秒 一分钟内
// 对 d h m s 进行处理,个位数补 0
d = d < 10 ? '0' + d : d;
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
// 拼接字符串
var dateStr = '距离春节还有<br>' + d + '天' + h + '小时' + m + '分钟' + s + '秒';
var dateStr = `距 离 培 训 结 束 还 有<br>${d} 天 ${h} 小 时 ${m} 分 钟 ${s} 秒`
// 将结果输出到 #content 元素中
contentNode.innerHTML = dateStr;
}
})()
18.内置对象
String Number Boolean 可以进行对象的操作
var str = '哈哈哈'
console.log(str.a)
console.log(str)
偷偷做了一些事情
1. str.a 调用 var str = new String()
2.进行完对象的操作
3.再让这个实例化对象变成'哈哈哈'
//把每个生活中看似理所应当的都要写成代码
分析a.b
a一定是对象吗? 不一定
a是对象 a一定存在吗? 当前作用域没有a这个对象 沿着作用域链一直向上查找 知道找到全局
找到全局 有就用 没有报错 报什么错呢?a没有定义
b一定在a对象身上吗?a身上没有b 沿着原型链一直往上查找 找到就用 找不到呢?返回undefined
a不是对象 一定会报错?不一定 因为对于数字 字符串 布尔值来说是有包装对象的 结果是undefined
Ⅷ JS高级
1.面向对象
#特征:封装 继承 多态
封装:把同类事物的特征和功能包装在一起
// 模拟狗类
function Dog(name, age) { //父类
this.name = name
this.age = age
}
Dog.prototype.run = function () {//父类的原型上添加方法
console.log('跑的很快')
}
// 模拟泰迪类
function Teddy(name, age) { //子类
Dog.call(this, name, age)//1.构造函数继承:继承属性,借助父类的构造函数实现属性继承
}
//2.原型继承: 通过让子类的原型变成父类的实例来让子类继承父类的原型方法
Teddy.prototype=new Dog()
Teddy.prototype.constructor=Teddy//构造函数原型上的构造器属性指向其构造函数
//3.组合继承:原型继承和构造函数继承组合起来
多态:方法重写 方法重载是多态的两种表现形式
方法重写:子类的方法跟父类的方法同名 但是功能不一样
方法重载:根据参数的不同做不同事
//将子类原型上的的方法重写,子类的实例再用run方法是优先用自己的了
Teddy.prototype.run = function (flag) {
if (typeof flag == 'number') {
console.log('跑的很慢')
} else if (typeof flag == 'boolean') {
console.log('跑的一般快')
}
}
2.终极原型链
3.打断点
// 断点 让咱们来调试代码
// debugger:浏览器运行程序会停在debugger处,开启调试模式,可以写多个debugger
// 第一个 跳到下一个断点 (断点之间的跳跃)
// 第二个 一步一步走 但是不走函数内部的代码
// 第三个 一步一步走 会走函数内部的代码
// 第四个 跳出当前所在的函数
// 第五个 一步一步走
// 第六个 让断点暂时失效或生效
// 删除所有的断点:BreackPoints种随便找一个断点 右击 有一个remove all BreakPoints
4.执行上下文
//作用域链:
真实存在的,作用域链是使用执行上下文当中变量对象所组成的链条结构(数组结构)
查找的时候其实真正是先去自身的变量对象当中查找,如果没有,去上级执行上下文的变量对象当中去查找,直到找到全局执行上下文的变量对象;
/*全局执行上下文:分为创建阶段和执行阶段 (程序开始执行后,代码执行前)
一:全局 执行上下文压入执行上下文栈)
创建上下文阶段:
1、收集全局变量形成变量对象 函数、var的变量会收集
预解析(其实在创建变量对象的时候已经做了预解析)
2、确定this指向(可以认为确定执行者)
3、创建自身执行上下文的作用域链 (全局作用域中tihs不显示,但是指向window的,函数声明的时候会把上一级作用域链提前给[[scopes]])
2、执行全局执行上下文
执行全局上下文阶段
为变量真正赋值
顺着作用域链查找要使用的变量或者函数执行
二:函数执行上下文
1、函数执行上下文压栈
1、收集变量 var 形参 arguments 函数
2、确定this指向(可以认为确定执行者)
3、创建自身执行上下文的作用域链(它是拿自己的变量对象和上一级的作用域链组成自己的作用域链)
2、执行函数执行上下文
为变量真正赋值
顺着作用域链查找要使用的变量或者函数执行
*/
Call Stack 调用栈 Scope:作用域链 Scope数组里面会有全局、局部的变量对象,,函数声明的时候就会把上级作用域链放入[[Scope]][fn0,fn,Global] ,只后会把他的变量对象插到前面,这样的他就能知道自己怎么找作用域链了
5.闭包
// 闭包产生条件: 缺一不可 Closure
1.函数嵌套
2.内部函数使用外部函数的变量
3.外部调用嵌套函数
// 闭包是啥:所谓的闭包是一个引用关系,该引用关系存在于内部函数中,引用的是外部函数的变量的对象(深入理解)
// 作用: 1.延长外部函数变量对象的生命周期
// 2.在函数外部可以操作函数内部的变量了
// 缺点: 1.内存泄漏 ==> 内存释放不了了
// 2.闭包滥用可能会内存溢出 ==> 内存装不下了
清除闭包:把外部的变量重置为null f=null
function A() {
// 声明变量
var x = 10;
var y = 20;
function B() {
// 在函数B中使用上层作用域(函数A)中的变量
console.log(x, y);
}
// 第三步: 将函数B被外部引用
// 第一种方式 将函数B返回
// return B;
// 第二种方式 将函数B作为winodw的属性
window.B = B;
// 第三种方式 将函数B作为异步操作的回调函数
// setInterval(B, 100);
document.onclick = B;
} var res = A();
\