最近再次拜读冴羽大佬的博客,收益颇多。第一次读的时候有点囫囵吞枣,很多不清楚。这次把重要内容做了简单的总结,方便回顾。

1. 原型、原型链

2. 词法作用域和动态作用域

作用域

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

静态作用域与动态作用域

因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的

3. 执行上下文栈

当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。 当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

4. 变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值

5. 作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链

函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:

var scope = "global scope";
function checkscope(){var scope2 = 'local scope';return scope2;
}
checkscope();

执行过程如下:

1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]

checkscope.[[scope]] = [globalContext.VO
];

2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack = [checkscopeContext,globalContext
];

3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链

checkscopeContext = {Scope: checkscope.[[scope]],
}

4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明

checkscopeContext = {AO: {arguments: {length: 0},scope2: undefined},Scope: checkscope.[[scope]],
}

5.第三步:将活动对象压入 checkscope 作用域链顶端

checkscopeContext = {AO: {arguments: {length: 0},scope2: undefined},Scope: [AO, [[Scope]]]
}

6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值

checkscopeContext = {AO: {arguments: {length: 0},scope2: 'local scope'},Scope: [AO, [[Scope]]]
}

7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [globalContext
];

6. this

基于ECNAscript标准解析this指向的问题。

Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。 Reference 的构成,由三个组成部分,分别是:

  • base value
  • referenced name
  • strict reference

1.计算 MemberExpression 的结果赋值给 ref

2.判断 ref 是不是一个 Reference 类型

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)2.3 如果 ref 不是 Reference,那么 this 的值为 undefined

7. 闭包

​ 理论角度:

闭包是指那些能够访问自由变量的函数。

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量

从实践角度:以下函数才算是闭包:
  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  2. 在代码中引用了自由变量

让我们先写个例子:

var scope = "global scope";
function checkscope(){var scope = "local scope";function f(){return scope;}return f;
}var foo = checkscope();
foo();

f 执行上下文维护了一个作用域链:

fContext = {Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

闭包执行过程中,创建他的上下文已经销毁了。但是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

8. 参数按值传递

ECMAScript中所有函数的参数都是按值传递的。

两个复杂结构传递的例子:

var obj = {value: 1
};
function foo(o) {o.value = 2;console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
var obj = {value: 1
};
function foo(o) {o = 2;console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1

注意: 按引用传递是传递对象的引用,而按共享传递是传递对象的引用的副本!

所以修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。所以第二个和第三个例子其实都是按共享传递。

最后,你可以这样理解:

  1. 参数如果是基本类型是按值传递,如果是引用类型按共享传递。

  2. 但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递了。

9. call 和 apply的模拟实现

call的概念:

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var foo = {value: 1
};function bar() {console.log(this.value);
}bar.call(foo); // 1

注意两点:

  1. call 改变了 this 的指向,指向到 foo
  2. bar 函数执行了

实现总体思路:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数

其他细节:

  1. 传参:使用数组获取arguments,并使用eval函数执行;
  2. 返回值,将结果返回。

10. bind实现

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

由此我们可以首先得出 bind 函数的两个特点:

  1. 返回一个函数
  2. 可以传入参数

总体思路:

  1. 返回一个函数,函数中使用apply传入this指向的对象;
  2. 其他参数通过arguments拼接
  3. 可以new的构造函数通过修改返回的函数的原型来实现。修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值

11. new 的模拟实现

使用 new 操作符,调用构造函数会执行如下操作:
(1) 在内存中创建一个新对象。
(2) 这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性(获取原型上的属性和方法)。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象) -> 获取构造函数内的属性和方法。
(4) 执行构造函数内部的代码(给新对象添加属性)。
(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

12. 类数组对象与arguments

所谓的类数组对象:

拥有一个 length 属性和若干索引属性的对象

类数组转数组的方法:

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"]
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"]
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"]
// 4. apply
Array.prototype.concat.apply([], arrayLike)

Arguments 对象的 callee 属性,通过它可以调用函数自身。

13. 创建对象的多种方式以及优缺点

  1. 工厂模式。在函数内new一个object对象,然后设置各个属性

    缺点:对象无法识别,因为所有的实例都指向一个原型

  2. 构造函数模式

    优点:实例可以识别为一个特定的类型

    缺点:每次创建实例时,每个方法都要被创建一次

  3. 原型模式。 属性和方法都写在构造函数的原型上

    优点:方法不会重新创建

    缺点:1. 所有的属性和方法都共享 2. 不能初始化参数

  4. 组合模式,构造函数模式与原型模式双剑合璧。

    优点:该共享的共享,该私有的私有,使用最广泛的方式

    缺点:有的人就是希望全部都写在一起,即更好的封装性

14. 继承的多种方式和优缺点

  1. 原型链继承,将子元素的原型指向父元素;

    缺点:引用类型的属性被所有实例共享,

    ​ 在创建 Child 的实例时,不能向Parent传参

  2. ****借用构造函数****(经典继承) 改变this执行,借用方法

    function Parent () {this.names = ['kevin', 'daisy'];
    }
    function Child () {Parent.call(this);
    }
    

    优点:

    1.避免了引用类型的属性被所有实例共享

    2.可以在 Child 中向 Parent 传参

    缺点:

    方法都在构造函数中定义,每次创建实例都会创建一遍方法。

  3. ****组合继承****,原型链继承和经典继承双剑合璧。变量写在构造函数里,方法写在原型链上。

  4. 原型式继承

    //就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
    function createObj(o) {function F(){}F.prototype = o;return new F();
    }
    

    缺点:

    包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

JavaScript深入系列-冴羽博客读后总结相关推荐

  1. javascript设计模式系列 - LukeLin - 博客园

    javascript设计模式系列 创建型: 1.抽象工厂模式(Abstract Factory) 2.构建者模式(Builder) 3.工厂方法模式(Factory Method) 4.原型模式(Pr ...

  2. JavaScript设计模式系列—模式篇总结(上)

    转载请注明预见才能遇见的博客:http://my.csdn.net/ 原文地址:https://blog.csdn.net/pcaxb/article/details/102517956 JavaSc ...

  3. 吐槽java之《程序员的呐喊》读后总结

    <程序员的呐喊>读后总结 --关于java的批判 一.写在总结前面的一些废话 <程序员的呐喊>(后文简称呐喊),是一本非常有趣的散篇,全文都是作者对目前软件开发界的看法,主要翻 ...

  4. 元宇宙时代 理查德数和哥德尔数——哥德尔读后之二十一

    元宇宙时代 理查德数和哥德尔数--哥德尔读后之二十一 电视机产生之后,人类就开始进入屏幕时代.而这个屏幕时代,因为计算机的出现,特别是个人计算机的出现,它似乎就在用远超寻常的速度,以技术发现为标志,从 ...

  5. 哥德尔原著译本的前十四个定义——哥德尔读后之十三

    哥德尔原著译本的前十四个定义--哥德尔读后之十三 广州的疫情依然紧张,我校昨晚还包括今天早上,员工都在奔赴南操场进行自5月底以来的第二次核酸检测.因为疫情,差不多有一个月都蜷缩在家中了,顶多因为生活必 ...

  6. JavaScript 测试系列实战(一):使用 Jest 和 Enzyme 测试 React 组件

    你或许早已经知道"单元测试""端到端测试"这些名词,但从未真正付诸实践.在这一系列实战教程中,我们将手把手带你掌握 Jest.Enzyme.Cypress 等测 ...

  7. 新生 语不惊人死不休 —— 《无限恐怖》读后有感

    开篇声明,我博客中"小心情"这一系列,全都是日记啊随笔啊什么乱七八糟的.如果一不小心点进来了,不妨直接关掉.我自己曾经写过一段时间的日记,常常翻看,毫无疑问我的文笔是很差的,而且心 ...

  8. 深入理解javascript函数系列第二篇——函数参数

    前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...

  9. javascript动画系列第一篇——模拟拖拽

    前面的话 从本文开始,介绍javascript动画系列.javascript本身是具有原生拖放功能的,但是由于兼容性问题,以及功能实现的方式,用的不是很广泛.javascript动画广泛使用的还是模拟 ...

最新文章

  1. Hadoop实战(6)_搭建Apache Hadoop的Eclipse开发环境
  2. 怎么注册今日头条?哪里可以直接购置?
  3. 第三方账号登陆的过程及由此引发的血案
  4. 进制转换应用场景_PLC编程:PLC内部各类型数据转换(整理分享)
  5. Java技术分享:升级所安装Java版本的两种方式
  6. mybatis mysql 配置文件详解_Mybatis的配置文件参数详解
  7. 从LeNet到SENet——卷积神经网络回顾
  8. 今日恐慌与贪婪指数为77 贪婪程度有所上升
  9. dagger2 android快速注入框架相关学习资料
  10. 与Android热更新方案Amigo的亲密接触
  11. Android Multimedia框架总结(四)MediaPlayer从Java层到C++层类关系及prepare及之后其他过...
  12. el-input只能输入数值和小数点,并且不显示上下箭头
  13. c# iot .net6 树莓派+RS485串口工业级光照度传感器 代码实例
  14. HTML5期末大作业:动漫网站设计——迪斯尼公主(6个页面) HTML+CSS+JavaScript 动漫网页HTML代码 学生网页课程设计期末作业下载 动漫大学生网页设计制作成
  15. DCGAN生成动漫头像(附代码)
  16. 第九节 Shiro标签原理分析
  17. 全链路压测原理剖析(Coding)
  18. 【ZZULIOJ】1037:四则运算
  19. SqlSession 同步为注册,因为同步未激活
  20. git_v1.txt

热门文章

  1. 【LSTM回归预测】基于matlab灰狼算法优化LSTM回归预测【含Matlab源码 2038期】
  2. 索尼大变身:消费电子业务转向医疗设备
  3. 耀辉:医院网络营销之站外合作篇
  4. 十天小白训练营day02—古诗作业
  5. HTML罕见的冷门标签
  6. 揭秘维基解密及创始人:朱利安·阿桑奇 [图]
  7. stackoverflow出现的原因
  8. Ubuntu连接不了网络的解决方法
  9. 关于12306网络购票的架构方面思考
  10. 美国CPSIA关于玩具和儿童产品的测试要求,CPC证书要求