JS高级02_原型、执行上下文、作用域、闭包

P20 P21没看 +22前11

1. 原型与原型链

  1. 函数的prototype属性(图)
  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)

  • 原型对象中有一个属性constructor, 它指向函数对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person( ) {

    }

    //1.每一个函数都有一个属性prototype
    console.log(Person.prototype);//空对象--->原型对象(显式原型对象)
    //Person.prototype.constructor = 函数本身 声明当前的构造器是谁

    var person1 = new Person(); //生成实例对象


    //2.每一个实例对象身上都有一个属性_proto_,该属性指向当前实例对象的原型对象(隐式原型对象) 带下划线的叫隐式原型对象△
    //3.构造函数的显式原型对象 === 当前构造函数实例对象 的隐式原型对象
    console.log(Person.prototype === person1._proto_); // true
  1. 给原型对象添加属性(一般都是方法)
  • 作用:函数的所有实例对象 都能有原型中的方法(或属性,属性更少因为它一般不公共),即它是公共的
  • 好处:节省内存

1). 所有函数都有一个特别的属性:

`prototype` : 显式原型属性

2). 所有实例对象都有一个特别的属性:

`__proto__` : 隐式原型属性
另外:带下划线的是内置方法 以前不可以改动,ES6中可以改了。

3). 显式原型与隐式原型的关系

函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
原型对象即为当前实例对象的父对象

4). 原型链

原型链:△

当使用对象.属性(方法)的时候,

1.在自身 属性(方法)找

2.在自身的原型(是一个实例对象) 的属性(方法) 找

3.Object的原型的 属性(方法) 再原型对象就 = null了。真没有。
Object是个函数对象(就是个函数,但它也是个对象)不是实例对象,Object的原型是实例对象,它有一个toString()方法。

1
2
3
a.b 

a(变量)会沿着作用域链找,找b(对象的属性)会沿着原型链找

再上课举例:

image-20210815161200572

所有的实例对象都有__proto__属性, 它指向的就是原型对象
这样通过__proto__属性就形成了一个链的结构---->原型链
当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

另外,新建一个变量,也就是new一个实例。

1
2
var obj = {};  与  var obj = new Object() 一样
var arr = [12,3,4] 与 arr = new Array() 一样

函数对象 与 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Fun( ) {

}

//Fun() || Fun .xxx --->函数对象
Fun.xxx = '123';

console.log ( Fun) ;//函数性质:打印函数本身 没有123
console.log( Fun. xxx);//对象性质:打印123
------------------------------------------
var obj = {};
console.log(obj);
// obj = new Object()
obj.xxx = 123
console.log(obj.xxx); //纯 对象

所有函数都由Function() 和 Object()派生

img

Function可以new自己,Object

Object可以new Function

金字塔:Object类—-Function Array类

另外:a instanceof b何时返回true(属于某一类、new b 能得到a):a的隐式原型链上 可以找到 b的显式原型对象

1
2
3
4
5
6
7
8
9
10
11
function Foo() {  }
var f1 = new Foo();
console.log(f1 instanceof Foo);//true
console.log(f1 instanceof Object);//true
console.log(f1 instanceof Function);//false

console.log(Object instanceof Function)
console.log(Object instanceof Object)
console.log(Function instanceof Object)
console.log(Function instanceof Function)
console.log(Object instanceof Foo);//false上面四个都是true

5). 详图

function Foo () {}
var f1 = new Foo()
var f2 = new Foo()
var o1 = {}
var o2 = {}

2. 执行上下文与执行上下文栈

1). 变量提前声明与函数提前声明

提前声明讲得贼好:

https://www.cnblogs.com/lvonve/p/9871226.html

变量提前声明(预解析): 在变量定义语句之前, 就可以访问到这个变量(undefined)
函数提前声明: 在函数定义语句之前, 就执行该函数(函数本身)
先有函数提升, 后有变量提升

原理:
js引擎在js代码正式执行之前会做一些预处理工作

1、找var和function关键字
(不带var关键字的变量可以写,但是分两种情况:1.在全局写不会被预解析 2.在函数里写会自动升成全局的 进行更改或定义)
2.找到var以后将var后边的变量提前声明但是不赋值var a;
3.找到function以后定义该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
提前声明举例
console.log( username);//undefined
aTest();//aTest
var username = 'kobe'
function aTest(){
console.log( "aTest' );
}

-------

var fun2 = function (){

}
//这个只是 变 量 的提前声明 fun2 undefined

冷知识:
1全局定义变量不要定义name 因为window对象上本身就有name属性(一般的不定义的函数、属性会报错,而name不报错但是空的)
2函数有个默认的属性name === 函数名

2). 理解执行上下文 是什么?是全局、局部的 预习变量和函数

执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性(函数)。我认为就是提前声明!+ 找作用域链 + 找this?
执行上下文栈: 用来管理产生的多个执行上下文。不嵌套
在source中的 call stack中可查看

变量被写入对象之后,再遇到还会再读,写入值一遍,函数不会再读一遍了。

4). 执行上下文生命周期

全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
函数 : 准备调用函数时产生, 函数执行完时死亡。
?我理解:没等到调用那行呢,就才遍历到了函数名第一行,就产生了

image-20210816213116676

5). 包含哪些属性:

全局 : 
    用var定义的全局变量  ==>undefined
    使用function声明的函数   ===>function
    this   ===>window
函数 :
    用var定义的局部变量  ==>undefined
    使用function声明的函数   ===>function
    this   ===> 调用函数的对象, 如果没有指定就是window 
    形参变量   ===>对应实参值
    arguments ===>实参列表的伪数组

6). 执行上下文创建和初始化的过程

执行上下文:

原理:

js引擎在js代码正式执行之前会先创建执行环境,在执行环境中作预处理工作工作内容:

过程:

1.创建空对象—->执行上下文对象
2.该空对象用于收集变量,函数,函数的参数(找var和function)
3.创建作用域链
4.确认this的指向: 全局:window 局部: this —>?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.创建全局的执行上下文 入栈(anonymous)
var a = 10

var bar = function (x) {//创建bar的执行上下文 入栈bar
var b = 5
foo(x + b)
}

var foo = function(y) {//创建foo的执行上下文 入栈foo
var c = 5
console.log(a +c +y)
}

bar( 10)

冷知识:

  • 所有的函数都有返回值,如果不指定返回值,就是undefined

  • 嵌套函数的内部函数 没有被引用到(在fun1里面 fun2() ),fun2将不会被上下文。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    console. log( "xxx");
    var a = 1;
    function fun() {
    console.log( '-----' );
    function fun2(){
    //空的 本该有fun2的提前声明,结果没有。
    }
    }

上下文练习题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log('global begin: '+ i)
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)//函数内部没有修改i 修改了也犯不着全局的i = 1?

//undefined 1 2 3 3 2 1 1

提前声明易考知识点:

1.当有同名function和变量,不管顺序,预解析结果一定是function。
原理:只要看见了function就不再另声明了。(注意仅限提前声明!!!!)

1
2
3
console.log(typeof a) //function
function a() {}
var a;

2.

1
2
3
4
5
6
if (!(b in window)) {//in:判断b 是否是 window的一个属性
var b = 1;
}
console.log(b)//undefined

//这不是函数,if不是个作用域。所以var b被全局提前声明,window.b if里条件不满足不执行。

3.

1
2
3
4
5
6
7
8
//预解析 c = function(){}
var c = 1 // c = 1
function c(c) {//重点!!!函 数 预解析了就不会再读一遍赋值了 所以c:function ---> 1
console.log(c)
var c = 3
}
c(2)
//报错 因为c是一个变量不是一个函数

3. 作用域与作用域链

1). 定义:

作用域: 代码执行区域。 在编码时就确定了, 不会再变化
作用域链: 多个嵌套的作用域 查找变量时 先找当前的,再找往外的,直到全局作用域。如果还没有,会报错,xxx is not defined

2). 分类:

全局
函数(局部)
js没有块作用域(在ES6之前)

作用域销毁

局部销毁:函数执行完毕

全局销毁:关闭浏览器

3). 作用域作用

作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
作用域链: 查找变量

4). 作用域 执行上下文 区别

作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
联系:

  • 执行上下文环境 是在对应的作用域中的
  • 在作用域中查找变量去 当前作用域下的 执行上下文对象中找
1
2
3
4
5
6
7
8
9
10
11
function fun() {

console.log(num);// {num: undefined}
console.log(b);//报错!undefined
var num = 123;
b = 234;//注意这里!! 全局提前声明的时候,看不到他,局部提前声明的时候,不带var也看不到。只有执行完这一行,全局就拥有一个b了。所以上面是报错,下面是234
console.log(b);//234
}

fun();

练习题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1
//我做错了!
var x = 10;
function fn() {
console.log(x);//这自己没运行啊
}
function show(f) {
var x = 20;
f();
}
show(fn);//函数作为参数 - 调用了fn() - fn里面没有x - 去全局找x=10

//10
//这道题!!千万注意!!就是作用域是固定的,两个作用域平行。还是两个函数各自分离的,只是调用了它一下!并不是在show()里调用了fn(),不 像 想象的:
function show(){
var x = 20;
function fn(){//这里fn的作用域就是show的子作用域了
console.log(x);
}
fn();//在这里确实是20
}

---------------
2
var fn = function () {
console.log(fn)
}
fn()//log fn本级没有,往外找了,全局找到了 ƒ () {console.log(fn)}。。。
//注意这里function简写成f了吗?

var obj = {
fn2: function () {
console.log(fn2)//
}
}
obj.fn2()//会报错 is not defined。因为本级没有fn2。向上一级(注意obj不算一级!!!!,即全局)也没有fn2,因为只有obj!!!
// 所以 console.log()里面写obj.fn2 或 this.fn2 才能实现fn2所表示函数的打印。

4. 闭包

定义:是一个闭合的容器,可以理解为是一个对象,以键值对形式存在

特点:闭包在使用的时候通常会将内部的函数return出去

  1. 产生闭包的条件?
  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  • 外部函数调用

2). 作用:

延长外部函数 局部变量的 生命周期
从外部访问函数内部的局部变量

3). 写一个闭包程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//经典闭包作用:
<script type="text/javascript">


var btns = document.getElementsByTagName('button')
//它是个伪数组:因为它具备数组的一般特征,可以通过下标取值,还有Length属性,但是!!没有数组的一般方法: foreach等等等

for(var i=0,length=btns.length;i<length;i++) {
var btn = btns[i]
console.log(i)//0 1 2
btn.onclick = function () {
// console.log(i)
//回调函数!!在for循环的时候没执行,跳过了。
// 等它执行的时候,每次都是3。而且本以为是2,但是循环完之后又i++了,所以是3.
alert('第'+(i)+'个') //每次都是3
}
}
-----------------------------

for(var i=0,length=btns.length;i<length;i++) {
var btn = btns[i]
btn.index = i //在这里集体都排好队起好属性名,等会对象.属性名就不会混了。通过这里 + this回调对象 实现按钮不同的信息
btn.onclick = function () {
alert('第'+(this.index+1)+'个')
}
}
-----------------------------

//闭包解决
for(var i=0,length=btns.length;i<length;i++) {
(function (i) {
var btn = btns[i]
btn.onclick = function () {
alert('第'+(i+1)+'个')
}
})(i)
}


----------------------------
//一个简单的闭包,嵌套,引用外面的值了
//闭包在此处的作用:从外部访问\操作函数内部的局部变量。???
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();

外部调用,操作内部变量的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
1.在html中
<script type="text/javascript" src="05_coolModule.js"></script>
<!--外部引用跟我在这直接写一个coolModule是一样的-->
<script type="text/javascript">
var module = coolModule()
module.doSomething()//如果log 就是ATGUIGU,即用了内部变量。
module.doOtherthing()
</script>


2.在coolModule.js中

function coolModule() {
//私有的数据,外部引用了也看不到,但是可以选择向外暴露就能看到了。
var msg = 'atguigu'
var names = ['I', 'Love', 'you']

//私有的操作数据的函数
function doSomething() {//产生了闭包
console.log(msg.toUpperCase())
}
function doOtherthing() {
console.log(names.join(' '))
}

//向外暴露包含多个方法的对象
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
-----------------------------
用法2
1.在html中
<script type="text/javascript" src="05_coolModule2.js"></script>
<script type="text/javascript">
coolModule2.a()//coolModule2是个对象了
coolModule2.b()

2.在js中coolModule2.js
(function (window) {
//私有的数据
var msg = 'atguigu'
var names = ['I', 'Love', 'you']
//操作数据的函数
function a() {
console.log(msg.toUpperCase())
}
function b() {
console.log(names.join(' '))
}

window.coolModule2 = {//这是啥玩意操作?对象.属性 = 对象
a: a,//前面的a可以任意起名,后面是函数名
b: b
}
})(window)

4). 闭包应用:

循环遍历加监听(按钮赋值例子)
将内部的函数返出来
将函数作为实参传递给另一个函数调用
JS框架(jQuery)大量使用了闭包

生命周期

1
2
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时

5). 缺点及解决:

变量占用内存的时间可能会过长
可能导致内存泄露
解决: 能不用就不用闭包/及时释放 : var f = null; //让内部函数变量 成为垃圾对 象

习题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//代码片段一
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
//此处this是object
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()()); //? My Object
//这个()()是连续执行。相当于
console.log(function () {
return this.name;
}());
//这里的this是(),window

//代码片段二
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function () {
var that = this;//缓存this 保证this的指向不发生改变,即object2
return function () {
return that.name2;
};
}
};
console.log(object2.getNameFunc()()); //My Object
1
2
3
4
5
6
7
8
9
10
习题

function fun(){
return console.log(1);
// return alert(1); 也是同样的效果
}
console.log(fun());
//会输出两次,1,undefined。undefined是因为console.log 返回值是undefined

-----------------------

5. 内存溢出与内存泄露

1). 内存溢出

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

2). 内存泄露

占用的内存没有及时释放
内存泄露积累多了就容易导致内存溢出
常见的内存泄露:
    意外的全局变量
    没有及时清理的计时器或回调函数
    闭包

函数高级补充知识:

1.同步与异步

同步:没有回调。阻塞后续代码执行,一个一个执行。

异步:一定有回调函数。可能不立马执行,但是非阻塞,同时进行。例如:定时器。