《你不知道的JavaScript—上卷》

本文深入解析JavaScript中的作用域、闭包、this绑定及对象原型等核心概念。通过详细阐述作用域的词法特性、闭包的工作原理、this的绑定规则及对象的属性管理方式等内容,帮助读者全面理解这些关键特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

语雀地址:你不知道的JavaScript—上卷

两章,一:作用域和闭包,二:this和对象原型。

一:作用域和闭包

1.1作用域

js引擎执行代码的步骤——编译:词法分析,语法分析,代码生成
工作原理:
js引擎:负责整个程序的编译及执行过程
编译器:负责语法分析及代码生成
作用域:收集并维护由所有变量(声明的标识符)组成的一系列查询,并实施一套很严格的规则,确定当前执行的代码对变量的访问权限
赋值操作的过程:编译器在当前作用域声明一个变量(如果之前没有),运行时引擎在当前作用域查找该变量,找到后赋值。
编译器里面:
LHS查询:赋值操作的目标是谁
RHS查询:指在赋值操作的非左侧,谁是赋值操作的源头
作用域的嵌套:——词法作用域(定义时的作用域)
一个块或函数嵌套在另一个块或函数中。在当前作用域中无法找到某变量时,引擎会在外层嵌套的作用域中查找,知道找到该变量或到最外层作用域(全局作用域)
异常
RHS查询在所有嵌套的作用域中都找不到所需变量,引擎抛出ReferenceError异常
RHS查询找到了变量,但是对变量的值得操作不合理,引擎抛出TypeError异常
LHS查询,在所有嵌套的作用域中都找不到所需变量,非严格模式下自动隐式创建一个变量,严格模式下抛出ReferenceError异常

1.2 词法作用域

作用域的两种工作模型:
词法作用域:定义在词法阶段的作用域——大多数编程语言采用
动态作用域:定义过程发生在代码书写阶段,作用域链是基于调用栈的
词法作用域只会查找一级标识符,foo.bar.baz,词法作用域只负责找到foo,后续由对象属性访问规则来接管
欺骗词法:运行时修改词法作用域
eval:接受一个字符串作为参数,并假装其中内容是书写时就在这个位置(实际是又创建了一个变量,进行遮蔽)。严格模式下,eval运行时有自己的词法作用域,无法欺骗了
with:接收一个对象,将对象的引用当做作用域来处理,对象的属性当做作用域中的标识符来处理,创建了一个新的词法作用域

1.3 函数作用域和块作用域

函数作用域:属于这个函数的全部变量都可以在整个函数范围内使用及复用
闭包的作用:隐藏变量实现,避免同名标识符之间的冲突(模块管理也可以避免冲突)
函数声明:以function…开始——绑定在所在作用域中
函数表达式:以(function …开始——绑定在函数表达式自身的函数中,变量名隐藏在自身中
二者最重要的区别是名称标识符会绑定在何处
IIFE:immediately invoked function expression立即执行函数,函数表达式+()
块作用域:对最小授权原则进行扩展
实现:with,try/catch,let(隐性劫持所在的块作用域),const
for(let i=0;… 循环将i绑定到for的块中,也绑定到每一个迭代中

1.4提升

函数和变量声明会被提升到所在作用域顶部,函数优先(如果存在同名函数和变量,变量声明会因为已经有了而被忽略)
函数表达式不会提升,let声明不会在块作用域中提升
var a=2
编译阶段:var a
执行阶段:a=2

1.5 闭包

函数可以记住并访问所在的词法作用域时,即使函数是在当前词法作用域之外执行,就产生了闭包
只要使用了回调函数(函数作为参数进行实现),实际上就是在使用闭包
模块的特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回值至少包括一个对外部函数的引用(闭包)

二、this和对象原型

2.1 this

this是在函数被调用时发生的绑定,指向什么完全取决于函数在哪里被调用

2.2 this全解

调用栈:为了达到当前执行位置所调用的所有函数
function baz(){…}; baz();
…的调用栈是baz,当前调用位置是全局
this绑定规则:(1)new中调用——新创建的对象;
(2)call、apply显示绑定或硬绑定调用——绑定的对象
(3)某个上下文对象中调用,隐式绑定——上下文对象
(4)默认绑定——严格是undefined,非严格是全局对象
箭头函数:根据当前的词法作用域来决定this,继承外层函数调用的this绑定
空对象:Object.create(null)比{}更空,因为不会创建Object.prototype委托

2.3 对象

对象就是键/值对的集合
JS有6种语言类型:string、number、boolean、null、undefined、object
内置对象:String、Number、Boolean、Object、Function、Array、Date、RegExp、Error
可以直接在字符串字面量上访问属性或方法,因为引擎自动把字面量转换成String对象
.a属性访问 [“a”]键访问——.a需要注意命名规范
属性和方法:在js中是可以互换的——函数永远不会“属于”一个对象
数组:特殊对象,可以添加命名属性,但length不会变
属性描述符:value:值
writable:可写——修改属性值
enumerable:可枚举——出现在for…in循环中
configurable:可配置——修改属性描述符,not value(false时也还可以将writable改为false,但是不能改为true),false还会禁止删除
可以用Object.defineProperty添加/修改属性描述符

实现不变:(1)对象常量:不可写,不可配置
(2)禁止扩展,禁止添加新属性:Object.preventExtensions(xxx)
(3)密封:Object.seal(xxx):相当于禁止扩展+不可配置
(4)冻结:Object.freeze(xxx):相当于密封+不可写
对象默认的Put和Get操作分别控制属性值得设置和获取
Put算法检查内容:(1)属性是否是访问描述符(有getter和setter,存在即用setter)
(2)writable:false——非严格:静默失败,严格:TypeError
(3)进行设置
访问描述符:拥有getter或setter或都有
判断属性存在:(1)(属性名 in 对象)——检查对象及原型链;
(2)对象.hasOwnProperty(属性名)/Object.prototype.hasOwnProperty.call(对象, 属性名)——仅检查对象
判断属性可遍历:(1)对象.propertyIsEnumerable(属性名);
(2)Object.keys(对象),返回可遍历的属性名数组;Object.getOwnPropertyNames(对象)——仅检查对象
遍历数组:for循环、for…of循环(寻找内置/自定义的@@iterator对象并调用它的next()方法)

2.4 类

类意味着复制,实例化时行为复制到实例对象,继承时行为复制到子类中。——js中没有复制机制
js中只有类似类的语法。类是一种设计模式:代码的组织结构形式。
数据结构:把数据以及和它相关的行为打包
js中,类是属于构造函数的。
继承:复制。子类得到的是父类的副本,修改子类的方法不会影响父类
多态(相对多态):任何方法都可以引用继承层次中高层的方法
js不会(像类那样)自动创建对象的副本,可以用混入模式模拟类的复制行为(但不完全,因为复制的是地址)

2.5 原型

书中将原型写为[[prototype]],即更常见的写法:proto
原型机制就是指对象中的一个内部链接引用了另一个对象。原型链的顶端是Object.prototype
Object.create(…)的作用:创建一个新的A对象,并把A的原型关联到B上,即A.proto=B
A=Object.create(B)
属性屏蔽:A.xx=“xx”,如果A自己没有xx属性,但是A的原型链上有,就会给A创建一个xx属性,即屏蔽了原型链上的xx属性(原型链上的xx属性为不可写或有访问描述符则不会屏蔽)

2.5.1 假类函数

js中没有复制机制,并没有初始化一个类。new只是让qq和QQ这两个对象关联起来,qq可以通过委托访问QQ的属性和方法 let qq=new QQ() //等同于 let a=Object.create(QQ.prototype)
测试下来:new操作符会在新对象qq上初始化this开头属性。其他都保留在QQ.prototype上,仅用__proto__访问
QQ.prototype上添加了ww这个复杂数据类型,qq的__proto__会直接变化
即所有this属性(含基本和复杂)都会实例化到新对象上,但非this属性都保留在原对象上(含基本和复杂)
在这里插入图片描述

注意:函数里需要写this属性,需要在函数原型上加属性使用 函数.prototype.xx=xxx
不要直接在函数里写xx属性,会被直接挂在window上面的

       下面截图中是正确写法        修改BBB的原型属性不会影响AAA,因为BBB自己新建了一个,然后发生了屏蔽              

在这里插入图片描述

new关键字:new会劫持所有普通函数并用构造对象的形式来调用它
构造函数:所有带new调用的函数

2.5.2 对象的.constructor属性

指向一个函数,不要理解成默认由XXX构造。
对象.constructor=对象.constructor.prototype…constructor
加粗样式

2.5.3 委托关系

new构造的对象和原对象的prototype之间产生委托关系,下面的原型之间也产生委托关系。
BBB.prototype=Object.create(AAA.prototype)
不过BBB.prototype会丢失它的.constructor属性
错误写法:(1)BBB.prototype=AAA.prototype 直接引用,修改BBB.prototype也会导致AAA.prototype修改
(2)BBB.prototype=new AAA() AAA的构造函数调用,如果AAA给this添加数据属性会影响BBB()的后代
检查对象的委托关系:
对象 instanceof 函数; //True或false 不过无法判断两个对象之间的关联
对象.prototype.isPrototypeOf(对象X) 对象的原型链上是否有对象X
对象X.isPrototypeOf(对象) 对象X是否出现在对象的原型链中
Object.getPrototypeOf(对象) 获取对象的原型链
对象.proto=对象X.prototype (大多数浏览器支持) __proto__更像getter/setter,对Object.getPrototypeOf(对象)进行读写操作

2.5.4 关联

关联就是实现委托设计模式。与作为代理(Proxy)的备用功能有区别
对象关联是一种编码风格,倡导直接创建和关联对象,而不用抽象成类

2.6 委托

禁止互相委托,因为引擎开发者在设置时检查比调用时检查更高效。
class类继承模式:ES6封装的原型的语法糖。无法定义类成员属性,需要实例共享属性还是要用prototype
行为委托:对象之间是兄弟关系。原型本质上就是委托机制。
内省:检查实例的类型

鸭子类型:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型

从作者观点看,更支持使用行为委托,class让一些问题被隐藏起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值