深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究。

一、一切都是对象

1. typeof操作符输出6种类型:string boolean number undefined function object
2. 数组、null object都是object类型
3. 对象:若干属性的集合。js中,数组是对象,函数是对象,对象是对象
4. 函数和对象的关系:
    1. 函数生成对象:通过new构造函数生成实例对象
    2. 函数是一种对象:
        1. 所有的函数都有一个prototype(原型)属性,是一个对象,这个对象中默认的存在一个constructor属性指向函数本身。
        2. 每个对象都有一个隐藏的属性:`__proto__`,指向创建这个对象的构造函数的prototype
        3. 函数也是对象,当然也有`__proto__`属性,指向Function.prototype(大写Function的原型)
        4. 总结:
            1. 自定义构造函数(也是对象)的`__proto__`指向Function.prototype;
            2. Object.`__proto__`也指向Function.protorype;
            3. Function.`__proto__`指向Function.prototype(Function这个对象,当然是被Function函数自身创建的,所以指向自身的prototype)
        5. Function.prototype这个对象,是被Object创建的对象,它的隐藏`__proto__`属性,指向Object.prototype,最后,Object.prototype指向Null!

二、 继承

1. 访问一个对象的属性时,先在基本属性中查找,如果没有找到,沿着`__proto__`这个属性(指向构造函数的prototype)往上找
2. 实际应用中如何区分一个属性是自有的还是在原型中的呢?——obj.hasOwnProperty(proName)

3.每个函数都有call、apply方法,length、arguments等属性,肯定是继承的,都是继承自Function.prototype

4. 原型的灵活性:

在Java和C#中,你可以简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼。
        压个啥样,就得是个啥样,不能随便动,动一动就坏了。
        而在javascript中,就没有模子了,月饼被换成了面团,你可以捏成自己想要的样子。

三、执行上下文

1. **一段代码**在正真一行一行执行前,浏览器事先做了准备工作:
    1. 对变量的**声明**,但不赋值,赋值是在执行到赋值语句时进行的
    2. 直接对this进行**赋值**
    3. 函数:
        1. 函数声明:function foo(){}        //把函数名赋值了!
        2. 函数表达式: var foo = function(){}  //由于是var的变量,与第一种情况一样,只声明,不赋值
    4. 总结:
        1. var出来的变量,函数表达式,只声明,不赋值,默认undefined
        2. this:赋值
        3. 函数声明:赋值

2. 执行**代码段**前的这个准备工作,就叫做执行上下文/上下文环境/执行环境
3. 这个**代码段**其实又分为三种情况:
    1. 全局代码
    2. eval()
    3. 函数【重点↓】

4. 函数代码段中的执行上下文有这些数据:
    1. var 出来的变量只声明,没赋值
    2. this和函数声明已经赋值
    3. 特殊的地方:
        1. arguments变量和函数的参数都已经被赋值

  1. function foo(x){
  2. console.log(arguments);
  3. console.log(x);
  4. }
  5. foo(10);
  6. //在进入函数体内部开始执行之前,函数体内部的arguments和参数x已经被赋值了

   2. **自由变量的取值作用域:赋值**

5. 由此可知,函数每调用一次,都会产生一个新的执行上下文环境,因为不同的调用可能会有不同的参数传入

6. 函数在定义的时候(不是调用的时候),就已经确定了函数体内部**自由变量**的**作用域**

  1. var a =10;
  2. function fn(){
  3. console.log(a);
  4. //本函数在创建的时候就决定了这个a要取值的作用域:全局作用域
  5. }
  6.  
  7. function bar(f){
  8. var a = 20;
  9. f();
  10. }
  11.  
  12. bar(fn); //10

7. 关于this的一点小问题

  1. var obj = {
  2. x:10,
  3. fn:function(){
  4. function f(){
  5. console.log(this);
  6. console.log(this.x);
  7. }
  8. f();
  9. }
  10. };
  11. obj.fn(); //this指window
  12. /*
  13. obj.fn保存了指向
  14. function(){
  15. function f(){
  16. console.log(this);
  17. console.log(this.x);
  18. }
  19. f();
  20. }
  21. 的指针,加一个(): =>obj.fn()表示开始调用:
  22. function f(){
  23. console.log(this);
  24. console.log(this.x);
  25. }
  26. f();
  27. 这个f函数在window环境调用执行,所以this指向window
  28. */

8. 上下文环境的执行顺序
    1. 执行全局代码时,会产生一个全局上下文环境。
    2. 每次调用函数时,会产生函数内部的执行上下文环境。
    3. 当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。
    4. 处于活动状态的执行上下文环境只有一个。

  1. var a=10;
  2. function bar(x){
  3. var b = 5;
  4. fn(x+b);
  5. };
  6. function fn(y){
  7. var c = 5;
  8. console.log(y+c);
  9. };
  10.  
  11. bar(10);
  12. ----------------解析----------------------
  13. /*
  14. 1. 产生全局上下文执行环境,该声明的声明,该赋值的赋值
  15. 1. a = 10 ; fn = function(); bar = function()
  16.  
  17. 2. 到调用bar(10)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值
  18. 1. b = 5; x = 10; arguments = [10]
  19.  
  20. 3. 接着调用fn函数,进入fn函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值
  21. 2. c = 5 ; y = 15;
  22.  
  23. 4. fn函数执行完毕,它对应的上下文执行环境都会被销毁,里面保存的数据都没有了
  24. 5. bar函数同样如此,它对应的上下文执行环境都会被销毁,数据清除
  25. */

四、自由变量和作用域

1. 作用域:javascript除了全局作用域之外,只有函数可以产生作用域。所以,在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式。
2. 作用域就是一个地盘,最大的用处就是隔离变量,使不同作用域下相同的变量名不会产生冲突
3. 每个函数都会创建自己的作用域,作用域在函数**定义**的时候就已经确定了,而不是在调用的时候确定

  1. var a = 10,b=20;
  2.  
  3. function fn(x){
  4. var a =100, c = 300;
  5.  
  6. function bar(x){
  7. var a = 1000,d = 4000;
  8. console.log(x);
  9. console.log(a);
  10. console.log(b);
  11. console.log(c);
  12. console.log(d);
  13. }
  14.  
  15. bar(100);
  16. bar(200);
  17. }
  18.  
  19. fn(10);
  20. ----------------------解析:--------------------------
  21. /*
  22.  
  23. 1. 产生全局上下文执行环境,该声明的声明,该赋值的赋值
  24. 1. a = 10; b=20; fn = function();
  25.  
  26. 2. 到调用fn(10)函数时,进入fn函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值
  27. 1. a = 100 ; c = 300 ; x = 10 ; arguments = [10] ; bar = function()
  28.  
  29. 3. 到调用bar(100)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值
  30. 1. a = 1000 ; d = 4000 ; x = 100;
  31. 4. 到调用bar(200)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值
  32. 1. a = 1000 ; d = 4000 ; x = 200;
  33. 2. 与bar(100)函数相比较,同一个作用域,不同的调用,产生不同的执行上下文环境
  34.  
  35. 5. 总结:
  36. 1. 作用域只是一个“地盘”,一个抽象的概念,其中没有变量。
  37. 2. 要通过作用域对应的执行上下文环境来获取变量的值。
  38. 3. 同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
  39. 4. 作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
  40. 5. 所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
  41.  
  42. */

4. 自由变量:在函数内部中使用的变量x,却没有在函数内部声明,也就是在其他作用域中声明的,对这个函数来说,x就是自由变量

  1. var x= 10;
  2.  
  3. function fn(){
  4. var b = 20;
  5. console.log(x+b);
  6. //x就是自由变量
  7. }
  8. /*
  9. 1. b从本作用域中取,x就要到另外的作用域中取,
  10. 2. 是到fn的父级作用域取吗?不是的!
  11. */
  12. --------------------分割线---------------------------
  13.  
  14. var x= 10;
  15.  
  16. function fn(){
  17. console.log(x);
  18. }
  19.  
  20. function show(f){
  21. var x = 20;
  22.  
  23. function foo(){
  24. f(); //10
  25. }
  26. foo();
  27. }
  28.  
  29. show(fn);
  30. //因此:要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记。
  31. //创建fn函数的作用域是window,所以,x = 10;
  32. /*
  33. 总结一下:
  34. 1. 先在当前作用域查找变量x,没有找到则继续;
  35.  
  36. 2. 如果当前作用域是全局作用域,则证明x未定义,结束;否则继续;
  37.  
  38. 3. 不是全局作用域,那就是函数作用域,将**创建该函数的作用域**作为当前作用域;
  39.  
  40. 4. 跳到第一步循环……
  41.  
  42. */

5. 必须多次重复强调:要去**创建**这个函数的作用域取值,而不是“父作用域”。

五、最后的闭包:

1. 一般情况下,当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
2. 但有些情况下,函数调用完,其执行上下文环境没有销毁,其中的变量也还在内存中,这就是闭包的核心内容。

  1. function fn(){
  2. var max = 10 ;
  3.  
  4. return function bar(x){
  5. if(x>max){console.log(x);}
  6. };
  7. }
  8.  
  9. var f1 = fn(),max = 100 ;
  10.  
  11. f1(15);
  12.  
  13. ----------------解析:---------------------
  14. /*
  15. 1. 执行所有代码前,先生成全局上下文执行环境,该声明的声明,该赋值的赋值
  16. 1. fn = function() ; f1 = undefined ; max = 100;
  17.  
  18. 2. 执行fn()函数,进入fn函数的上下文执行环境,声明、赋值
  19. 1. max = 10 ; bar = function() ;
  20. 2. 执行fn函数里的语句,把bar函数return出去,赋值给fn
  21.  
  22. 3. 【重点】此时,fn函数执行完毕了,按理来说,fn的上下文执行环境应该被销毁,变量从内存清除,但在这里不能这么做!!
  23. 1. 因为执行fn函数的过程中返回了一个bar函数,这个bar函数的特别之处在于, 它创建了一个自己独立的作用域。
  24. 2. 在bar函数内部,有一个max自由变量,如果自己没有,就要到创建这个函数的作用域中找,就是fn函数作用域中的max
  25. 3. 所以,这个max不能被销毁,fn函数的上下文执行环境不能被销毁,依然存在于执行栈中。
  26.  
  27. 4. 也就是说,执行完var f1 = fn() ,max = 100;之后,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。
  28. 5. 创建bar函数是在执行fn函数时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,里面的变量声明、赋值都还在,
  29. 6. 因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到值了。
  30. */

六、上下文环境和作用域的关系

1. 上下文环境:就是一个对象,里面保存了许多属性,所有变量都在这个对象里面存着。对于函数来说,上下文环境是在调用函数的时候创建

2. 作用域:是一个地盘,一个抽象的概念,规定了变量起作用的范围。除了全局作用域之外只有函数才能创建作用域,作用域在函数定义时确定,而不是在函数调用时确定。
3. 关系:
    1. 要通过作用域对应的执行上下文环境来获取变量的值。
    2. 同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
    3. 作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。 
    4. 如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

 
 
标签: JavaScript,

《深入理解javascript原型和闭包系列》 知识点整理(转)的更多相关文章

  1. 《深入理解javascript原型和闭包系列》 知识点整理

    深入理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每个字都值得细细研究. 一.一切都是对象 1. typeof操作符输出6种类型:string boolea ...

  2. 转:深入理解javascript原型和闭包系列

    转自:深入理解javascript原型和闭包系列 从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然 ...

  3. 深入理解javascript原型和闭包系列

    从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...

  4. 深入理解javascript原型和闭包(8)——简述【执行上下文】上

    什么是“执行上下文”(也叫做“执行上下文环境”)?暂且不下定义,先看一段代码: 第一句报错,a未定义,很正常.第二句.第三句输出都是undefined,说明浏览器在执行console.log(a)时, ...

  5. 深入理解javascript原型和闭包(10)——this

    接着上一节讲的话,应该轮到“执行上下文栈”了,但是这里不得不插入一节,把this说一下.因为this很重要,js的面试题如果不出几个与this有关的,那出题者都不合格. 其实,this的取值,分四种情 ...

  6. 深入理解javascript原型和闭包(4)——隐式原型

    注意:本文不是javascript基础教程,如果你没有接触过原型的基本知识,应该先去了解一下,推荐看<javascript高级程序设计(第三版)>第6章:面向对象的程序设计. 上节已经提到 ...

  7. 深入理解javascript原型和闭包(5)——instanceof

    又介绍一个老朋友——instanceof. 对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/ ...

  8. 深入理解javascript原型和闭包(6)——继承

    为何用“继承”为标题,而不用“原型链”? 原型链如果解释清楚了很容易理解,不会与常用的java/C#产生混淆.而“继承”确实常用面向对象语言中最基本的概念,但是java中的继承与javascript中 ...

  9. 深入理解javascript原型和闭包(7)——原型的灵活性

    在Java和C#中,你可以简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼(中秋节刚过完).压个啥样,就得是个啥样,不能随便动,动一动就坏了. 而在javascript中,就没有 ...

随机推荐

  1. windows下mysql增量备份与全备份批处理

    win下的全备批处理 批处理用于游戏服务器,经过严格测试,且正式使用,主要用来完全备份数据库,当然.这只是将数备份出来 ,至于如何将备份出来的数据远程传送的远程服务器上可以调用ftp的功能,此脚本并未 ...

  2. DEEPIN下搭建FTP服务器步骤(备忘录)

    1.打开终端,执行命令[apt-get install vsftpd],安装VSFTPD 2.安装完成后,修改以下配置信息(否则文件无法传输) [echo 'listen=YES'>>/e ...

  3. 苹果系统开发中的混合编程(1):Objective-C和C++的相互调用

    首先是OC调用C++的代码.   创建一个Objective-C的项目,并创建c++文件MyCppFile.hpp和MyCppFile.cpp.   把要调用Cpp代码的文件名改成mm后缀名,项目代码 ...

  4. iOS学习笔记--OC系列(1)

    前言 从学校毕业进入公司工作已经第3个年头了,回顾这3年的经历,有种迷茫的感觉.在公司我主要是做零售业公司的系统维护,接触的主要是Oracle的Database的东西.但是业务知识和oracle,都没 ...

  5. Objective-C 【This is ARC】

    ------------------------------------------- ARC的概念及原理 (1)指针分类 强指针:默认情况下,所有的指针都是强指针,关键字strong 弱指针:_ _ ...

  6. C++ Double Ended Queues(双向队列)

    双向队列和向量很相似,但是它允许在容器头部快速插入和删除(就像在尾部一样). Constructors 创建一个新双向队列 Operators 比较和赋值双向队列 assign() 设置双向队列的值 ...

  7. 三角函数计算,Cordic 算法入门

    [-] 三角函数计算Cordic 算法入门 从二分查找法说起 减少乘法运算 消除乘法运算 三角函数计算,Cordic 算法入门 三角函数的计算是个复杂的主题,有计算机之前,人们通常通过查找三角函数表来 ...

  8. [大牛翻译系列]Hadoop(3)MapReduce 连接:半连接(Semi-join)

    4.1.3 半连接(Semi-join) 假设一个场景,需要连接两个很大的数据集,例如,用户日志和OLTP的用户数据.任何一个数据集都不是足够小到可以缓存在map作业的内存中.这样看来,似乎就不能使用 ...

  9. 使用cookie解决微信不能存储localStorage的问题

    最近在开发基于微信的Web页面时,发现有些机型不能存储信息到localStorage中,或者是页面一旦关闭,存储的信息也失效了. 于是想到用cookie来替代localStorage,存储一些简单的数 ...

  10. Laravel 5 基础(三)- 向视图传送数据

    我们在Routes.php中新建一个路由 Route::get('about', 'PagesController@about'); 在浏览器中浏览会获得一个错误,错误信息仅仅是一个提示信息,缺少细节 ...