let const
var 声明变量 全局作用域 let 声明变量 可以重新赋值 const 声明常量
深拷贝和浅拷贝的区别
当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
1深拷贝是指针 是值
浅拷贝是地址 比如b=a
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
- 如果是基本数据类型,名字和值都会储存在栈内存中
var a = 1;
b = a; // 栈内存会开辟一个新的内存空间,此时b和a都是相互独立的
b = 2;
console.log(a); // 1
当然,这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。
- 如果是引用数据类型,名字存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值
2 常用实现:
1 扩展运算符
2 JSON.stringify(obj)
3 lodash _.cloneDeep(test)
4 slice
5 手动 obj.a=xxx
js执行机制
js单线程,同一时间只能做一件事,一切的多线程都是通过单线程模拟出来的 事件循环机制:event loop 同步:进入主线程 异步:进入事件表并注册函数 当指定的事情完成之后,事件表会将这个函数移入到任务队列,主线程内任务执行完毕之后,回任务队列读取对应的函数,移入到主线程执行,不断重复
JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是Ajax请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
也就是说setTimeout只能保证在指定的时间过后将任务(需要执行的函数)插入队列等候,并不保证这个任务在什么时候执行。执行javascript的线程会在空闲的时候,自行从队列中取出任务然后执行它。javascript通过这种队列机制,给我们制造一个异步执行的假象。
当我们执行以下代码时,结果会按1、3、2的顺序弹出。
setTimeout(function() {
alert(2);
},0);
alert(3);
这是事件循环机制,因为js是单线程的,是基于事件循环的。而setTimeout函数是异步的,异步的事件会加入一个队列,会等到当前同步的任务执行完毕后,再执行setTimeout队列的任务。所以,通过设置任务在延迟0毫秒后执行,就能改变任务执行的先后顺序,延迟该任务发生,改变它所调用的函数的优先级,使之异步执行。
-
下面这段代码,是不会实时获取到输入框里面的内容的。
<input type="text" onkeydown="test(this.value)">
<div></div>
<script">
function test(val) {
document.getElementsByTagName('div')[0].innerHTML = val;
}
可以发现,每按下一个字符时,< div > 中只能显示出之前的内容,无法得到当前的字符。结果如下:
- 利用 setTimeout延时为0,我们把取 value 的操作放入队列中,放在更新 value 值以后.
虚拟dom和真实dom区别
ts的interface和type区别 如何在里面写方法
type
type (类型别名),顾名思义,类型别名只是给类型起一个新名字。它并不是一个类型,只是一个别名而已
-
可以定义基本类型别名,如
type StringType = string
-
可以声明联合类型,如
type paramType = number | string;
-
可以声明元组类型,如
type arrType = [string, string, number];
元组 Tuple,元组类型允许标示一个已知元素数量和类型的数组,各元素的类型不必相同。
interface
interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
都可以定义一个对象或函数
我们来看一下如何定义函数。
type addType = (num1:number,num2:number) => number
interface addType {
(num1:number,num2:number):number
}
不同点
1. 声明类型
type作为类型别名,可以给任何类型起别名
interface基本只用来给对象定义类型接口
2. 重复声明
interface可以重复声明,会合并接口类型
type会报错
type 一旦定义就不能再添加新的属性,而 interface 总是可扩展的
如果是type,重复声明会报错
3. 类
interface可以使用implements关键字来约束类
一、新增数据类型Symbol。
概念:
Symbol代表独一无二的
Symbol类型的值通过Symbol函数来生成,同时Symbol函数生成的值是唯一的
Symbol函数何以接收字符串作为参数,但是即使相同参数返回的值也是唯一的
作用:
属性私有化
数据保护
//没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 //false
//有参数的情况
let s1 = Symbol("fun");
let s2 = Symbol("fun");
s1 === s2 //false
let mySymbol = Symbol();
//第一种写法
let a = {};
a[mySymbol] = "Hello!";
//第二种写法
let a = {
[mySymbol]: "Hello!"
}
//第三种写法
let a = {};
Object.defineProperty(a,mySymbol,{value: "Hello!"});
//枚举Symbol的key值
Object. getOwnPropertySymbols(obj);
//注意,Symbol作为对象的key值不能被for in进行遍历。
二、块级作用域。
概念:在ES6中,凡事{}包裹的代码都是跨级作用域,凡事在块级作用域中用let、const声明的变量都有一个暂时性死区。
{
let a = 20;
}
console.log(a); //报错
三、var、let、const声明变量。
var
支持变量声明与解析
不支持块级作用域
可以重复声明。
let
不支持变量声明与解析
支持块级作用域
不可以重复声明
用let声明的变量或者方法只能在代码块中生效。
{
let a = 10;
var b = 20;
}
console.log(a); //报错
console.log(b); //20
const
不支持变量声明与解析
支持块级作用域
不可以重复声明
声明变量,一旦声明不可修改
声明变量必须赋值,不可以像var一样先声明后再定义。
四、解构赋值。
概念:允许按照一定格式,从对象和数组中提取值。
//数组解构
let [a,b,c] = [1,2,3];
console.log(a,b,c); //1,2,3
//对象解构,对象解构时key值必须要一一对应。
let {name,age} = {name: "张三",age: 20};
console.log(name,age); //张三 20
//对象解构+别名
let {name: _name, age: _age} = {name: "张三", age: 20};
console.log(_name,_age); //张三 20
//多重解构
let {obj:{name},arr:[a,b]} = {obj:{name: "张三"},arr: [10,20]};
五、扩展运算符
概念:将数组或对象转换成参数序列,使用逗号分割的序列。
作用:
1、数组、对象的合并
2、函数剩余参数
3、替代arguments
//数组合并
let arr1 = [10,20,30];
let arr2 = [40,50,60];
let newArr = [...arr1,...arr2];
console.log(newArr); //[10,20,30,40,50,60];
//展开数组
console.log(Math.Max(...arr));
//对象合并
let obj1 = {width: 100, height: 100};
let obj2 = {left: 100, top: 100};
let newObj = {...obj1,...obj2};
console.log(newObj); //{width:100,height:100,left:100,top:100};
六、字符串模版。
1、字符串太长需要换行怎么办?
//常规解决办法
var a = "<div>" +
"<span>"+num+"</span>"
"</div>";
//ES6语法
let a = `<div>
<span>${num}</span>
</div>`
2、字符串太长怎么办?
let phone = 15844423232
let intro = `my name is chj, my phone id ${phone}`;
3、includes字符串搜索。
//ES6神器,includes方法,str.insludes(内容),找到了返回true,没找到返回false
let str = "good method!";
str.includes("method"); //true
4、判断首尾,startsWith endsWith
/*
startsWith判断是否位于头部
endsWith判断是否位于尾部
这两个方法是includes的扩展
*/
let str = "how are you?";
str.startsWith("how"); //true
str.endsWith("?") //true
5、repeat字符串重复
//str.repeat(n); 将字符串重复n次
let str = "abc";
str.repeat(3); //abcabcabc
七、对象新增方法。
1、对象的简写
let a = 10;
let obj = {a};
//等价于
let obj = {a: 10};
//当key值和value值一样的时候我们可以写一个。
2、Object.is
//判断两个对象是否指向同一个内存地址
let obj1 = {a: 1, b: 2};
let obj2 = obj1;
Object.is(obj1, obj2); //true
3、Object.assign
//合并对象
let obj1 = {name: '曹海杰', age: 20};
let obj2 = {sex: '男'};
let newObj = Object.assign(obj1, obj2);
console.log(newObj); //{name: '曹海杰', age: 20, sex: '男'};
八、数组中新增的方法。
1、Array.of()
//将一组值转换为数组
let arr = Array.of(1,2,3,4);
console.log(arr); //[1,2,3,4];
2、Array.from()
//将伪数组转换为数组。
let aLi = Array.from(document.getElementsByTagName("li"));
console.log(aLi instanceof Array); //instanceof判断某对象是否属于某类的实例。
3、Array.find()
//通过条件查找数据,返回第一个符合条件的数据。
let arr = [1,2,3,4];
let n = arr.find(function(item, index, array) {
return item > 3;
})
console.log(n); //4
4、Array.findIndex()
//查找数组中符合条件的数据的下标,如果没有找到则返回undefined
let arr = [1,2,3,4];
let n = arr.findIndex(function(item, index , array) {
return item > 3;
})
console.log(n);
5、Array.fill();
//对数组进行填充,语法:arr.fill('内容',开始下标,结束下标);
let arr = [1,2,3,4];
arr.fill('qwe', 1, 3);
console.log(arr); //[1, 'qwe', 'qwe', 4];
九、for of and for in
/*
1、for of 是ES6的,for in 是ES5的
2、for of 遍历的是值,for in遍历的是键
3、for of 不能遍历对象,for in 既可以遍历对象也可以遍历数组
4、for of 遍历数组的时候,如果有未定义的项,遍历出来的是undefined,for in 则遍历不到
5、for of 不能遍历到原型上定义的属性(自定义属性),for in 可以遍历到
6、for of 的兼容性不是很好,移动端安卓微信浏览器不支持,Safari支持
*/
Array.prototype.hehe = '呵呵';
let arr = [1,2,3, ,4];
for (let item of arr) {
console.log(item); //1,2,3,undefined,4
}
for (let prop in arr) {
console.log(prop); //0,1,2,4,hehe
}
let obj = {
name: 'chj',
age: 20
}
for (let item of obj) { //报错
console.log(item);
}
for (let prop in obj) {
console.log(prop); //name age
}
十、函数
1、函数参数默认值
//ES6之前函数怎么设置默认值
function fn(x) {
let x= x || 10;
}
//ES6函数默认值,等价于上面的写法,如果没有传递参数,就使用默认值10
function fn(x=10) {
}
2、剩余参数
//fn函数中a接收实参1,...rest接收剩余参数为一个数组。
funciton fn(a, ...rest) {
console.log(...rest); //[2,3,4,5];
}
fn(1,2,3,4,5);
3、箭头函数
//语法一 function 换成 () =>
let fn = (a) => {
console.log(a)
}
//语法二 不写{}默认表示return,当前函数意思是返回a这个值
let fn = a => a
fn(10);
//语法三 不写{}默认表示return,当前函数表示返回一个对象
let fn = a => ({a:1});
/*
箭头函数特点:
1、this指向离自己最近的外层作用域的对象
2、不能当作构造函数使用(箭头函数是匿名函数,没有函数名字所以没有办法new)
3、没有arguments这个参数(ES6已经取消arguments这个参数了)
4、不能当作generator函数
*/
十一、Set集合
/*
Set: 集合
1、类似于数组,但成员的值是唯一的,没有重复的值,并且是无序的
2、Set是一个构造函数
3、Set每次执行完毕后都会返回一个Set,因此可以进行链式调用
*/
let s = new Set();
//添加 add()
s.add("a").add("b");
console.log(s); //Set(2) {"a","b"};
//删除 返回值是一个布尔值
s.delete("a") ; //true
//判断元素是不是Set的成员,返回值是一个布尔值
s.has("a"); //true
//清除所有 没有返回值
s.clear();
//返回所有键名
s.keys();
//返回所有value值
s.values();
//返回所有键值对
s.entries();
//可以通过for of 遍历每一个值
for(let item of s) {
console.log(s);
}
十二、Map字典类型结构
/*
1、字典:用来存储不重复key的hash结构,不同于Set集合,字典使用[键,值]的形式来存储
2、Map执行完毕后都会返回一个Map,可以进行链式调用
3、特点:普通对象只能通过字符串来当作key值,但是Map可以使用任何值来当作key值
*/
//创建Map对象
let map = new Map({
["a", 1],
["b", 2]
})
console.log(map); //Map(2) {"a" => 1, "b" => 2}
//获取map长度
map.size
//添加数组
map.set("c",3)
//获取map值
map.get("a")
//删除数据 删除成功返回true
map.delete("a")
//检测map中是否含有某个值 返回布尔值
map.has("a")
//清除所有数据
map.clear();
//获取所有key值
map.keys();
//获取所有value值
map.values();
//获取所有键值对
map.entries();
//遍历map对象 index在前item在后(数组中item在前)
map.forEach((index, item) => {
console.log(index, item);
})
十三、Proxy介绍
概念:Proixy是ES6中新增的一个特性。
作用:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须想通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,很类似于设计模式中的代理模式。
基本用法:
let p = new Proxy(target, handler);
参数:
target: 用Proxy包装被代理的对象(可以是任意类型的对象,包括原生数组、函数、甚至是另一个代理)
handler:是一个对象,其声明了代理target的一些操作,其属性是当执行一个操作时定义代理的行为的函数
特点:
1、可以劫持整个对象,并返回一个新对象
2、有13中劫持操作
3、handler代理的一些常用方法:
get 读取
set 修改
has 判断对象是否有该属性
construct 构造函数
apply 当目标对象是一个函数的时候
deletePrototy 用于拦截delete操作
十四、get/set方法
let target = {
name: "chj",
age: 20,
sex: "男"
}
let p = new Proxy(target, {
get(obj, attr) {
console.log("属性被访问了");
},
set(obj, attr, value) {
console.log("属性被设置了");
}
})
p.name;
p.name = "小王";
get函数:当访问target对象身上的一些属性的时候就会触发get函数,get函数接收两个参数
参数一:代理的对象,也就是target。
参数二:访问的属性。
set函数:当设置target对象身上的一些属性的时候就会触发set函数,set参数接收三个参数
参数一:代理的对象
参数二:设置对象的属性
参数三:设置对象的属性的值
使用场景:
1、虚拟场景
let target = {
firstName: "chj",
lastName: "ls"
}
let p = new Proxy(target, {
get(obj, attr) {
if(attr == "fullName") {
retrun [obj.firstName, obj.lastName].join(" ");
}
return obj[attr];
},
set(obj, attr, value) {
if(attr == "fullName") {
let fullNameInfo = value.split(" ");
obj.firstName = fullNameInfo[0];
obj.lastName = fullNameInfo[1];
}else {
obj[attr] = value;
}
}
})
console.log(fullName); //chj
p.fullName = "小 甜甜";
console.log(p.firstName); //小
console.log(p.lastName); //甜甜
2、私有属性
//把_开头的变量都认为私有变量
let target = {
name: "张三",
age: 20,
_sex: "女"
}
let p = new Proxy(target, {
get(obj, attr) {
if(attr.startWith("_")) {
console.log("私有属性不被允许访问");
return false;
}
return obj[attr];
},
set(obj, attr, value) {
if(attr.startWith("_")) {
console.log("私有属性不允许设置");
retrun false;
}
obj[attr] = value;
},
has(obj, attr) {
if(atrr.startWith("_")) {
return false;
}
retrun (attr in obj);
}
})
十五、函数拦截
apply:当目标对象是一个函数,且他被调用时,就是被apply方法拦截
参数:apply(target, context, arguments) {}
target:目标对象
context:目标对象的上下文(this)
arguments:目标对象的参数数组
construct:用于拦截new命令,意思就是你在new目标对象的时候,会走construct() {}
参数:construct(target, arguments) {}
target:目标对象
arguments:构造函数的参数对象
function fn(a, b) {
lat handler = {
apply: function(target, context, args) {
console.log(target, context, args)
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
}
};
let p = new Proxy(fn, handler);
console.log(p(1, 2)) //1
console.log(new p(1, 2)) //{value: 2}