var | const | let | |
---|---|---|---|
变量提升 | 有 | 无 | 无 |
块作用域 { } | 有 | 有 | 有 |
同一作用域下,声明同名变量 | 可以 | 不可以 | 不可以 |
声明的值、类型、变量能否改变 | 可以 | 基础数据类型不能改变,引用数据类型可以修改其属性 | 可以 |
暂时性死区域 | 无 | 有 | 有 |
window挂载 | 有 | 无 | 无 |
var 有变量提升
var a = 1;
console.log(a); // 1
console.log(a); // undefined
var a = 1;
//上面代码预解析
var a;
console.log(a); // undefined
a = 1;
const与let 没有变量提升
const b = 1;
console.log(b); // 1
let b = 1;
console.log(b); // 1
console.log(b) // 报错
const b = 1;
console.log(b) // 报错
let b = 1;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0Oud2fE-1657280554681)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220509204906927.png)]
ES5只有全局作用域、函数作用域。ES6有了块级作用域,块级就是{ }, 花括号内就是块级作用域的范围
① 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型
② 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用)因此叫做引用数据类型
① 值类型(简单数据类型):string , number , boolean , undefined , null
② 值类型变量的数据直接存放在变量(栈空间)中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHPs2caL-1657280554683)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220410164131254.png)]
① 引用类型(复杂数据类型):通过 new 关键字创建的对象(系统对象、自定义对象)、如Object、Array、Date等
② 引用类型变量(栈空间)里面存放的是地址,真正的对象实例存放在堆空间中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6efgJ5VW-1657280554683)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220410164654129.png)]
var a = 9;
let b = 9;
console.log(a, b)//9,9
{
console.log(a)//报错:a is not defined;
console, log(b)//报错:b is not defined
//这就造成了暂时性死区了(1.封闭的环境2.不存在变量提升)
var a = 8
let a = 8;
let b = 8;
console.log(a)//8;
console.log(b)//8
}
①var可以挂载
var a = 1;
console.log(window.a); // 1
②const不可以挂载
const a = 1;
console.log(a); // 1
console.log(window.a); // undefined
③let不可以挂载
let a = 1;
console.log(a); // 1
console.log(window.a); // undefined
① const定义的变量是不可以被改变的,但是这个仅限于描述定义的基本数据类型(number、string、布尔型)像(function、array、object)这样的其实是可以被改变的,(基本数据类型可直接赋值在变量上,像function、array、object这样的只是把一个地址赋值给了变量)
**注意:**const定义的变量必须要在定义的时候同时赋值;
死区:let 是定义变量的关键字,当解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行
数组解构赋值
let [a,b,c,d ] = [1,2,3,4];
consloe.log(a);
对象解构赋值
let {name,age} = {
name:'kgc',
age:18
}
console.log(name)
// 以前
var str = 'There are '
+ count + ''
// 现在
var newStr = `There are ${count}`
includes() 方法用于判断字符串是否包含指定的子字符串。
如果找到匹配的字符串则返回 true,否则返回 false。
function fn() {
let str = "Hello world";
let n = str.includes("world");
console.log(n); // true
}
fn();
repeat() 方法字符串复制指定次数。
let str = "Runoob";
let newStr = str.repeat(2);
console.log(newStr); // RunoobRunoob
返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
console.log("h".padStart(5,"o")); // "ooooh"
如果没有指定第二个参数,默认用空格填充
console.log("h".padStart(5)); // " h"
如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
console.log("h".padEnd(5,"o")); // "hoooo"
如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
常用于补全位数
console.log("123".padStart(10,"0")); // "0000000123"
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。
注意: forEach() 对于空数组是不会执行回调函数的。
<div id="box">div>
<script>
let arr = [1, 2, 3, 4, 5, 6]
arr.forEach(function (item, index) {
console.log(item, index);
box.innerHTML += item;
}, box);
script>
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
let data = [
{name:'刘哲',age:20,sex:'女'},
{name:'许博恩',age:18,sex:'女'}
]
let arr = data.map(function(item,index){
let obj = {};
obj.objName = item.name;
obj.age = item.age+10;
obj.sex = item.sex;
obj.sex = '男';
return obj
})
console.log(arr);
console.log(data);
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
let arr = [
{title:'aaa',ready:100,hot:true},
{title:'bbb',ready:100,hot:false},
{title:'ccc',ready:100,hot:false},
{title:'ddd',ready:100,hot:true}
];
let newArr = arr.filter(function(item,index){
return item.hot //返回 hot为true的
// return item.hot===false; //返回 hot为false的
})
console.log(newArr);
some() 方法用来检测数组中是否存在符合指定条件的元素,存在就返回 true,不存在就返回 false。
换个角度思考,some() 也可以用来检测数组中的所有元素是否都不符合指定条件,都不符合的话就返回 false,有一个或者多个符合的话就返回 true。
查找数组中是否存在某个元素,如果存在就返回它在数组中的索引,如果不存在另做处理
let arr = ['apple', 'banana', 'orange'];
let num;
let flag = arr.some((item, index) => {
if (item === 'banana') {
num=index;
return true
}
})
if (flag) { // 如果存在
console.log('存在,下角标为',num); // 存在,下角标为 1
} else {
console.log('不存在,下角标为',num);
}
全部符合后返回true 有一项不符合返回false
let arr = [32, 33, 16, 40];
let newArr = arr.every (function fn(age) {
return age >= 18;
})
console.log(newArr); // false
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
let arr = [65, 44, 12, 4];
let newArr = arr.reduce(function getSum(total, num) {
return total + num;
})
console.log(newArr); // 125
从右到左,从数组的末尾向前将数组中的数组项做累加
let arr = [65, 44, 12, 4];
let newArr = arr.reduceRight(function getSum(total, num) {
return total + num;
})
console.log(newArr); // 125
将类数组对象或可迭代对象转化为数组。
// 利用Set()去重
let arr = [1,1,2,2,3,3];
let newArr = Array.from(new Set(arr))
console.log(newArr); //输出[1,2,3]
keys() 下角标 values() 值
let arr = [1,5,50,4,40,50];
for(let val of arr){
console.log(val); // 1 5 50 4 40 50
}
"use strict";
function fn(){
console.log(this);
console.log(arguments);
}
fn(1,2,3);
// window.fn(1,2,3)
函数形参默认赋值 function(x=1){}
arguments
function fn(){
console.log(arguments);
}
fn(1,2,3);
区别:
声明函数:
① 函数声明必须包含名称
② 函数声明预解析时,函数部分会提升至作用域的顶部, 解析后就可以执行, 所以函数可以在声明之前可以被调用
function fn(){}
fc();
表达式函数:
① 函数表达式可以省略函数名称
② 函数表达式预解析时,函数部分不会提升,只有运行完赋值的函数部分,才能被调用,调用必须在表达式之后
var fun = function(aru){
console.log('我是函数表达式');
console.log(aru)
}
fun('pink');
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NN8ExNpp-1657280554684)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220511121741133.png)]
注意:箭头函数 不能作为构造函数 不能使用new 没有原型prototype
let fn = (a) => {
console.log("这是一个箭头函数",a)
}
fn(1); // 这是一个箭头函数 1
let fn2 = a => {
console.log("参数只有一个值",a)
}
fn2(2) // 参数只有一个值 2
①在一行上面可以不写花括号 不写return 返回值
let fn3 = () =>"这是我的返回值";
console.log(fn3()); // 这是我的返回值
let fn4 = () => {
return "这是我的返回值"
}
console.log(fn4()); // 这是我的返回值
②箭头函数没有arguments
let fn6 = () => {
console.log(arguments)
}
fn6(1,12,4) // arguments is not defined
③有rest
let fn6 = (...a) =>{
console.log(a)
}
fn6(1,12,4) // [1, 12, 4]
④this指向
1.指向绑定调用它的区域 window;
this指向定义它的对象( obj3)所指向的对象 (window )
let obj2 = {
name:'kgc',
age:18,
show:() => {
console.log(this);
document.write('当前'+this.name+'的年龄是'+this.age);
}
}
obj2.show() // this 指向window
2.this指向定义它的对象( show() )所指向的对象 ( obj3 )
let obj3 = {
name:'kgc',
age:18,
show:function(){
let fn = () => {
document.write('当前'+this.name+'的年龄是'+this.age);
}
fn()
}
}
obj3.show() // this 指向 obj3
属于浅拷贝
let obj = {name:'kgc',age:12};
let obj2 = {head:1,foot:2}
// let obj3 = {...obj,...obj2};
// console.log(obj3);
let obj4 = Object.assign(obj,obj2);
console.log(obj4);
console.log(obj);
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。
可以使用 new 来调用 Promise 的构造器来进行实例化
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
var myPromise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
reject('失败!')
});
//成功执行
myPromise.then((success)=>{
//success的值是上面调用resolve(...)方法传入的值.
//success参数不一定非要是字符串类型,这里只是举个例子
return success+1
document.write("Yay! " + successMessage);
}).then((success)=>{
document.write("Yay! " + successMessage);
});
//失败执行
myPromise.catch(function(fail){
document.write("NO! " + failMessage);
})
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then() 是 promise 最为常用的方法。
promise.then(onFulfilled, onRejected)
promise简化了对error的处理,上面的代码我们也可以这样写:
promise.then(onFulfilled).catch(onRejected)
![
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTdSGrQ9-1657280554685)(https://www.liaoxuefeng.com/files/attachments/1027242914217888/l)]
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(也就是回调地狱)。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
下面代码为回调地狱
function akax(url,data={}){
return new Promise((resolve,reject)=>{
var req = new XMLHttpRequest();
req.open("GET",url,true);
req.onload = function(){
if (req.status === 200) {
resolve(req.responseText)
}else{
reject(new Error(req.responseText))
}
}
req.onerror = function(){
reject(new Error(req.responseText))
}
req.send()
})
}
ajax("data.json").then(res=>{
console.log(res);
})
上面代码中,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
var p1 = new Promise(function(resolve, reject){
// ... some code
});
var p2 = new Promise(function(resolve, reject){
// ... some code
resolve(p1);
})
上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。
Promise.prototype.then 方法返回的是一个新的 Promise 对象,因此可以采用链式写法。
let promise = new Promise((resolve,reject)=>{
resolve(1)
})
promise.then(res=>{
console.log(res);
return res+1
}).then(res=>{
console.log(res);
return res+1
}).then(res=>{
console.log(res)
})
上面的代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。
function ajax(url,data={}){
return new Promise((resolve,reject)=>{
var req = new XMLHttpRequest();
req.open("GET",url,true);
req.onload = function(){
if(req.status ===200){
resolve(req.responseText)
}else{
reject(new Error(req.responseText))
}
};
req.onerror =function(){
reject(new Error(req.statusText))
};
req.send()
})
}
ajax("data.json").then(res=>{
console.log(JSON.parse(res))
return ajax("data.json")
}).then(res=>{
console.log(res)
})
这种设计使得嵌套的异步操作,可以被很容易得改写,从回调函数的"横向发展"改为"向下发展"。
Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。
getJSON("/posts.json").then(function(posts) {
// some code
}).catch(function(error) {
// 处理前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。
ajax("data.json").then(res=>{
console.log(JSON.parse(res))
return ajax("data.json")
}).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
p 的状态由 p1、p2、p3 决定,分成两种情况。
下面是具体的案例
var promise = ["users","goods","data"].map((item,index)=>{
return ajax(item+".json")
})
Promise.all(promise).then(res=>{
console.log(res)
})
Promise.race()
var p = Promise.race([p1,p2,p3]);
只要有一个promise状态改变 外层promise状态就会改变 率先改变的promise的返回值就会传递给外层的返回值
function axios(type=get,url,data={}){
return new Promise((resolve,reject)=>{
$.ajax({
type:type,
url:url,
data:data,
timeout:5000,
success:function(res){
resolve(res)
}
})
})
}
function timeout(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("请求超时")
},1)
})
}
Promise.race([timeout(),axios("get","data.json")]).then(res=>{
console.log(res)
})
async函数是用来取代回调函数的另一种方法
只要函数名之前加上async关键字,就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象,前面用await关键字注明。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncValue(value) {
await timeout(50);
return value;
}
上面代码中,asyncValue函数前面有async关键字,表明函数体内有异步操作。执行的时候,遇到await语句就会先返回,等到timeout函数执行完毕,再返回value。
async函数并不属于ES6,而是被列入了ES7,但是traceur编译器已经实现了这个功能。
js是单线程语言,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?答案就是回调函数,整个程序是事件驱动的,每个事件都会绑定相应的回调函数,举个栗子,有段代码设置了一个定时器
setTimeout(function(){
console.log(time is out);
},1000);
执行这段代码的时候,浏览器异步执行计时操作,当1000ms到了后,会触发定时事件,这个时候,就会把回调函数放到任务队列里。整个程序就是通过这样的一个个事件驱动起来的。
所以说,js是一直是单线程的,浏览器才是实现异步的那个家伙。
promise主任务是主线程执行 与log按顺序执行
导图要表达的内容用文字来表述的话:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
异步任务有宏任务和微任务。
2.宏任务macrotask:
(事件队列中的每一个事件都是一个macrotask)
优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
比如:setImmediate指定的回调函数,总是排在setTimeout前面
3.微任务包括:
优先级:process.nextTick > Promise > MutationObserver
下面这个代码输出结果是什么?
主程序和和settimeout都是宏任务,两个promise是微任务
第一个宏任务(主程序)执行完,执行全部的微任务(两个promise),再执行下一个宏任务
原型
静态方法 直接 用Person.fn = function (){}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u172XCYR-1657280554688)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220512100216991.png)]
没有函数提升
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXZ7p275-1657280554688)(C:\Users\Timi\AppData\Roaming\Typora\typora-user-images\image-20220512094037346.png)]
static 静态方法 不能继承 用Person直接调用 首先加载的是静态方法 优化性能
cunstructor 里面有一个 super() 管道
静态方法 不能继承
继承的必须先执行super才能执行this
class Person {
// 构造器构造属性
constructor(foot, head) {
this.foot = foot;
this.head = head;
}
// 方法
show() {
console.log(this.foot, this.head);
}
// 静态方法 首先加载的是静态方法
static fn() {
console.log('这是Person的静态方法');
}
}
console.log(new Person(2, 1).foot);
console.log(new Person(2, 1).head);
Person.fn();
console.log('---------');
// Student 继承 Person 直接extends
class Student extends Person {
constructor(name, age, ...obj) {
super(...obj);
this.name = name;
this.age = age;
}
show2() {
super.show();
console.log(this.name, this.age);
}
static fn2() {
console.log('这是Student的静态方法');
}
}
let student = new Student('张三', 18, 2, 1);
student.show2();
Student.fn2(); //静态方法不能继承 undefined
在return之前 提前返回 用async 和await
instanceof 同来检测是否为 数组或者是对象
hansOwnProperty()判断对象中是否含有特定的属性 有返回true 没有返回false
// 使用深拷贝
let obj = {
name: 'kgc',
age: 15,
arr: [21, 5, 56, 8],
show: undefined,
num: null
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj); // 识别不到undefined 自动避开未拷贝show 可以识别到num 显示值为null
deepClone = (obj) => {
if (typeof obj !== 'object') return;
let newObj = obj instanceof Array ? [] : {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
}
}
return newObj
}
let newObj2 = deepClone(obj);
console.log(newObj2);// 可以识别到show 可以识别到num 显示值为{}