1、块级作用域
<script>
// 1、值得记录的块级作用域
// 用let、const声明得变量会有会计作用域,而var不会有可以对比一下下面两个for循环打印得结果
for(var j = 0; j<=3; j++) {
console.log(j);
}
console.log(j)// 这里打印4
for(let i = 0; i<=3; i++) {
console.log(i);
}
console.log(i);// 这里报错
</script>
2、垃圾回收机制
<script>
// 2、垃圾回收机制(GC),内存的分配和回收
// 2.1、内存分配:当我们在生命变量、函数、对象的时候,系统会自动分配内存
// 2.2、内存使用:在使用变量和函数的时候会读写内存
// 2.3、使用完毕,由垃圾回收器自动回收不再使用内存
// 注意:全局变一般不会回收;局部变量会在不用的时候被自动回收
// 2.4、垃圾回收算法:引用计数法和标记清除法
// 2.4.1、引用计数法:内存不在使用,也就是看一个对象是否有指向它的引用,没有引用就回收对象
// 首先记住很重要的一点,引用计数法是IE以前用的方法,现在已经没人用了
// 基本流程是:
// 1、跟踪记录被引用的词书;
// 2、如果被引用了一次,就记录一次,多次引用会累加次数;
// 3、如果减少一个就减一;
// 4、如果引用次数为0,则释放内存。
// 内存泄漏:程序中分配的内存由于有种原因程序未释放或无法释放
// 下面这种情况就是相互引用,会造成内存泄漏,这种情况下,引用计数法是没法回收的
// 还有要注意的,不是说这个函数没被使用时就不会开辟空间创建里面的变量,没有被使用时,变量也是被创建的
// 还有一个问题,就是这种情况下,fn在被使用时内存是不会爆的,只是两个指针互相指向而已
function fn() {
let o1 = {};
let o2 = {};
o1.a = o2;
o2.a = o1;
}
// 2.4.2、标记清楚法:将不在使用的对象改成无法到达的对象
// 基本流程是:
// 1、定时扫描;
// 2、从根部(JS中就是全局对象)出发开始扫描内存中的对象;
// 3、凡是能从根部到达的对象,都还是需要使用的;
// 4、无法由根本出发触及到的对象被标记为不再使用,稍后进行回收。
// 理解:一种从根本出发,然后变量之间形成一种树状或网状结构,被使用的变量都是相互连接的,
// 而如果有几个变量游离在这个网状之外,没有任何连接,那么就会被回收,就比如上面那个函数,当他不被使用时,内部即便再怎么
// 循环都是在网状之外自己玩,和网状主体没有任何连接,也就是说当这个函数不被使用时,从根部的全局无法进入到这个函数,函数就会
// 被回收。而之前引用计数法不会管你有没有使用,它完全就是你在创建变量时就会计算引用次数,索引循环引用无法被消除。
</script>
3、闭包
<script>
// 1、什么时闭包
// 说白了就是内层函数中访问(使用)外层函数的作用域下的变量即:闭包 = 内层函数 + 外层函数的变量
// 2、看下简单的闭包
function outer() {
const a = 1;
function inner() {
consoel.log(a);
}
inner()
}
outer();
// 3、闭包的作用:封闭数据,提供操作,外部也可以访问函数内部的变量;下面是使用情况
// 以前函数内部的变量外部是无法访问的,闭包的方法可以解决该问题
function test() {
let i = 1;
function fn() {
console.log(i);
}
return fn;
}
const fun = test();
fun();
// 4、闭包的应用:实现数据的私有
// 4.1、比如,做一个统计函数,实现对函数调用次数的统计
let i = 0;
let times = 0;
function total() {
i++;
return i;
}
for(let j = 0; j<5; j++) {
times = total();
}
console.log(times);
// 4.2、缺点,这里的i是全局变量,所有人都可以访问到,所以我们来用闭包解决
let time = 0;
function total2() {
let i = 0;
function inner() {
i++;
return i;
}
return inner;
}
const count = total2();
for(let j = 0; j<10; j++) {
time = count();
}
console.log(time);
// 5、这个很好玩,我们来看下整个流程:count是全局变量只有关闭的时候才会被回收,那么count引用了total2
// 里面的inner函数,所以inner函数被引用了,不会被回收。inner函数里用了i变量,所以i虽然是局部变量,但是也不会被回收
// 所以这就形成了一个从根部开始的树状结构,也就是标记清除法。同时!!!!!这也就是闭包的不好的一点,内存泄露!!!!!
</script>
4、变量提升
<script>
// 1、认识变量提升
// 变量提升不是什么好东西,它是js的一个缺陷,它允许在变量声明之前被访问到
// 2、例如:不会报错而是打印undefined;
console.log(num1);
var num1 = 10;
// 所以变量提升就是会把var声明的变量提升到当前作用域,注意是当前作用域的最前面,先声明,这样都可以访问到,只不过是undefined;
// 其次当执行到赋值代码的时候才会赋值;
// 3、正应为如此,ES6引用了会计作用域,let 和 const 就不会有变量提升,会报错,所以已经不建议用var了
console.log(num2);
let num2 = 10;
console.log(num3);
let num3 = 10;
</script>
5、函数提升
<script>
// 1、认识函数提升
// 函数提升不是缺陷,而是一种机制,比如
fn();
function fn() {
console.log('我是好人!')
}
// 函数提升会把所有函数的声明提升到当前作用域的最前面,这里的声明指函数的所有内容,而不是一个undefined
// 2、其他情况
console.log(fun);
fun();
var fun = function fn2() {
console.log('我不知道是不是好人!')
}
// 这里就是说函数提升了,var声明的变量也提升了,但是函数赋值给fun的操作还没执行,所以打印了undefined
// 而在调用函数时会报错,因为还没赋值;
</script>
6、函数参数与展开运算符
<script>
// 1、剩余参数,这里的...是剩余参数运算符
function fn(...args) {
// 这里的args是数组
console.log(args)
let result = 0;
args.forEach(item => {
result += item;
})
return result;
}
console.log(fn(1,2,3))
console.log(fn(1,2,3,4))
// 2、动态参数:arguments
function fn() {
// arguments是伪数组
console.log(arguments);
let result = 0;
for(let i = 0; i<arguments.length; i++) {
result += arguments[i];
}
return result;
}
console.log(fn(1,2));
console.log(fn(1,2,3,4,5));
// 3、剩余参数的其他情况,完全解释了为什么叫剩余参数:
// args装的就是剩余的参数
function fn(a,b,...args) {
console.log(a,b,args)
}
fn(1); // 1 undefined []
fn(1,2,3); // 1 2 [3]
fn(1,2,3,4); // 1 2 [3,4]
// 4、注意哦:以后的箭头函数没有arguments,所以以后要常用...args
// 5、展开运算符
// 注意也是...但是和剩余参数运算符不是一个东西,看下面
// 5.1、展开运算符展开数组
const arr = [1,2,3];
// 我们很少直接展开,因为没有什么用,所以不要去理解展开后返回的是什么类型的东西,而是直接拿来用
console.log(...arr);
// 5.2、拼接数组
let x = [100,20,500];
let y = ['lili','jerry'];
let z = [...x,...y];
console.log(z);
// 5.2、求数组最大值
// Math.max(1,2,3); 不能接收数组,用展开运算符可以解决
const w = [4,3,7,9,5,7,8];
console.log(Math.max(...w));
// 5.3、拼接对象
const obj1 = {
a: 1,
c: 3
}
const obj2 = {
b: 2,
c: 4
}
// 相同的属性,后面的覆盖前面的
console.log({...obj1,...obj2});
console.log({...obj2,...obj1});
// 5.4、合并数组和其他
let o = ['elva','tom',100,200];
let p = 'lili';
let q = [...x,y];
console.log(q);
</script>
7、箭头函数
<script>
// 1、当参数只有一个的时候,括号可以省略,如下
const fn = value => {
console.log(value);
}
fn('wujunjie');
// 2、只有一行代码的时候,可以省略大括号
const fn2 = value => console.log(value);
// 只有返回值得一行代码都不用写return
const fn3 = value => 2*value;
fn2('jjwu');
console.log(fn3(3));
// 3、还可以直接返回一个对象,但是要用小括号抱着
const fn4 = name => ({name: name});
console.log(fn4('jiayou'));
// 4、箭头函数的参数
// 有趣的是箭头函数没有动态参数arguments,但是有剩余参数...args
const fn5 = (...args) => {
args.forEach(item => {
console.log(item);
})
}
fn5(1,2,3,4,5);
// 5、this指向问题
// 5.1、正常情况:谁调用的函数,this就指向谁
console.log(this) //window
function fn6() {
console.log(this) //window kjm
}
fn6()//这里的fn()实际上就是window.fn()
const obj = {
name: 'wujunjie',
sayHello() {
console.log(this) // obj
}
}
obj.sayHello();
// 5.2、箭头函数
// 值得注意的是,箭头函数没有this,那么他会去上一层作用于找this
const obj2 = {
name: 'wujunjie',
sayHello: () => {
console.log(this) // obj
}
}
obj.sayHello();
// 5.3、双层嵌套
const obj3 = {
name: 'wujunjie',
sayHi: function() {
let i = 1;
const fn = () => {
console.log(this) // obj
}
return fn;
}
}
obj3.sayHi()();
// 5.4、DOM监听事件
btn.addEventListener('click', () => {
console.log(this) // window
})
btn.addEventListener('click', function() {
console.log(this) // btn
})
</script>
8、数组解构
<script>
// 1、简单解构实现
const [max,avg,min] = [10,8,6];
console.log(max,avg,min);
// 2、有意思的玩法:交换变量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);
// 3、搭配剩余参数运算符
const [c, d, ...other] = [1,2,3,4,5];
console.log(c,d,other);
// 4、多维数组解构
const [[o, k], [i, j]] = [['o','k'], ['i', 'j']];
console.log(o,k,i,j);
</script>
9、对象解构
<script>
// 1、解构时的重命名
const obj = {
name: 'wujunjie',
age: 18
}
const name = 'huaxia';
// 我们在解构的时候会提示变量名重复了,所以需要重新给一个变量名
// const { name, age } = obj;
const { name: newName, age } = obj;
console.log(newName,name,age);
// 2、数组对象解构
const objArr = [
{
name1: 'wujunjie',
age1: 18
},
{
name1: 'huaixa',
age1: 17
}
]
const [{ name1, age1}, {name1: name2, age1:age2}] = objArr;
console.log(name1,age,name2,age2);
// 3、多级对象解构
const pig = {
name: 'wujunjie',
family: {
girlFriend: 'huaxia'
}
}
const { family: { girlFriend } } = pig;
console.log(girlFriend);
// 4 有意思的用在函数里
function render({ data: myData }) {
console.log(myData);
}
const dataObj = {
data: [
{
name: 'wujunjie',
age: 18
},
{
name: 'huaxia',
age: '17'
}
]
}
render(dataObj);
</script>
10、创建对象方式
<script>
// 1、第一种方式
const o = {
name: 'wujunjie'
}
console.log(o);
// 2、第二种方式,也是创建一个空对象,1创建对象的本质就是2这种方式
const j = new Object();
j.name = 'huaxia';
console.log(j);
// 3、第三种方式
const k = new Object({name: 'xiaoxia'});
console.log(k);
</script>