前言
最近读勒基本关于前端的数据《JavaScript核心技术开发解密》,《webpack从入门到进阶》…这几本书帮助到我更好的理解JS、webpack在前端技术领域中的作用。以前可能更多的是知道怎么使用,但从未从更深的层面去思考他们是如何运作,为什么会产生这种特性,等等…这本书先从《JavaScript核心技术开发解密》开始讲解,分为两篇讲完,读完本篇你能学到:
前端进阶必读:《JavaScript核心技术开发解密》核心提炼一前端进阶必读:《JavaScript核心技术开发解密》核心提炼三
@TOC
五、作用域与作用域链
在JS中,作用域是用来规定变量/函数可访问的规则
5.1作用域
5.1.1全局作用域
在非严格模式,未定义的变量/函数会自动的变成window的属性;在实践中,尽可能的减少使用全局变量/函数,为了减少协作冲突,提高性能(全局作用域在作用域链的链尾)
5.1.2函数作用域
在ES6之前,es是没有块级作用域的,所以一定是在函数环境中才能生成新作用域
案例:
var arr = [1,2,3];for(var i = 0; i < arr.length; i++) {console.log(i);}console.log(i); // 4
5.1.3模拟块级作用域
A: 利用函数产生
var arr = [1,2,3];// 匿名自执行函数执行会产生函数作用域(function () {for(var i = 0; i < arr.length; i++) {console.log(i);}})()// 匿名自执行函数的几种写法(function(){})() // 最常用*function(){}()-function(){}()!function(){}()
重要知识来了:
在ES中,往往通过自执行函数来实现模块化;模块化是非常重要的开发思维
5.2作用域链
scopeChain是当前执行环境的上层执行环境的一系列变量对象组成,串联作用域。案例:
var a = 20;function test () {var b = a + 10;function innerTest () {var c = 10;return b + c;}return innerTest;}test();/**以上代码innerText的作用域链如下:innerTestEC = {VO: {...}, // 变量对象scopeChain: [VO(innerTest),VO(test),VO(window)], // 好好看这里!this: {...}}**/
6、闭包
到了这里,就到了前端是个人都听过的东西了,极其重要的特性!!!
6.1概念
闭包是一种特殊对象由两部分组成:
- 执行上下文A
- 该执行上下文创建的函数B
B执行时,刚问了A中的值,闭包即产生
6.2闭包与垃圾回收机制
正常的上下文运行后,内部的内容都是去了引用而被垃圾回收,但闭包导致A的变量被B持有,阻止了垃圾回授(所以也会导致性能问题)
6.3闭包与作用域链
- 作用域链在编译中以确定规则,在执行中产生
- 函数调用栈在执行中才确定
6.4 在chrome开发工具中能够观察函数调用栈,作用域链与闭包
6.5 应用闭包
6.5.1 循环、setTtimeOut与闭包
案例:利用闭包的知识,修改下面这段代码,让代码的执行结果为隔秒输出1,2,3,4,5
for(var i = 0; i <= 5; i++) {setTimeOut(function timer() {console.log(i);},i * 1000 )}
利用闭包修改
for(var i = 0; i <= 5; i++) {(function (i) {setTimeOut(function timer() {console.log(i);},i * 1000 )})(i)}
或者
for(var i = 0; i <= 5; i++) {setTimeOut((function (i) {return function timer () {console.log(i);}})(i), i * 1000 )}
6.5.2单例模式与闭包
在JS中有许多编程思维,其中单例模式时实践中最常用的模式之一,而它的实现与闭包息息相关
七、this
鸡婆一下:来到这个很多人都搞不清楚this指向的问题了,看完这一样相信你会豁然开朗,因为this指向其实比想象中要简单得多。
7.1 this指向
当函数被调用执行时,函数进入创建阶段(前面有讲到),此时会生成变量对象,this确定指向,还做了XXX,随后进入执行阶段。案例:
var a = 10;var obj = {a : 20}function fn () {console.log(this.a);}fn(); // 10 (方法时一定有调用者的,这里省略是因为在非严格模式下,自动变成window.fn())fn.call(obj); // 20 因为fn函数的执行上下文this被修改成obj对象
7.2 call/apply/bind显示绑定this
相信很多人都知道这三个函数的作用,call/apply几乎一样,知只是传参规则不同,bind是先绑定,返回绑定后的方法,未执行。
call | apply | bind |
---|---|---|
直接传参 | 数组传参 | 直接传参(不立即执行,而是返回修改this后的方法) |
ES6中的箭头函数是不能改变this的,使用以上三个方法也不能(先预告,后面会讲到)
八、面向对象
8.1.1
// 构造函数与普通函数并无区别,首字母大写是约定的习惯,new关键字才是将构造方法变得与众不同var Person = function (name, age) {this.name = name;this.age = age;}Person.protptype.getName = function () {return this.name}
8.1.2 new关键字
下面我们来模拟一边new关键字的作用
function New (constructFunc) {// 生命中间对象,最后作为返回的实例,相当于let obj = New(Obj); => obj = resvar res = {};if(constructFunc.prototype !== null) {// 将实例的原型指向构造函数的原型res.__proto__ = constructFunc.prototype;}// ret为该构造函数执行的结果,将构造函数的this改为执行resvar ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));// 如果构造函数有返回值,则直接返回if((typeof rest === "object" || typeof ret === "function") && ret !== null) {return ret;}// 否则返回该实例return res;}
总结:
- 先创建空的、新的实例对象(New(func)返回的对象)
- 将实例对象的原型指向构造函数的原型
- 将构造函数的this改为指向实例对象
- 最后返回该实例对象(如果构造函数本身有返回则直接返回)
8.1.3更简单的原型写法
function Person () {};Person.prototype.getName = function () {};Person.prototype.getAge = function () {};// 简化Person.prototype = {construor : Person, // 此时Person.protottype指向了新的对象,所以会导致原型对象丢失,所以加上此行getName : function () {},getAge: function () {},}
8.1.4实例方法、原型方法、静态方法
function Foo () {this.bar = fucntion () { // 实例方法return \'bar in Foo\';}}Foo.bar = function () {return \'static\'} // 静态方法Foo.prototype.bar = function () {return \'prototype\'} // 原型方法