你不知道的javascript

js编译原理

分为三个步骤:
1、分词/词法分析

作用:将代码分解为代码块(词法单元)

分词和词法分析的区别:

在生成词法单元时,如果生成的词法单元是未声明的(词法单元生成器调用无状态的解析规则),解析的过程被称为分词。
如果生成的词法单元是已经声明的(词法单元生成器调用有状态的解析规则),解析的过程被称为词法分析。

举例

var a = 1;//此处是分词,词法单元生成器将其解析为var、 a、 =、 1、 ;五个词法单元。
a = 2;//此处是词法分析,因为a已经声明了,词法单元生成器将调用有状态的解析规则。

2、解析/语法分析

作用:将词法单元流(数组)转换为有层次关系的抽象语法树(Abstract Syntax Tree,AST)

3、代码生成

作用:将AST转换为可执行代码的过程被称为代码生成

理解作用域

相关概念
1、引擎:负责整个JavaScript程序的编译及执行过程。
2、编译器:负责词法分析、语法分析及代码生成。
3、作用域
4、LHS和RHS

LHS和RHS是变量的两种查询方式,可以简单理解为赋值操作的左侧和右侧(注意:有些赋值操作并没有显示的=,比如形参)。
LHS:因为需要为变量赋值,所以要查询到变量的容器本身,可以理解为内存地址。
RHS:赋值操作的右侧变量的查询,此时只用关心右侧变量的值,所以需要查询变量在内存中的值。

举例

var a = 1;//a变量会进行LHS查询
var b = a;//b变量会进行LHS查询,a变量会进行RHS查询
function fun(a){//a变量被隐式赋值,所以是LHS查询
	console.log(a);//此处没有赋值操作,是RHS查询
}
fun(1);//fun进行了RHS查询

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

异常

ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

词法作用域

1、函数词法作用域只有函数被声明时所处位置决定
2、词法作用域只会查找一级标识符,如果代码中引用了foo.bar.baz,词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则会分别接管对bar和baz属性的访问。

函数作用域和块级作用域

函数作用域
函数声明和表达式的区别:

如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

块级作用域
for循环中用var声明的 i 并未绑定在for循环的块级作用域中,而是绑定在了for循环的外层作用域中

for(var i=0;i<10;i++){
 	.....
}

可以将上述例子理解为

{
	var i;
	for(i=0;i<10;i++){
	 	.....
	}
}

另外var声明的变量可以跨块访问到

for(var i=0;i<10;i++){
	var j=1;
}
console.log(j);//1

在for循环中用let声明的变量附属于一个新的作用域而不是当前for循环后面的块级作用域,也不是for循环的外层作用域

例子一:
for(let i=0;i<10;i++){
	...
}
console.log(i)//Uncaught ReferenceError: i is not defined

例子二:
for(let i=0;i<10;i++){
	let i=1;
}

上面第一个例子在外部访问i,报错
第二个例子用let声明了两个i,但是可以声明成功,说明两个i不是在同一个块级作用域
上面的例子可以理解为

{
	let i;
	for (i=0;i<10;i++){
		...
	}
}

let 和const声明的变量不能跨块访问

{
	let i=0;
}
console.log(i);//1

循环和闭包

for(var i=0;i<5;i++){
	(function(){
		var j = i;
		setTimeout(function(){
			console.log(j);
		}, i*1000)
	})()
}
for(var i=0;i<5;i++){
	(function(j){
		setTimeout(function(){
			console.log(j);
		}, i*1000)
	})(i)
}

块级作用域和闭包

for()中的let声明在循环中不止声明一次,每次迭代都会声明,每次迭代都会使用上一次迭代结束的值来初始化这个变量

词法作用域和动态作用域的区别

词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。(this也是!)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
例如:

function bar(){
    console.log(a);
}
function foo(){
    var a = 3;
    bar();
}
var a = 2;
bar();

上面的函数在词法作用域中输出2,但是如果是在动态作用域中输出应该为3(因为动态作用域查找变量是沿着调用栈查找的)。

this词法

箭头函数的this指向

箭头函数中的this取决于其外层作用域的this指向

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this绑定

硬绑定

function peopleInfo(){
	console.log('name: ' + this.age + ',age: ' + this.age);
}
var people = {
	name:'heidy',
	age:10,
};
function bind(fn, obj){
	return function(){
		fn.apply(obj);
	}
}
var fun = bind(peopleInfo,people);
fun();

对象

对象常量
结合writable:false和configurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)

var obj = {};
Object.defineProperty(obj,'a',{
	value: 1,
	writable: false,
	configurable: false
})

禁止扩展
Object.preventExtensions(…)

var obj = {a: 1};
Object.preventExtension(obj);
obj.b = 2;//创建属性b会静默失败。在严格模式下,将会抛出TypeError错误。

Get
访问对象属性时,如果对象没有属性,会到原型链上找,如果原型链上也没有,返回undefined
Put
[[Put]]算法大致会检查下面这些内容

1.属性是否是访问描述符?如果是并且存在setter就调用setter。
2.属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError异常。
3.如果都不是,将该值设置为属性的值。

Getter | Setter
可以使用getter和setter部分改写默认的Get和Set操作。

当你给一个属性定义getter、setter或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript会忽略它们的value和writable特性,取而代之的是关心set和get(还有configurable和enumerable)特性。
下面得例子中,第一个例子,obj.a的赋值被忽略了

//例子一
var obj = {
	get a(){
		return 2;
	}
}
obj.a = 1
obj.a //2

//例子二
var obj1 = {
	get a(){
		return 2;
	}
	a: 1
}
obj1.a //1

判断对象上是否有某个属性

in操作符会检查属性是否在对象及其[[Prototype]]原型链中,hasOwnProperty(…)只会检查属性是否在myObject对象中,不会检查[[Prototype]]链。

Object. create(null)创建的对象没有连接到Object.prototype。
看起来in操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在。对于数组来说这个区别非常重要,4 in [2, 4, 6]的结果并不是你期待的True,因为[2, 4, 6]这个数组中包含的属性名是0、1、2,没有4。

在数组上应用for..in循环有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。

propertyIsEnumerable(..)会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足enumerable:true。

obj.propertyIsEnumerable('a');

Object.keys(..)会返回一个数组,包含所有可枚举属性

Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举

第5章 原型

几乎所有的对象在创建时[[Prototype]]都会被赋予一个非空的值(也有一些特例,比如Proxy)

对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[Prototype]]链

原型链

所有的普通的原型链最终都会指向Object.prototype对象,所以这个对象包含了很多通用功能
Object.create():会创建一个对象并把这个对象的[[Prototype]]关联到指定的对象。
for in:任何可以通过原型链访问到的属性(同时属性是可枚举的enumerable),都会被枚举。
in:会查找对象的整条原型链(无论属性是否可枚举),来检查属性是否存在于对象中。

Object.prototype
所有普通的原型链最终都会指向内置的Object.prototype,所以其包含了许多通用的功能,比如toString()、valueOf()

属性的设置和屏蔽

在这里插入图片描述

//原型链上的color属性被标记为只读,那么不会在cloud对象上创建屏蔽属性
//也无法修改原型链上的color属性。严格模式下,代码会报错
var sky = {};
Object.defineProperty(sky, 'color', {
    value: 'blue',
    writable: false,
    configurable: false
});
var cloud = Object.create(sky);
console.log(cloud); //{}
console.log(cloud.color); //blue
cloud.color = 'white'; //writable为false,修改失败
console.log(cloud.color); //blue
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值