1. 数据、变量和内存

1.1 数据类型判断

  1. typeof:
    • 可以区别: 数值, 字符串, 布尔值, undefined, function,symbol
    • 不能区别: null与对象, 数组与一般对象
    • 返回的是数据类型的字符串表达形式
    1
    2
    3
    4
    5
    a = 3;
    console.log(typeof a = = = 'number');//注意引号

    a = null;
    console.log(typeof a) // 'object'
  2. instanceof
    • 专门用来判断对象数据的类型: Object, Array与Function
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var b1 = {
    b2: [2, 'abc', console.log],
    b3: function () {
    console.log('b3()')
    }
    };
    console.log(b1 instanceof Object, typeof b1); // true 'object'
    console.log(b1.b2 instanceof Array, typeof b1.b2); // true 'object' //typeof不能区分数组与一般对象
    console.log(b1.b3 instanceof Function, typeof b1.b3); // true 'function'
  3. ===
    • 可以判断: undefined和null
    1
    2
    a = null;
    console.log(a===null); // true

1.1.1常见问题

  1. undefined与null的区别 undefined:声明,未赋值 null:声明,赋值为null。用于:①初始赋值,表明Object类型;②结束时,断开堆内存链接,被垃圾回收。
  2. 严格区分变量类型与数据类型
    变量类型:
    基本类型:保存数据
    引用类型:保存地址值

数据类型:

基本类型

对象类型

1.2数据、变量和内存

  1. var a = xxx;a内存中保存的到底是什么? ①xxx是基本数据,保存的就是这个数据 ②xxx是对象,保存的是地址值 ③xxx是变量,依变量类型而定
  2. 关于引用变量的赋值问题 ①多个引用变量指向一个对象 ,一个变量的赋值发生变化,其他也随之变化 ②两个引用变量指向一个对象,修改一个变量指向另一个对象,另一变量依然指向前一个对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    a = { name: "Bob", age: 18 };
    function fun(obj) {
    obj = {age:15};
    }

    fun(a);
    console.log(a.age);
    //输出结果:18
    //解析:传参的过程其实是赋值的过程。将a的地址值赋给obj,obj = {age:15}改变了obj的地址,并未改变a,故a.age仍为18

    a = { name: "Bob", age: 18 };
    function fun(obj) {
    obj.age = 15;
    }

    fun(a);
    console.log(a.age);
    //输出结果:15
    //解析:obj.age = 15改变了堆内存中age的值,a也随之改变
  3. JS如何管理内存?
    内存生命周期:
    分配内存空间,得到它的使用权;
    存储数据,可以反复操作;
    释放内存

释放内存:

为执行函数分配的栈内存: 函数执行完自动释放

存储对象的堆内存: 当内存没有引用指向时, 对象成为垃圾对象, 垃圾回收器后面就会回收释放此内存

  1. 浅拷贝与深拷贝 浅拷贝只拷贝一层,深层次的对象只拷贝引用(地址值);

    深拷贝每一级别的数据都会拷贝

2. 函数高级

2.1对象

  1. 对象是多个数据的集合体(封装体),用于保存多个数据的容器,便于对多个数据进行统一管理。
  2. 对象的组成 属性:由属性名和属性值组成,属性名都是字符串类型, 属性值是任意类型 方法:是特别的属性(属性值是函数)
  3. 访问对象 .属性名 有时不能用 [‘属性名’]
  4. 只能用[‘属性名’]的情况 ①属性名包含非法字符 ②属性名不确定

2.2 函数

  1. 函数定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //函数声明
    function fun1(){

    }

    //表达式,声明提前
    var fun2 = function(){

    };
  2. 函数的调用 ①test(); ②obj.test(); ③new Test(); ④test.call(obj); test.apply(obj); 让test临时成为obj的方法,从而改变this的指向
  3. 立即执行函数(IIFE) 作用:隐藏实现 ;不会污染命名空间;用于编码JS模块

2.3 this

任何函数本质上都是通过某个对象调用的,如未指定,这个对象默认是window

所有函数内部都有一个变量this,this指向调用函数的对象

确定this的指向:

  • test(): window
  • p.test(): p
  • new test(): 新创建的对象
  • p.call(obj,…): obj

2.4 原型prototype

2.4.1原型与原型对象

每个函数都有一个prototype属性,他指向一个object对象(原型对象)。

原型对象中有一个属性constructor,它指向函数本身。由此函数与原型对象构成了双向引用的关系。

我们可以通过Fun.prototype.test = xxx;来为构造函数的实例添加属性或方法。

1
2
3
//输出Date的原型对象:
console.log(Date.prototype);
//结果如下:

1
2
//通过.prototype.constructor获取当前函数
console.log(Date.prototype.constructor === Date);//true

2.4.2 显式原型与隐式原型

每个函数Function都有一个prototype,即显式原型;

每个实例对象都有一个_ proto _,可称为隐式原型;

他们保存一样的地址值,共同指向原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建构造函数
function Fn() { // 自动添加内部语句: this.prototype = {}

}

//创建实例对象
var fn = new Fn(); // 自动添加内部语句: this.__proto__ = Fn.prototype

console.log(Fn.prototype===fn.__proto__) // true

//给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
//通过实例调用原型的方法
fn.test()

2.4.3 原型链

访问一个对象的属性时,

  • 先在自身属性中查找,找到返回
  • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
  • 如果最终没找到, 返回undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
*/
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
/*
2. 所有函数都是Function的实例(包含Function)
*/
console.log(Function.__proto__===Function.prototype)
/*
3. Object的原型对象是原型链尽头
*/
console.log(Object.prototype.__proto__) // null

设置对象的属性值时,不会查找原型链,如果当前对象没有此属性,则为其添加此属性并设置其值。

方法一般定义在原型中,属性一般通过构造函数定义在对象本身上。

1
2
3
4
5
6
7
8
9
10
11
function Fun() {

}
Fun.prototype.a = "xxx";
var fn1 = new Fun();
var fn2 = new Fun();
fn2.a = "yyy"
console.log(fn1);//{}
console.log(fn2);//{a:"yyy"}
console.log("fn1.a = "+fn1.a);//fn1.a = xxx
console.log("fn2.a = "+fn2.a);//fn2.a = yyy

2.4.4 探索instanceof

a instanceof B

如果B的原型对象在a的隐式原型链上,则返回true

2.5 执行上下文与执行上下文栈

声明提前

全局执行上下文:

  1. 在执行全局代码前,将window确定为全局执行上下文
  2. var定义的全局变量添加到window,值为undefined
  3. function声明的函数,添加到window的方法
  4. 为this赋值——window
  5. 执行全局代码

注:先变量提升,再函数提升

函数执行上下文:

  1. 在调用函数、执行函数之前,创建函数执行上下文对象(虚拟的对象,存在于栈中)
  2. 对局部数据进行预处理: 形参赋值为实参,添加到执行上下文的属性 argument(实参列表)添加到执行上下文的属性 var定义的局部变量添加为执行上下文的属性,值为undefined function声明的函数,添加到执行上下文的方法 为this赋值——谁调用的函数,谁就是this
  3. 开始执行函数代码

2.6 作用域与作用域链

作用域与执行上下文的区别与联系

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

练习:

1
2
3
4
5
6
7
8
9
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn); //10 注意作用域与执行上下文的区别,两个函数作用域是互相独立的,因此输出全局变量
1
2
3
4
5
6
7
8
9
10
11
var fn = function () {
console.log(fn)
}
fn() //输出fn本身

var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2() //报错。这种写法相当于obj.fn2 = function(){};需要用this.fn2获取到fn2

2.7 闭包

2.7.1闭包简介

高阶函数:参数是函数或返回值是函数

  1. 闭包的产生 当子函数引用了父函数的变量(变量也可以是函数),就产生了闭包。 另:一个作用域有权访问另一个函数的局部变量,这个作用域就称为闭包。
  2. 常见的闭包: ①将函数作为返回值返回
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function fn1() {
    var a = 2
    function fn2() {
    a++
    console.log(a)
    }
    return fn2 //也可以写成:returnfunction fn2() { a++; console.log(a); }
    }
    var f = fn1()
    f() // 3
    f() // 4
    ②将函数作为实参传递给另一个函数调用
    1
    2
    3
    4
    5
    6
    function showDelay(msg,time){
    setTimeout(function(){
    alert(msg)
    },time)
    }
    showDelay("lizhenduo",2000)
  3. 闭包的作用 延长局部变量生命周期:函数内部变量在函数执行完后,仍然在内存中; 让函数外部可以操作到函数内部数据
  4. 闭包的生命周期 子函数定义执行完产生,成为垃圾对象后死亡

2.7.2 闭包应用:自定义JS模块

编写js文件,将所有数据和功能封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象,以供模块的使用者实现对应功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function () {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}

//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()

2.7.3内存溢出与泄露

函数执行完之后,函数内的局部变量没有释放,仍然占用内存,容易造成内存泄露。

解决办法:能不用闭包就不用;及时释放

内存溢出:

一种程序运行时出现的错误。当程序运行需要的内存超过了剩余内存时,就抛出内存溢出的错误。

内存泄漏:

占用的内存未及时释放,内存泄漏积累多了就会导致内存溢出。

常见的内存泄漏:①意外的全局变量;②没有及时清理计时器或回调函数;③闭包

2.7.4 练习

见课件

2.8 递归

如果一个函数在内部可以调用其本身,就称为递归。为防止内存溢出,必须设置终止条件(return)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function fun(){
num = 1;
console.log("打印六句话");

if(num === 6){
return;
}
num++;
fun();
}

//利用递归求阶乘
function fn(num){
if(num === 1){
return 1
}
return num*f(num-1);
}