let && const
let a=10;
{
console.log(a);
let a=100;
}
//let 声明的变量会绑定到当前的作用域上
//Uncaught ReferenceError: Cannot access 'a' before initialization
let fn=function a(){
}
console.log(fn);
console.log(a)//a is not defined
var fn=function a(){
}
console.log(fn);
console.log(a)//a is not defined
//var 和 let 都一样都获取不到函数名 a
解构赋值&剩余运算符&展开
解构赋值
//数组解构赋值:
let [name,age]=['zhangsan',18];
//只获取第二项
let [,age]=['zhangsan',18];
//对象的解构赋值
let {name,age}={name:'zhangsan',age:18};
//变量重命名
let {name:hisName,age:hisAge}={name:'zhangsan',age:18}
//默认赋值
let {name,age,address='默认地址'}={name:'zhangsan',age:18}
console.log(hisName,hisAge)
对象的解构赋值
剩余运算符
//需求 从['zhangsan',18,'成都']获取zhangsan,并将 18,成都 在组成新的数组
let [name,...args]=['zhangsan',18,'成都'];
console.log(name,args);
//需求 从['zhangsan',18,'成都']去掉第一项后面的组成数组
//ES5:
var arr=['zhangsan',18,'成都'];
arr.splice(0,1);
console.log(arr);
//ES6:
let arr=['zhangsan',18,'成都'];
[,...args]=arr;
console.log(args);
//对象的剩余运算符
let zhangsan={name:'zhangsan',age:18,address:'成都'};
let {name,...args}=zhangsan;
console.log(args);//{age:18,address:'成都'}
//该方法可用于copy对象:
let zhangsan={name:'zhangsan',age:18,address:'成都'};
let lisi=zhangsan
console.log(zhangsan===lisi);
let {...args}=zhangsan;
console.log(args===zhangsan);
展开
//数组的展开:
let arr=[1,2,3];
console.log(...arr);
//运用展开运算符 合并数组:
let a1=[1,2,3];
let a2=[4,5,6];
let a3=[...a1,...a2];
//扩展 合并去重:
let a1=[1,2,3];
let a2=[3,2,5,6];
let a3=[...a1,...a2];
let a4=[...new Set(a3)];
//扩展 交集
let s1=new Set(a1);
let s2=new Set(a2);
let a3=a1.filter((item)=>s2.has(item))
console.log(a3);
//扩展 并集
let a3=[...a1,...a2];
let a4=[...new Set(a3)];
//扩展 差集
let s1=new Set(a1);
let s2=new Set(a2);
let a3=a1.filter((item)=>!s2.has(item))
console.log(a3);
//对象的展开(浅拷贝,只拷贝了一层) 功能同 Object.assign()
let zhangsan_name={name:"zhangsan"};
let zhangsan_age={age:20};
let zhangsan_address={address:"成都"};
let zhangsan={...zhangsan_name,...zhangsan_age,...zhangsan_address};
console.log(zhangsan);
//{name: "zhangsan", age: 20, address: "成都"}
//注意点:同名的key会被后面的key代替掉
//扩展:如何实现一个深拷贝呢?
//可以用 JSON.parse(JSON.stringify(newObj))实现 但有缺憾,不能很好的识别函数,正则表达式
//例子:
//浅拷贝例子:
let zhangsan_name = { "name": "zhangsan", "age": 18, son: { "name": "lisi", "age": 1 } }
let zhangsan_address = { "address": "成都" };
let zhangsan = { ...zhangsan_name, ...zhangsan_address };
console.log("zhangsan==>", zhangsan);
zhangsan.son.name = "wangwu";
console.log("zhangsan==>", zhangsan);
console.log("zhangsan_name==>", zhangsan_name);//跟着一起改变了
//深拷贝
let deepClone = (value,hash=new WeakMap) => {
//判断是否是null值或是undefined
if (value == null) {
return value;
}
//判断是否是普通类型或是函数类型,如果是普通类型则返回
if (typeof value !== 'object') {
//如果不是object,则是普通类型
return value;
}
//如果是object类型,则判断类型,构建不同的类型
if (value instanceof Date) {
return new Date(value)
}
if (value instanceof RegExp) {
return new RegExp(value)
}
//其他对象类型,或是数组类型
//如果是对象,则会获取一个新的{}
//如果是数组,则会获取一个新的[]
let newValue = new value.constructor;
if(hash.has(value)){
return hash.get(value)
}
hash.set(value,newValue);
for (const key in value) {
if (value.hasOwnProperty(key)) {
newValue[key] = deepClone(value[key],hash);
}
}
return newValue;
}
let zhangsan_name2 = { "name": "zhangsan", "age": 18, son: { "name": "lisi", "age": 1 } }
let zhangsan_address2 = { "address": "成都" };
let zhangsan2 = { ...deepClone(zhangsan_name2), ...deepClone(zhangsan_address2) }
console.log("zhangsan2===>", zhangsan2);
zhangsan2.son.name = "wangwu";
console.log("zhangsan2 new ===>", zhangsan2);
console.log("zhangsan_name2===>", zhangsan_name2);
Object.defineProperty & Proxy
//Object.defineProperty的getter 和 setter简写方式:
let obj={
_a:'',
get a(){
console.log("获取a的值");
return this._a
}
set a(value){
console.log("设置a的值");
this._a=value;
}
}
console.log(obj.a);//获取a的值
// Object.definfProperty的基本写法
let obj = {};
Object.defineProperty(obj, 'a', {
writable: true,//是否可以被更改
enumerable: true,//是否可以被循环出来 for-in
configurable:true,//是否可以被删除
value: 100
})
obj.a = 20;
delete obj.a;
console.log(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const item = obj[key];
console.log("item==>", item);
}
}
//使用getter和setter
let obj = {};
let _a;
Object.defineProperty(obj, 'a', {
// writable: true,//是否可以被更改
enumerable: true,//是否可以被循环出来 for-in
configurable: true,//是否可以被删除
get() {
return _a;
},
set(value) {
console.log("in a set");
_a = value;
}
})
obj.a = 20;
delete obj.a;
console.log(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const item = obj[key];
console.log("item==>", item);
}
}
//使用 Object.defineProperty实现监控一个对象
let obj = {
name: "zhangsan",
age: 18,
son: {
name: 'lisi',
age: 2
}
}
//监控对象(问题,不能监控数组的长度的变化,因为是单个属性进行监控,新增的属性不能有效的监控到)
let observer = (obj) => {
if (typeof obj !== 'object') {
//普通类型
return;
}
//如果是对象类型
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
let value = obj[key];
//可能这个value值也是一个对象
//对value值再进行一次监控(如果是普通值则不会被监控,如果是对象则会再次被监控)
observer(value);
Object.defineProperty(obj, key, {
get() {
//取值
console.log("this is get ");
return value;
},
set(val) {
//设值
console.log("this is set");
value = val;
}
})
}
}
}
observer(obj);
console.log(obj.name);
obj.name = 'lisi'
console.log(obj.name);
console.log(obj.age);
console.log(obj.son.name);
//Proxy 代理:
//拦截
//使用Proxy实现监控对象属性
//监控对象,set方法只能监控一级
let _zhangsan = {
name: 'zhangsan',
age: 18,
son: {
name: 'lisi',
age: 1
}
}
zhangsan = new Proxy(_zhangsan, {
get(target, key) {
console.log("进入get");
return target[key];
},
set(target, key, value) {
console.log("进入set");
target[key] = value;
}
})
zhangsan.name = 'lisi';
zhangsan.age = 20;
console.log(zhangsan.name);
console.log(zhangsan.age);
zhangsan.son.name = 'wangwu';
console.log(zhangsan.son.name);
//监控数组
let _arr = [1, 2, 3];
arr = new Proxy(_arr, {
get(target, key) {
console.log("进入get");
return target[key];
},
set(target, key, value) {
console.log("进入set");
target[key] = value;
}
});
arr.push(1);
//实现Proxy监控多级对象
let _zhangsan = {
name: 'zhangsan',
age: 18,
son: {
name: 'lisi',
age: 1
}
}
let observer = (obj) => {
let cache = new WeakMap;
let handler = {
get(target, key) {
console.log("进入get");
if (typeof target[key] === 'object' && target[key] != null) {
if (cache.has(target[key])) {
return cache.get(target[key]);
}
let newProxy = new Proxy(target[key], handler);
cache.set(target[key], newProxy);
return newProxy;
}
return target[key];
},
set(target, key, value) {
console.log("进入set");
target[key] = value;
}
}
return new Proxy(obj, handler)
}
let zhangsan=observer(_zhangsan);
zhangsan.son.name='lisi';
代码是如何运行的
- 代码读取
script
脚本中的代码从上到下依次读取依次执行(执行JS的主进程) - 当执行栈遇到异步代码(
click
的函数,setTimeout
,ajax
等)会去开辟另外的线程去执行这些操作 - 当settimeout的时间到了,或是
ajax
的回调完成了,就会把这些回调放入任务队里中(微任务/宏任务)
- 当主任务(JS执行的主栈)任务全部做完后,JS会从
任务队列中(优先是宏任务)
拿第一个任务在主任务中执行 - 循环罔替
//分析下一下代码:
setTimeout(() => {
console.log("1");
setTimeout(() => {
console.log("3");
}, 0);
}, 0);
setTimeout(() => {
console.log("2");
setTimeout(() => {
console.log("4");
}, 0);
}, 0);
//1. JS执行主栈拿到以上一段代码
//2. 执行时发现第一个异步任务settimeout不去执行
//3. 执行时又发现了第二个异步任务 settimeout 不去执行
//4. 扫描时发现第一个settimeout执行的时间到了,将它放入宏任务队列中
//5. 扫描时发现第二个settimeout执行的时间也到了,也将它放入宏任务队列中
//6. JS执行主栈从宏任务中取出第一个宏队列中的任务(第一个settimeout)开始执行此刻打印了 1
//7. JS执行主栈又发现了一个异步任务 settimeout 不去执行
//8. 此刻JS执行主栈又空了,再从宏队列中获取第一个任务并开始执行 此刻打印了 2
//9. 打印3和4和打印1 2 类似
基本知识点
先执行宏任务,再执行微任务(因为最先执行的是script脚本中的代码,而script脚本也是一个宏任务)
UI的渲染和JS的执行是互斥的
每次JS执行主栈完成后都将把微任务进行清空,然后再从宏任务中取出一个任务进行执行
微任务
微任务:promise.then
MutationObserver
process.nextTick
每次当主栈中的任务执行完成后 微任务列表都将被清空
setTimeout(() => {
console.log("time0");
Promise.resolve().then(() => {
console.log("then0");
})
});//放入宏任务
setTimeout(() => {
console.log("time1");
});//放入宏任务
Promise.resolve().then(() => {
console.log("then1");
})//放入微任务
Promise.resolve().then(() => {
console.log("then2");
})//放入微任务
//执行jS主栈 将settimeout放入宏任务 promise.then放入微任务
//js主栈执行完成,清空微任务列表 then1 then2 被执行
//从宏任务中拿出第一个settimeout并执行 打印time0 并将then0 放入微任务
//js主栈执行完成,清空微任务列表 then0被执行
//从宏任务中拿出第二个settimeout并执行打印time1
宏任务
微任务:script
ajax
事件(click)
requestFrameAnimation
setTimeout
setInterval
setImmediate(IE的方法)
MessageChannel
I/O
UI rendering
每次只取一个队列中的任务出来执行
ES6的模块化 && Node的模块化
export let a=10 导出变量
//导出两个变量 a 和 b
export let a=10;
export let b=10;
// 等于于
const a=10;
const b=20;
export {
a,b
}
//导出的变量(接口)而不是值
//错误:
export {
a:10,
b:20
}
//导出时候修改别名
exoirt {
a as c,b
}
//将a名称修改成c
//实例:
//a.js文件
let a=10;
let b=20;
setInterval(() => {
a++
}, 1000);
export {
a,b
}
//b.js
import {a,b} from './a';
setInterval(()=>{
console.log(a)
},1000)
//b文件中的打印信息会改变吗
// 答应是会的 因为 export导出的是变量,而不是值,所以变量的值改变了再去取的值,会改变
//导入的 可以把它看成是一个接口,每次取的值其实是调用一个接口
import只能放到最外的作用域中
并且import语法会被解析到页面的最顶部
导入的变量不能更改它的值
export default 默认导出
和
export default
的区别export
导出的是一个变量(接口)export default
是导出的一个值
例如: 可以使用export default 'hello'
但不能有export 123
;
export default xx
的原理(不同,因为export default
导出的是值,而export导出的是变量
):
let a=10;
let b=20;
let c=30;
export default c
export {
a,b,
}
//等价于:
let a=10;
let b=20;
let c=30;
export {
a,b,c as default
}
// 引用方式
import xxx from './a';
使用
import
导入全部的属性
import * as obj from './a'
//这个时候 obj中就有了全部导入的属性了
如何接收默认导出的对象
//文件a.js
export let a=10;
export let b=20;
export default 30;
//文件b.js
import c,{a,b} from './a.js';
合并导出
export * from './a';
export * from './b';
副作用引用(文件会被执行,但引用的地方没用使用它)
import './a.css'
import './a.js'
动态导入
什么叫动态导入,当我代码执行到一个地方时,再去加载一个js文件
原理是 通过webpack
自动拆分成一个新的文件,再用jsonp的方式引用
let button=document.getElementById("btn")
btn.onclick=function(){
//动态引入file.js文件,并且执行
import('./file.js').then(data=>{
console.log("data");
})
}
instanceof 的原理:
我们在使用 instanceof进行实例判断时 其实 实例会去找它的proto属性的值,如果能找到,则返回true
class My {
}
let a = new My();
a.__proto__ = Array.prototype;
console.log(a instanceof Array);//true
Reflect && Symbol && ES6模块 && class类
Reflect 反射
```javascript
//1.get && set 设置/获取属性的值
//用Reflect设置和获取obj的值
let obj = {};
Reflect.set(obj, 'name', 'zhangsan');
console.log(obj.name);
console.log(Reflect.get(obj,'name'));
//2.has 判断对象中是否有属性
//使用Reflect判断key是否在对象中存在
let zhangsan={name:"zhangsan"}
//使用in
console.log("name" in zhangsan);
console.log(Reflect.has(zhangsan,'name'));
//3.defineProperty 定义对象属性
//使用Reflect设置属性
let zhangsan = { name: 'zhangsan' };
Object.defineProperty(zhangsan, 'name', {
value: 'zhangsan'
});
//用法和Object.defineProperty相同
Reflect.defineProperty(zhangsan,'age',{
value:18
});
console.log(zhangsan);
//为什么要用Reflect.defineProperty呢?
//例子:
let zhangsan = { name: "zhangsan" };
Object.freeze(zhangsan)
Object.defineProperty(zhangsan, 'a', {
value: 100
});
//用Object.freeze方法将zhangsan对象进行冻结,然后在使用Object.defineProperty的方式对zhangsan的属性进行赋值
//此时因为zhangsan已经被冻结了,再定义属性时会报错
//而如果使用Reflect则不会
let zhangsan = { name: "zhangsan" };
Object.freeze(zhangsan)
let flag=Reflect.defineProperty(zhangsan,'age',{
value:18
})
console.log(flag);
console.log(zhangsan);
//该方法会有一个返回值,标识是否设置成功
//4.getOwnPropertyDescriptor 获取属性描述符 value writeable enumerable configurable
let zhangsan = { name: "zhangsan" };
console.log(Object.getOwnPropertyDescriptor(zhangsan,'name'));
//5.ownKeys 获取对象全部的key(包括Symbol的属性)
let zhangsan = { name: "zhangsan",[Symbol()]:18 };
console.log(Object.keys(zhangsan));//获取对象的全部key,但不能获取到symbol的属性
console.log(Object.getOwnPropertyNames(zhangsan));//获取对象的全部key,但不能获取到symbol的属性
console.log(Object.getOwnPropertySymbols(zhangsan));//获取对象的全部Symbol的key值
console.log(Reflect.ownKeys(zhangsan));
//6.apply
//使用普通的aplly方法:
let fn=function(a,b){
console.log(this,a,b);
}
fn.apply(1,[2,3]);
//当fn中有一个同名方法:apply呢
fn.apply=function(){
console.log("this is apply")
}
//如何调用原型链上的apply呢?
//直接调用prototype上的apply方法
Function.prototype.apply.call(fn);
//使用Reflect如何实现呢?
Reflect.apply(fn,1,[2,3]);
//上面的两个方法是完全等价的
//7.deleteProperty删除属性
//使用delete 属性的方法:
let zhangsan = { name: "zhangsan" };
Object.freeze(zhangsan)
delete zhangsan.name;
console.log(zhangsan);
//没有报错,也没有删除掉
//使用Reflect.deleteProperty
let zhangsan = { name: "zhangsan" };
Object.freeze(zhangsan)
let flag = Reflect.deleteProperty(zhangsan,'name');
console.log(flag);
//删除失败了会有返回值告诉我们 删除失败了
//8.preventExtensions 不允许对象添加字段
let zhangsan = { name: "zhangsan" };
Object.preventExtensions(zhangsan);
zhangsan.age=20;
console.log(zhangsan);
//功能相同,多了一个返回值
let zhangsan = { name: "zhangsan" };
let flag=Reflect.preventExtensions(zhangsan);
zhangsan.age=20;
console.log(flag);
console.log(zhangsan);
//9.isExtensible判断对象是否可以被扩展的
let zhangsan = { name: "zhangsan" };
let flag=Reflect.preventExtensions(zhangsan);
console.log(Object.isExtensible(zhangsan));
let zhangsan = { name: "zhangsan" };
let flag=Reflect.preventExtensions(zhangsan);
console.log(Reflect.isExtensible(zhangsan));
```
Symbol 他的值是独一无二
- 作为常量使用
const s1=Symbol('zhangsan');
const s2=Symbol('zhangsan');
console.log(s1==s2);
- 属性私有化
- Symbol.for
//Symbol('zhangsan') 是每次创建一个Symbol 而使用Symbol.for是先去找有相同key的Symbol没,没有才创建,如果有直接返回
const s1=Symbol.for('zhangsan');
const s2=Symbol.for('zhangsan');
console.log(s1===s2)//true
- Symbol.keyFor() 获取Symbol的key值
const s1=Symbol.for('zhangsan');
Symbol.keyFor(s1)//zhangsan
- 将Symbol当成对象的属性
const s1=Symbol('name');
let zhangsan={
[s1]:'zhangsan'//将s1的值当成zhangsan对象的key
}
console.log(zhangsan[s1]);//这里不能使用"."来获取值
- 元编程,
6.1. 通过Symbol.hasInstance
改变原js的能力
let o = {
name: 'zhangsan'
}
let obj = {
}
// console.log(o instanceof obj);
//Right-hand side of 'instanceof' is not callable
//使用Symbol.hasInstance()方法改变js的instanceof方法
//Symbol.hasInstance 当调用:instanceof obj时,会去调用使用Symbol.hasInstance()
obj = {
[Symbol.hasInstance]() {
return 'name' in o
}
}
console.log(o instanceof obj);
6.2. 通过Symbol.toStringTag
改变对象的toString
行为
//原来的写法:
let zhangsan={
toString(){
return "zhangsan"
}
}
console.log(zhangsan+"")
//使用Symbol的写法:
let zhangsan={
get [Symbol.toStringTag](){
return 'zhangsan'
}
}
console.log(zhangsan+"")//[object zhangsan]
- 控制衍生对象的类的构造函数
class MyArray extends Array {
constructor(...args) {
super(...args)
}
static get [Symbol.species](){
return Array;//控制衍生对象的所属类型
}
}
let myArr = new MyArray(1,2,3);
console.log(myArr);
console.log(myArr instanceof MyArray);
console.log(myArr instanceof Array);
//newArr就是衍生对象
let newArr=myArr.map(item=>item*2);
console.log(newArr);
console.log(newArr instanceof MyArray);
网友评论