读书笔记--你所不知道的js(上卷)

2017-03-24  本文已影响0人  冥冥2017

第一章

编译原理

js是一门编译语言

传统编译语言流程:
  1. 分词/词法分析:把字符串分解成有意义的代码块
js编译:

编译过程不是发生在构建之前的,而是发生在代码执行前的几微秒内

理解作用域

变量的赋值操作:

步骤一:编译器在当前作用域中声明一个变量(如果之前没声明过)
步骤二:运行时,引擎在作用域中查找该变量,如果可以找到就对它赋值
引擎对变量进行LHS查询,另一个查找类型叫RHS查询----L代表左侧,R代表右侧
指的是赋值操作的左侧和右侧,即

变量出现在赋值操作的左侧时进行LHS操作,出现在右侧时进行RHS操作

RHS

1.2.5的小测验

  function foo(a){
      var b = a;
      return a + b;
   }
  var c = foo(2);
执行过程:
总计,3次LHS,4次RHS

区分RHS和LHS的意义:
RHS失败会抛出 ReferenceError 引用异常
LHS失败会自动隐式的创建全局变量(非严格模式下)
RHS之后如果尝试对变量进行不合理的操作,会抛出TypeError,这表明作用域判别成功,但是对结果的操作不合法


第二章

词法作用域:定义在词法阶段的作用域---

作用域气泡

全局变量会自动成为全局对象(比如widow)的属性,因此可以通过window.a来访问那些被同名变量所遮蔽的全局变量

欺骗词法-(性能会下降)

第三章

最小特权原则(最小授权、最小暴露原则)


应用

  1. 全局命名空间
  2. 模块管理

封装原始版本:

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

---不太理想,因为需要声明一个foo(),还需要显式的调用这个函数才能运行
改进版

var a =2;
(function foo(){
var a = 3;
console.log(a);
})(); //注意这一行
console.log(a);

以(function...而不是 function ...开始,函数会被当做函数表达式而不是一个标准的函数声明来处理,这是重要区别
区分函数声明和表达式最简单的方法是看function关键字出现在声明中的位置,如果function是声明的第一个词,那么就是一个函数声明----- 以! 、+ 、-、开头的都是这个逻辑,把函数声明变为了函数表达式
(function foo(){...})作为函数表达式意味着foo只能在(...)所代表的位置中被访问,外部不行

匿名和具名


块作用域

let关键字

let为其声明的变量隐式的指定了所在的块作用域
可以显式的用{...}创建块,使 其与其他语言中的块作用域工作原理一致

{ console.log(bar); // ReferenceError!
let bar = 2;
}

const关键字

也创建块作用域变量

第四章

1、 a =2; var a ; console.log (a); //2
2、console.log(a); var a = 2; //undefined

编译器
引擎会在解释js代码之前先对其进行编译----编译阶段的一个任务就是,找到所有的声明,并用合适的作用域将他们关联起来,因此包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。
var a= 2也许看起来是一个声明,但是js会将其理解为2个声明,var a和a= 2,第一个在编译阶段进行,第二个会被留在原地等待执行阶段,这解释了第二个例子,a只声明了,没有被赋值,赋值操作在console语句之后进行

foo(); //"b"
var a = true;
if(a){
function foo(){ console.log("a") }
}else{
function foo() {console.log("b");}
}

第五章

闭包定义:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
闭包使得函数可以继续访问定义时的词法作用域
经典闭包题:

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

结果是以每秒一次的频率输出5次6

模块

一个从函数调用所返回的,只有**数据属性**而没有**闭包函数**的对象并不是真正的模块
模块模式的两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

模块模式的两个特点:

ES6为模块增加了一级语法支持

浏览器对es6的支持,需要额外添加babel转换------下载babel后把babel链入文件,并把js文件类型改为type="text/babel",就可以了

附录A

js是基于词法作用域的语言,在bar内调用foo时,foo基于自己的作用域链进行查找

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

而this的运行机制是动态作用域

附录C

var foo=a=>{
console.log(a);
}
foo(2);///////2

第二部分第一章

在函数内部引用自身,可以用指向函数对象的词法标识符来引用,但是匿名函数没有词法标识,就不行了,另一种是arguments.callee来应用当前正在运行的函数对象,对匿名函数也有效。

this是什么

this是在运行时进行绑定的,它的上下文取决于函数调用时的各种条件

第二章

通过call()和apply()方法来显式的绑定this


判断this

第三章 对象

定义:声明形式和构造形式
声明形式:

var myObj={
key:value
/ / ...

};

构造形式:

var myObj= new Object();
myObj.key=value;


简单基本类型:string boolean number null undefined ——————并不是对象
(null 执行typeof会返回object,是因为不同对象底层表示为二进制,二进制前三位都为0的话会被判断为object,null的二进制表示全是0,因此会被错误判断)
函数和数组都是对象的一种类型
但是有对象子类型,叫内置对象
String Number Boolean Object Function Array Date RegExp Error


对象的属性
属性名称与值,属性名称存在对象容器内部,但是值一般不存在对象内部,而是通过属性名称作为指针指向它的存储位置。
两种访问方式:myObject.a 或者 myObject[a]
前者为属性访问,后者为键访问
两者有些区别,属性访问要求要满足标识符的命名规范,因此Super-Fun!这种属性名不符要求,由于后者是使用字符串访问,因此可以在程序中构造这个字符串
ES6新增了可计算属性名

var prefix = "foo"
var myObject = {
[prefix + "bar" ] :"hello",
[prefix + "baz"] : "world"

};


对象的浅复制和深复制
es6的浅复制 Object.assign()
var newObj = Object.assign({},myObject);
浅复制值会复制旧对象的值,对象的话会引用自原对象
es5的属性描述符:
Object.getOwnPropertyDescriptor(myObject,"a")
获取myObject的a属性的描述
三个特性:writable/configurable/enumerable
可修改/可配置/可列举(比如是否出现在for --in中)
默认或者设置 Object.defineProperty(...)


var myObject= {
a:2
};
myObject.a;

这个过程中实现了[[Get]]操作,先在对象中查找是否有名称相同的属性,如果没有,会继续遍历可能存在的原型链
还有一个[[Put]]
属性的getter和setter:成对出现

var myObject = {
get a(){
return this.a;
},
set a(val){
this.a=val*2;
};
myObject.a = 2;
myObject.a; /////4


一个属性undefined时,判断是属性中存着undefined还是属性本身不存在

var myObject = {
a:2
};
("a" in myObject);/ /true
("b" in myObject);/ /false
或者
myObject.hasOwnProperty("a");/ /true
myObject.hasOwnProperty("b");/ /false

二者略有不同,in 操作符会检查对象自身及其原型链(检查是否有这个属性名,而不是值,这个对于数组来说区别明显 4 in[2,4,6]返回false),而hasOwnProperty显然只检查对象自身(如果对象没有连接到Object.prototype这种方法会失败,因此改进一下,Object.prototype.hasOwnProperty.call(myObject,"a")就可以了)


枚举:指出现在对象的属性遍历中:

for (var k in myObject){
console.log(k,myObject[k]);
}
(数组的遍历用传统的for循环更好)

myObject.propertyIsEnumerable("a");/ /true
myObject.propertyIsEnumerable("b");/ /false
或者
Object.keys(myObject);会返回所有可枚举属性
Object.getOwnPropertyNames(myObject);会返回所有属性
es6的for of来遍历属性的值:
for(var v of myArray){
}


用内置的@@iterator迭代器来手动遍历数组:

var myArray = [1,2,3];
var it = myArraySymbol.iterator;
it.next();/ / {value:1, done:false}
it.next();/ / {value:2, done:false}
it.next();/ / {value:3, done:false}
it.next();/ / { done:true}

@@iterator本身不是一个迭代器对象,而是一个返回迭代器对象的函数,因此后边需要跟一个()
手动为一个对象添加自己的迭代器

var myObject = {
a:2,
b:3
};
Object.defineProperty(myObject,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var o = this;
var idx = 0;
var ks = Object.keys(o);
return{
next:function(){
return{
value:o[ks[idx++]],
done:(idx>ks.length)
}
}
}
}
})

第四章

js中的显式混入对象:

function mixin(souceObj,targeObj){
for(var key in sourceObj){
if(!(key in targetObj)){
targetObj[key] = sourceObj[key];
}
}
js中不存在类,都是对象
函数多态:显式多态和相对多态
显式多态缺点多,频繁引用原函数

第五章 原型

myObject.foo = "bar"

会发生的事情:

  1. 如果myObject中已包含名为foo的普通数据访问属性,那么这条只会修改已有的属性值
  2. 如果遍历原型链也没找到foo,那么会在myObject上添加foo
  3. 然而如果原型链上层有这条属性,会出现3种情况:
    A:如果上层存在普通数据访问属性,并且没有被标记为只读(writable:false),那么会直接在myObject中添加一个叫foo的新属性
    B:如果上层有且为只读,那么会抛出错误或者被忽略
    C:如果上层叫foo的是一个setter,那么会调用上层的setter

要注意避免隐式遮蔽:myObject.a++会隐式创建myObject.a


原型继承:委托机制
foo foo.prototype foo.prototype.constructor
constructor是默认构造foo时会添加给foo.prototype的属性,如果人为构造原型可能会丢失constructor
此外,var a=new foo();,a.constructor=foo并不代表,a是有constructor这个属性,该属性其实是[[Get]]查询原型链从foo.prototype那里引用来的


b.prototype=Object.create(a.prototype)
会创建一个新对象并把新对象内部的[[Prototype]]
关联到指定的对象
es6可以设定原型了:
Object.setPrototypeOf(Bar.prototype,Foo.prototype)


b.isPrototypeOf(a)——表达:b是否出现在a的原型链中
直接获取一个对象的[[Prototype]]链
Object.getPrototypeOf(a);
也有部分支持属性法:
a._proto_===Foo.prototype;

第六章

[[Prototype]]机制把对象关联到其他对象:
不同于类设计模式,方法名尽量不使用通用方法名,而是创建更有描述性的名字
注意区别类模式中的显式伪多态调用方法与委托模式中的委托调用的区别

上一篇 下一篇

猜你喜欢

热点阅读