书看了一段时间就忘记了,这次从新开始看,以博客的方式记录笔记,可能比较杂乱,如果你看了有用也是极好的,更建议你自己去买书或者看电子书
Let’s beginning
编译原理
通常将js归类为动态解释性语言,实际上javascript 引擎进行编译的步骤 和 传统编译语言非常类似。
1,分词/词法分析(Tokenizing/Lexint)
比如我们声明了一个 var a = 2,这时候这段代码会被分解为, var,a, =, 2 这些词法单元,空格是否会被当作词法单元,取决于空格在这门语言中是否有意义,比如在js中,空格不会被解析为词法单元。
2,解析/语法分析
这个过程是将第一部转换成的词法单元流(数组)转换成一个由元素逐渐嵌套所组成的代表了程序语法的结构的树,这和react里面那个ast抽象语法树实际上是类似的概念。react是将组件理解为一个抽象的语法树。“抽象语法树“ 全名简称”abstract syntax tree“。
我们以上面的为例,var a = 2 , 在整个抽象语法树里面可能会存在一个VariableDeclaration的顶级节点,抽象语法树为一个顶级对象,里面的一个一级节点就为VariableDeclaration对象,这个对象有一个Identifier它的值为a,它也有一个叫做AssignmentExpression的子节点。 AssignmentExpression节点有一个叫做NumericLiteral的子节点(它的值为2)
写成对象可能是这样:
GlobalAST: {
VariableDeclaration: {
Identifier: 'a',
AssignmentExpression: {
NumericLiteral: 2
}
}
}
3, 代码生成
将AST转换成为可执行代码的过程称之为代码生成。这个过程与语言,目标平台等信息息息相关。
比如我们将一份js代码,放在浏览器里面 和 nodeJS环境下,执行出来的结果可能是不同的。
作用域
引擎:比如google v8 引擎。负责整个js程序的编译以及执行过程。
编译器: 编译器是引擎的好朋友,编译器负责语法分析->转换抽象语法树->代码生成,然后引擎才能执行这些代码。
作用域: 作用域是引擎的另外一个好朋友,负责收集并维护由所有声明的标识符(变量)组成一些列查询。
LHS RHS
还有一个重要的概念,LHS RHS 左查询 和 右查询
LHS 去找到赋值的目标也【就是变量】,有一次赋值操作,就又一次LHS查询
RHS 去找到变量的值,就有一次RHS查询
小测验:
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2)
//有多少次LHS 查询? 3 what is the detail ?
//有多少次RHS 查询? 4 what is the detail ?
如果在LHS查询 和 RHS查询中遇到了错误怎么办呢?
RHS:
console.log(a) 这是执行的RHS,我们可以在chrome的控制台里面执行一次。
会报错: VM264:1 Uncaught ReferenceError: a is not defined
RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError的错误。
如果RHS查询到了一个变量,但是对这个变量执行不合理的操作,比如对一个number类型的变量执行split操作,就会报错,TypeError【表示作用域判别变量成功了,但是对结果的操作是非法的,就会报这个错误】。
LHS:
如果执行LHS查询的时候,如果在顶层(全局作用域)中无法找到目标变量,全局作用域中会
创建一个具有该名称的变量,并将其返回给引擎。【前提非严格模式下,而且es6的 let const等不会存在变量提升。】
定义函数的两种方式
以function开头的是函数声明的方式,其他的是函数表达式,其实setTimeout(function(){}),也是函数表达式,只是他是一个匿名函数表达式。
函数表达式是可以匿名的,但是匿名的函数声明在javascript是非法的,比如你不可以这样写 function(){ console.log(‘匿名函数声明’) },但是你可以这样写: var fname = function() { console.log(‘匿名函数表达式’) }
更加建议使用具名函数表达式。比如上面的setTimeout,我们可以这样写:
setTimeout(function timeoutHandler() {
console.log('I waited 1 second')
}, 1000)
1-代码可读性更高
2-出现异常的话,在栈中追踪不会出现有意义的函数名,只会出现anoynimous function,调试起来会困难不少。
==作用域 ==
ES6以前没有块级作用域的概念,只有函数作用域和全局作用域。
ES6出现了 let const 锁定一个块级作用域
提升
var a = 2
这句代码实际上有两个步骤来完成,编译阶段,和执行阶段。
编译阶段 转换成ast,然后转换成可执行代码 根据前面的知识点这是编译器来完成。 第二阶段 执行可执行代码 根据前面的只是点这是由引擎来完成。 第一阶段实际上就包含来变量的声明阶段,第二阶段才是执行的赋值阶段。
所以 var a = 2
有两个步骤
第一步: var a 声明
第二步: a = 2 赋值
提升是什么意思,可以理解为先声明,然后在执行阶段做赋值等其他操作。提升仅仅是指在当前的作用域提升【除去特殊情况】, 下面这个例子,可以看到 b仅仅提升了声明在函数内的作用域,对外面的全局作用域并没有影响。
a = 2
var a
console.log(a)
function () {
console.log(b)
var b = 3
}
console.log(b)
每个作用域都会提升,只会在各自的作用域提升。
tip:
1, 函数声明和变量都会提升。
2, 函数表达式不会被提升。
3, 函数声明首先会被提升,然后才是变量。
闭包
书面语:函数可以记住并且访问现在的词法作用域时,就产生了闭包,即使函数是在当前词法呕用语之外执行的。
函数 内引用了一个外部作用域的变量,函数执行的时候能够获取到外层作用域的该变量值。
闭包的应用—模块化
闭包来实现私有化变量,实现模块化。
可以先看之前的一个例子,闭包私有化变量。
function initGoodInfo({name: '', nums: 0, unit: 0}) {
let _name = name,
_nums = nums,
total = nums * unit
const getTotalPrice = () => {
return total
}
const getDetailInfo = () => {
return ''
}
return {
getTotalPrice: getTotalPrice,
getDetailInfo: getDetailInfo
}
}
模块有两个关键的特性:
1, 一个被调用的外部包装函数,用来创建外围作用域。
2,这个包装函数的返回值必须包含至少一个内部函数的引用【这个引用至少引用外部包装函数的局部变量】,这个函数才拥有包装函数内部作用域的的闭包。
this对象
之前做过一次对于this的记录:点击这个链接
this不是编写的时候决定,是运行的时候决定。this绑定和函数声明的方式无关,和函数的调用方式有关。
this实际上是函数在调用时建立的一个绑定,它指向什么完全由函数被调用的调用点来决定。
看下面的例子,你可以在chrome console里面执行这段代码:
var a = 'global string a'
function foo() {
console.log(this.a)
}
const obj = {
a: 'local string a',
foo: foo
}
// 谁调用的这个函数,谁是this
obj.foo() // 'local string a'
// 谁调用的这个函数,谁是this,这里没有xx.fooRef() 默认为全局作用域
// 传递的只是一个引用
const fooRef = obj.foo
fooRef() // 'global string a'
this绑定的方式:
隐式绑定:
默认的方式
显示绑定:
call/apply: 一样的效果,参数的传递方式不同。
new :构造函数使用【特殊点,return一个对象,则返回这个对象,如果返回一个非对象,无效,默认返回一个构造的对象】
bind: 返回一个函数,这个函h数的this会永久绑定到bind的第一个参数
总结:
1, 函数是和new 一起被调用的吗(new 绑定)?如果是,this就是新构建的对象。
var bar = new foo()
2, 函数如果是用call 或者apply被调用(明确绑定),甚至是硬绑定在bind的第一个参数中,如果是,那么this就指代明确的对象[call/apply/bind的第一个参数]。
const bar = foo.bind(obj)
3,函数是用环境对象(也称拥有者或者容器对象)被调用的吗(隐含绑定)?如果是,this就是致那个拥有者。
const obj = {
a: 'something',
foo: function() {
console.log(this.a);
}
}
obj.foo()
4, 使用默认的this(默认绑定),严格模式下是undefined,非严格模式就是全局对象,在chrome下是window,在node环境下是global。
// 全局模式下 chrome的console里面,执行 foo()
// is equal to
window.foo()
对象属性命名和复制
如果以string意外的其他值作为属性名,那么它首先会被转换为一个字符串。即使是数字也不会例外。虽然在数组下标中使用的是数字,但是在对象属性名中数字会被转换为字符串。数组和对象中数字的用法是不同的。
var obj = {}
obj[true] = "foo"
obj[3] = "bar"
obj[obj] = "fighter"
// then
obj["true"]
obj["3"]
obj["object Object"]
复制对象的一个方法[这个方法由于时区问题转换date的时候会出现错误]
请自行搜索,JSON.stringify() date 关键字
var newObj = JSON.parse(JSON.stringify(someObj))
对象属性的一些控制
writable : 是否可以修改属性的值。
configurable: 可配置性,如果设置为false,第二次给该属性调用defineProperties会报错。如果是false,阻止修改属性,阻止删除属性。
enumerable: 是否可枚举【可以理解为是否可以被遍历出来】
const obj = {}
Object.defineProperty(obj, 'a', {
writable: true,
configurable: true,
enumerable: true
})
阻止对象添加新的属性,但是不能阻止删掉该对象属性:
const obj = { name: 'kevin' }
Object.preventExtensions(obj)
delete obj.name
console.log(obj) // { }
定义一个对象常量属性 ,这个属性不能被修改,删除:
const obj = {}
Object.defineProperty(obj, "CONST_NUMBER", {
value: 7,
configurable: false,
writable: false
})
Object.seal()
方法 是同时调用了Object.preventExtensions 以及将configurable 设置为false。
Object.freeze()
方法是调用了Object.seal 以及将属性writable设置为false
检查属性是否属于对象
in
'name' in obj
会检查原型链上面是否存在该属性
hasOwnProperty
obj.hasOwnProperty('name')
只会检查属性是否在obj对象中,而不会检查[[prototype]]链 。但是这个可能会出错,hasOwnProperty是来自于Object.prototype,可能有的对象没有链接到这个对象。 比如Object.create(null),这个对象的prototype是null,并没有和Object.prototype链接起来
遍历对象
for in
会找原型链,获取的是对象属性或者数组下标, 而不是属性值,可作用于数组或者对象
for of
ES6新增的,遍历数组用的,可作用于数组 Map Set,默认对象是不行的,会报错。因为他们没有实现迭代器方法。数组 Map Set 的’Symbol.iterator’方法。
const obj = {
name: 'kev',
age: 18
}
for (let value of obj) {
console.log(vallue)
}
// 会报错 Uncaught TypeError: obj is not iterable
// 我们可以手动给该对象实现迭代, 比较重要
Object.defineProperty(obj, Symbol.itertor, {
enumerable: false,
writable: false,
confurable: true,
value: function() {
var o = this
var idx = 0
var keys = Object.keys(o)
return {
next: function() {
return {
value: o[keys[idx++]],
done: (idx > keys.length)
}
}
}
}
})
instanceof
a instanceof Foo
在a对象的整条[[Prototype]]链中是否又指向Foo.prototype 的对象
委托
funcgion Foo(){}
const foo = new Foo()
foo.constructor === Foo
foo 并没有constructor 属性, 它会委托[[Prototype]]链 (原型链)上的Foo.prototype。
内部委托
var anotherObject = {
cool: function() { console.log('cool') }
}
var myObjct = Object.create(anotherObject)
myObject.doCool = function(){
this.cool() // 内部委托
}
myObject.doCool()// cool
//我们这里调用的是doCool方法是实际存在于myObject对象的
//比我们直接调用myObject.cool()更加清晰。
内部委托比起直接委托可以让API接口设计更加清晰。