Skip to content

基础深入总结

数据类型

① 分类

js
原始类型/基础类型/值类型:
    String
    Number
    Boolean
    Null
    Undefined

对象类型/引用类型
    Object  基本对象
    Array
    Function
    .....

② 类型判断

js
typeof    
    两种形式:typeof函数,typeof运算符
    能判断: string、boolean、number、undefinedfunction
    不能判断null和除了Function的对象类型

instanceof
    判断对象类型

==== 全等
    两个操作数类型和值都要相等不会自动类型转换区分==
    判断 null和undefined

③ 相关问题

js
1. null和undefined的区别
    undefined 代表变量没有赋值
    null 代表变量赋值,只是值为null

2. 什么情况下数据类型是undefined
    ① 变量未赋值
    ② 函数的形参没有对应的实参
    ③ 函数没有返回值
    ④ 使用对象中不存在的属性

3. 什么情况下给变量赋值为null
    ① 定义变量,暂时不需要设置其他值,先设置为null
    ② 定义对象的某个属性,暂时不需要值
    ③ 如果一个不使用的对象,设置为null,对象会变为垃圾对象,被回收。

数据、变量和内存

① 什么是数据?

js
* 存储于内存中代表特定信息的'东东', 本质就是0101二进制
* 具有可读和可传递的基本特性
* 万物(一切)皆数据, 函数也是数据
* 程序中所有操作的目标: 数据

② 什么是变量?

js
变量就是可变的量
变量就是一小块内存的标识,里面存的数据

③ 什么是内存?

js
* 内存条通电后产生的存储空间(临时的)
* 产生和死亡: 内存条(集成电路板)==>通电==>产生一定容量的存储空间==>存储各种数据==>断电==>内存全部消失
* 内存的空间是临时的, 而硬盘的空间是持久的
* 一块内存包含2个数据
  * 内部存储的数据(一般数据/地址数据)
  * 内存地址值数据
* 内存分类
  *: 全局变量, 局部变量 (空间较小)
  *: 对象 (空间较大)

④ 数据、变量、内存之间的关系

js
内存是一块存储空间,里面存数据
变量是对象这块内存标识,通过变量名可以找到这块内存,从而获取数据

⑤ 相关问题

js
var a = xxx, a内存中到底保存的是什么?
    xxx是基本类型/原始类型
    xxx是对象类型
    xxx是变量
var a = 100;
//在栈里面把100这个值存下来

var a = new Date();
// 堆中开辟控件,存储对象;  栈存地址

var b = a;
// a变量的栈存的是什么, b就复制一份

垃圾回收机制(GC)

垃圾回收相关概念

① 什么是垃圾

js
1. 用不到的数据,就是垃圾
2. JavaScript 中没有被引用的对象,就是垃圾对象

② 什么是垃圾回收

js
1. 销毁垃圾对象,释放内存,就是垃圾回收
2. 需要手动垃圾回收的编程语言: CC++
3. 自动垃圾回收的编程语言: Java、Python、JS

③ 垃圾没有及时回收的后果

js
垃圾没有及时回收造成内存泄漏,越来越多的内存泄漏会导致内存溢出

内存溢出: 需要使用内存的时候,内存空间不够。

内存泄漏: 垃圾没有回收称为内存泄漏。

④ JavaScript 垃圾回收的常见算法

js
- 引用计数
- 标记清除

引用计数

① 原理

js
- ① 对象有个引用标记
- ② 如果对对象进行了引用 +1
- ③ 取消了对象对象的引用 -1
- ④ 当引用标记=0的时候,变为垃圾对象,并删除

② 优缺点:

js
- 优点: 及时清除垃圾对象
- 缺点: 互相引用的对象导致无法被回收(内存泄漏)
js
// 创建对象
// 小乐被 window.user01 引用,引用计数是 1
var user01 = {name:'小乐'};
// 老乐被 window.user02 引用,引用计数是 1
var user02 = {name:'老乐'};

// 老乐又被 user01.children 引用,引用计数是 2
user01.children =  user02;
// 小乐被 又被 user02.children 引用,引用计数是 2
user02.children = user01;


// window.user01不再引用 小乐,引用计数 1
user01 = 200;
// window.user02不再引用 老乐, 引用计数 1
user02 = 250;

标记清除

① 原理

js
浏览器不停地进行标记清除,每一轮分为标记和清除两个阶段
- 标记阶段:从根对象出发,每一个可以从根对象访问到的对象都会被添加一个标记,于是这个对象就被标识为可到达对象。
- 清除阶段:垃圾回收器,会对内存从头到尾进行线性遍历,如果发现有对象没有被标记为可到达对象,那么就将此对象占用的内存回收。
该轮结束,将原来标记为清除,以便进行下一轮标记清除。

② 优缺点

js
- 优点:  不会内存泄漏
- 缺点:  需要深度递归,耗费资源较多
js
// 通过 window.age 可以访问到对象
var age = {name:'xiaole'};
/// 通过 window.address 也可以访问到对象
var address = age;
/// 还可以通过 window.address 可以访问到对象
age = 200;
// 当整个页面关闭,window销毁,无法到达对象,被销毁


// 创建对象 
// 通过 window.user01到达 小乐
var user01 = {name:'小乐'};
// 通过 window.user02 到达 老乐
var user02 = {name:'老乐'};

// 通过 window.user01.children 也可以到达老乐
user01.children =  user02;
// 通过 window.user02.children 也可以到达小乐乐
user02.children = user01;


// window.user01不可以到达 小乐
user01 = 200;
// window.user02 不可以到达 老乐
user02 = 250;

执行上下文和执行栈

执行上下文

执行上下文是一个对象

① 全局执行上下文

js
1. 打开页面,js代码执行之前,创建 window 对象,确定 window 就是全局执行上下文对象
2. 对全局执行上下文对象进行预处理
   ① 找到使用 var 的变量声明语句,给全局执行上下文对象添加属性,但不赋值
   ② 找到使用 function 的函数声明语句给全局执行上下文对象添加属性值是函数
 this 进行赋值将全局执行上下文对象(window)赋值给 this
3. 正式执行全局代码
4. 页面关闭全局执行上下文对象销毁

② 函数内的执行上下文

js
函数内的执行上下文对象是看不到的
1. 调用函数的时候,函数内代码执行之前,创建该函数的执行上下文对象;
2. 对函数内执行上下文对象进行预处理
   ① 将形参作为函数内执行上下文对象的属性,并赋值
   ② 给函数内执行上下文对象添加属性arguments,并赋值
   ③ 找到函数内使用 var 的变量声明语句,给函数内执行上下文对象添加属性,不赋值
   ④ 找到函数内使用 function 的函数声明语句给函数内执行上下文对象添加属性值是函数
 this 进行赋值将调用该函数的对象赋值给 this
3. 正式执行函数内的语句
4. 函数调用结束函数内执行上下文对象被销毁

注意: 函数每调用一次,就创建一个执行上下文对象。

执行栈

  • 执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
  • 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
  • 引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
js
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下全局执行上下文

栈结构: 是一种数据存储结构,特点先进后出,后进先出。

**执行栈:**执行上下文对象创建之后,要放入执行栈,放入执行栈才能执行。

js
<!--
1. 依次输出什么?
2. 整个过程中产生了几个执行上下文?
-->
<script type="text/javascript">
  console.log('global begin: '+ i); //globa begin undeifined
  var i = 1;
  foo(1);
  function foo(i) {
    if (i == 4) {
      return;
    }
    console.log('foo() begin:' + i);
    foo(i + 1);
    console.log('foo() end:' + i);
  }
  console.log('global end: ' + i); 
</script>

作用域和执行上下文的关系

作用域

js
作用域就是变量的作用范围

分类:
    全局作用域
    函数作用域(局部作用域)

特点:
    在函数声明的时候就决定了,跟函数在哪里调用没有关系

区别:

js
1. 变量的作用域在函数声明的时候就确定了,是静态的
2. 执行上下文对象函数调用的时候才创建,每调用一次就创建一次,调用结束会销毁,是动态的

联系:

js
执行上下文对象从属于所在的作用域:
全局执行上下文对象作用域是全局; 函数内执行上下文对象作用域是所在函数。
js
1. 区别1
  * 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  * 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  * 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
2. 区别2
  * 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  * 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
3. 联系
  * 上下文环境(对象)是从属于所在的作用域
  * 全局上下文环境==>全局作用域
  * 函数上下文环境==>对应的函数使用域

JS引入

js
<!-- 引入脚本 -->
    <script src="./js/function.js"></script>
    <script src="./js/data.js"></script>
    <script src="./js/index.js"></script>

定义的全局变量和函数需要先引入
引入后在index.js中就可以直接使用,相当于写在了index.js代码的最上边。

闭包

什么是闭包?

js
1)简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
2MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

如何产生闭包

js
1. 函数A中嵌套函数B
2. 函数B中访问函数A中定义的数据(上层作用域的变量)
3. 实现从函数A的外部使用函数B
   方式一: 将函数B作为返回值
   方式二: 将函数B赋值给全局对象的属性
   方式三: 将函数B作为一个事件的回调函数
   
-------------------------------------------   
闭包常见的形式:
1.把被嵌套的函数返回
2.把被嵌套的函数作为回调函数(事件的回调函数、定时器)
js
function A() {
    // 定义数据
    var num01 = 100, num02 = 200;
    // 函数B
    function B(){
        // 函数B中可以访问到函数A中的数据
        console.log(num01 + num02);
    }
    // 方式一 函数B作为返回值
    return B;

    // 方式二 函数B赋值给全局对象的属性
    // window.func = B;

    // 方式三 函数B作为事件的回调函数
    // document.onclick = B;
}

//调用
var fn = A();
fn();

闭包和作用域

js
1. 可以访问上层作用域的数据
2. 作用域只与函数声明的位置有关,与调用位置无关

闭包的作用

js
1. 函数内部的变量长时间存储在内存中(被内部的函数引用了),延长了局部变量的声明周期
2. 在函数的外部操作(读写)函数内部的数据(变量\函数)

闭包和垃圾回收

js
闭包延长了数据的生命周期

闭包的缺点

js
闭包会让数据常驻内存,增加了内存溢出的风险

闭包的应用

闭包:定义js模块

js
JS模块:具有特定功能的文件
把模块内所有的数据和功能封装在一个函数内(私有的)
模块向外暴露一个对象,对象中想要对外的方法和属性

暴露方式: ①直接return  ②作为window的属性
js
// 使用for循环遍历选项卡导航 每一个添加单击事件
for (var i = 0; i < tabNavItems.length; i ++) {
    (function(index){
        tabNavItems[index].onclick = function() {
            // 排他
            // 把所有的tabNav都取消选中  把所有的tabContent隐藏
            for (var j = 0; j < tabNavItems.length; j ++) {
                tabNavItems[j].classList.remove('active');
                tabContentItems[j].classList.remove('active');
            }

            // 当前点击的选项卡导航添加 active 类名 表示当前选中
            this.classList.add('active');
            // 与当前tabNav对应的tabContent要显示出来
            tabContentItems[index].classList.add('active');
        };
    })(i);
}

// 遍历选项卡导航 每一个添加单击事件
tabNavItems.forEach(function(tabNavItem, index) {
    tabNavItem.onclick = function() {
        // 排他
        // 把所有的tabNav都取消选中  把所有的tabContent隐藏
        for (var i = 0; i < tabNavItems.length; i ++) {
            tabNavItems[i].classList.remove('active');
            tabContentItems[i].classList.remove('active');
        }

        // 当前点击添加 active 类名 表示当前选中
        tabNavItem.classList.add('active');
        // 与当前tabNav对应的tabContent要显示出来
        tabContentItems[index].classList.add('active');
    }
});
js
/*
	说说它们的输出情况
*/

function fun(n, o) {
    console.log(o);
    return {
        fun: function (m) {
            return fun(m, n)
        }
    }
}
        
var a = fun(0);    // undefeind
a.fun(1);          // 0
a.fun(2);          // 0
a.fun(3);          // 0
console.log('');

var b = fun(0).fun(1).fun(2).fun(3);
/*
	undefeind
	0
	1
	2
*/
console.log('');



var c = fun(0).fun(1);
/*
	undefeind
	0
*/
c.fun(2);       // 1
c.fun(3);       // 1

闭包2

节选自CSDN

shell
用阮大师的话说:闭包就是能够读取其他函数内部变量的函数。

闭包的作用

shell
1. 可以读取函数内部的变量
2. 让这些变量的值始终保持在内存中

影响

shell
1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2.闭包会在父函数外部,改变父函数内部变量的值。
所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,不要随便改变父函数内部变量的值。

闭包案例

js
function f1(){
 var n = '哈哈大笑';
 function f2(){
  console.log(n);
 }
 return f2;
}

var result=f1();
result(); // 哈哈大笑
shell
上段代码f2函数,就是闭包。
js
function f1() {
    var n = 999;
    nAdd = function () { n += 1 }
    function f2() {
        console.log(n);
    }
    return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
shell
result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数,而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。