温故而知新--JavaScript书摘(一)

前言:

毕业到入职腾讯已经差很少一年的时光了,接触了不少项目,也积累了不少实践经验,在处理问题的方式方法上有很大的提高。随着时间的增长,越发发现基础知识的重要性,不少开发过程当中遇到的问题都是由最基础的知识点遗忘形成,基础不牢,地动山摇。因此,就再次回归基础知识,从新学习JavaScript相关内容,加深对JavaScript语言本质的理解。日知其所亡,身为有追求的程序员,理应不断学习,不断拓展本身的知识边界。本系列文章是在此阶段产生的积累,以记录下以往没有关注的核心知识点,供后续查阅之用。程序员

2017

02/26

apply(参数为数组) 、 call (参数需一一列举)、bind 三者都是用来改变函数的this对象的指向的;apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;apply 、 call 、bind 三者均可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply 、call 则是当即调用 。

02/27

匿名函数:在栈追踪中不显示有意义的函数名,难以调试,没有函数名很差引用自身,缺乏了可读性和可理解性。
块级做用域:with、try{} catch(){}、let、const。let:不会有变量提高。

02/28

JS代码执行分为两个阶段:编译阶段、执行阶段。
包含函数和变量的全部声明都会在任何代码被执行前首先被处理,声明自己会被提高,而赋值或其余运行逻辑会留在原地。函数声明和函数表达式不一样,函数表达式不会总体提高,只会把表达式赋给的变量声明提高,函数表达式赋值还留在原来位置。
函数声明和变量声明都会被提高,可是一个值得注意的细节(这个细节能够出如今有多个 “重复”声明的代码中)是函数会首先被提高,而后才是变量。  

03/01

闭包使得函数能够继续访问定义时的词法做用域。不管经过何种手段将内部函数传递到所在的词法做用域之外,它都会持有对原始定义做用域的引用,不管在何处执行这个函数都会使用闭包。
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function timer() {
            console.log(j);
        }, j * 1000);
    })(i);
}
在迭代内使用 IIFE 会为每一个迭代都生成一个新的做用域,使得延迟函数的回调能够将新的做用域封闭在每一个迭代内部,每一个迭代中都会含有一个具备正确值的变量供咱们访问。  
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}

03/02

模块有两个主要特征:(1)为建立内部做用域而调用了一个包装函数;(2)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会建立涵盖整个包装函数内部做用域的闭 包。
主要区别:词法做用域是在写代码或者说定义时肯定的,而动态做用域是在运行时肯定的。(this也是!)词法做用域关注函数在何处声明,而动态做用域关注函数从何处调用。 动态做用域并不关心函数和做用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,做用域链是基于调用栈的,而不是代码中的做用域嵌套。

03/03

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法做用域来决定 this,具体来讲,箭头函数会继承外层函数调用的 this 绑定(不管 this 绑定到什么)。这 其实和 ES6 以前代码中的 self = this 机制同样。 简单来讲,箭头函数在涉及 this 绑定时的行为和普通函数的行为彻底不一致。它放弃了所 有普通 this 绑定的规则,取而代之的是用当前的词法做用域覆盖了 this 原本的值。

03/04

须要明确的是,this 在任何状况下都不指向函数的词法做用域。在 JavaScript 内部,做用 域确实和对象相似,可见的标识符都是它的属性。可是做用域“对象”没法经过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。  以前咱们说过 this 是在运行时进行绑定的,并非在编写时绑定,它的上下文取决于函数调 用时的各类条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。  this 既不指向函数自身也不指向函数的词法做用域,this 其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里被调用。

03/06

Object.create(null) 和 {} 很 像, 但  并 不 会 创 建 Object. prototype 这个委托,因此它比 {}“更空”。
typeof null == “object” :原理是这样的,不一样的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判 断为 object 类型,null 的二进制表示是全 0,天然前三位也是 0,因此执行 typeof 时会返回“object”。  

03/07

若是你试图向数组添加一个属性,可是属性名“看起来”像一个数字,那它会变成一个数值下标(所以会修改数组的内容而不是添加一个属性)。 
对于 JSON 安全(也就是说能够被序列化为一个 JSON 字符串而且能够根据这个字符串解析出一个结构和值彻底同样的对象)的对象来讲,有一种巧妙的复制方法:  var newObj = JSON.parse( JSON.stringify( someObj ) );  
要注意有一个小小的例外:即使属性是 configurable:false,咱们仍是能够把 writable 的状态由 true 改成 false,可是没法由 false 改成 true。
不变性:
  • 1. 对象常量 : 结合 writable:false 和 configurable:false 就能够建立一个真正的常量属性(不可修改、 重定义或者删除)。
  • 2. 禁止扩展若是你想禁止一个对象添加新属性而且保留已有属性, 可 以使用 Object.prevent Extensions(..)。
  • 3. 密封 Object.seal(..) 会建立一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把全部现有属性标记为 configurable:false。
  • 4. 冻结 Object.freeze(..) 会建立一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把全部“数据访问”属性标记为 writable:false,这样就没法修改它们 的值。
在 ES5 中可使用 getter 和 setter 部分改写默认操做,可是只能应用在单个属性上,没法应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏函数,会在设置属性值时调用。  

03/08

“可枚举”就至关于“能够出如今对象属性 的遍历中”。  
更增强硬的方法来进行判 断:Object.prototype.hasOwnProperty. call(myObject,"a"),它借用基础的 hasOwnProperty(..) 方法并把它显式绑定到 myObject 上。  
propertyIsEnumerable(..) 会检查给定的属性名是否直接存在于对象中(而不是在原型链 上)而且知足 enumerable:true。
Object.keys(..) 会返回一个数组,包含全部可枚举属性,Object.getOwnPropertyNames(..) 会返回一个数组,包含全部属性,不管它们是否可枚举。
遍历数组下标时采用的是数字顺序(for 循环或者其余迭代器),可是遍历对象属性时的顺序是不肯定的,在不一样的 JavaScript 引擎中可能不同。所以, 在不一样的环境中须要保证一致性时,必定不要相信任何观察到的顺序,它们是不可靠的。

03/09

使用 for..in 遍历对象时原理和查找 [[Prototype]] 链相似,任何能够经过原型链访问到 (而且是 enumerable)的属性都会被枚举。使用 in 操做符来检查属性在对象 中是否存在时,一样会查找对象的整条原型链(不管属性是否可枚举)。 所以,当你经过各类语法进行属性查找时都会查找 [[Prototype]] 链,直到找到属性或者查找完整条原型链。
全部普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。因为全部的“普通” (内置,不是特定主机的扩展)对象都“源于”(或者说把 [[Prototype]] 链的顶端设置为) 这个 Object.prototype 对象,因此它包含 JavaScript 中许多通用的功能。
若是 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo = "bar" 会出现的三种状况:
  • 1. 若是在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性而且没有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新 属性,它是屏蔽属性。
  • 2. 若是在 [[Prototype]] 链上层存在 foo,可是它被标记为只读(writable:false),那么 没法修改已有属性或者在 myObject 上建立屏蔽属性。若是运行在严格模式下,代码会 抛出一个错误。不然,这条赋值语句会被忽略。总之,不会发生屏蔽。
  • 3. 若是在 [[Prototype]] 链上层存在 foo 而且它是一个 setter,那就必定会 调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会从新定义 foo 这 个 setter。 若是你但愿在第二种和第三种状况下也屏蔽 foo,那就不能使用 = 操做符来赋值,而是使 用 Object.defineProperty(..)来向 myObject 添加 foo。

03/10

Foo.prototype 默认有一个公有而且不可枚举的属性 .constructor,这个属性引用的是对象关联的函数。 能够看到经过“构造函数”调用 new Foo() 建立的对象也有一个 .constructor 属性,指向 “建立这个对象的函数”。 在普通的函数调用前面加上 new 关键字以后,就会把这个函数调用变成一个“构造函数 调用”。实际上,new 会劫持全部普通函数并用构造对象的形式来调用它。 换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,全部带 new 的函数调用。 这是一个很不幸的误解。实际上,.constructor 引用一样被委托给了 Foo.prototype,而 Foo.prototype.constructor 默认指向 Foo。 Foo.prototype 的 .constructor 属性只是 Foo 函数在声明时的默认属性。若是 你建立了一个新对象并替换了函数默认的 .prototype 对象引用,那么新对象并不会自动获 得 .constructor 属性。
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 建立一个新原型对象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
委托给委托链顶端的 Object.prototype。这个对象 有 .constructor 属性,指向内置的 Object(..) 函数。
Bar.prototype = Object.create( Foo.prototype )
若是使用内置的 .bind(..) 函数来生成一个硬绑定函数的话, 该函数是没有 .prototype 属性的。在这样的函数上使用 instanceof 的话, 目标函数的 .prototype 会代替硬绑定函数的 .prototype。  
Foo.prototype.isPrototypeOf(a);
b.isPrototypeOf(c);
.__proto__ 实际上并不存在于你正在使用的对象中 ,存在于内置的 Object.prototype 中(它是不可枚举的)。  

03/11

Object.create(null) 会建立一个拥有空( 或者说 null)[[Prototype]] 连接的对象,这个对象没法进行委托。这些特殊的空 [[Prototype]] 对象一般被称做“字典”,它们彻底不会受到原型链的干扰,所以很是适合用来存储数据。
对象的连接被称为“原型链”,  JavaScript 中这个机制的本质就是对象之间的关联关系。
委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另外一 个对象(Task)。  
对象关联能够更好地支持关注分离(separation of concerns)原则,建立和初始化并不须要 合并为一个步骤。
// 让 Foo 和 Bar 互相关联
Foo.isPrototypeOf(Bar); // true
Object.getPrototypeOf(Bar) === Foo; // true
行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。也就是说,咱们能够选择在 JavaScript 中努 力实现类机制,也能够拥抱更天然的 [[Prototype]] 委托机制。
对象关联(对象以前互相关联)是一种编码风格,它倡导的是直接建立和关联对象,不把它们抽象成类。对象关联能够用基于 [[Prototype]] 的行为委托很是天然地实现。
首先,你可能会认为 ES6 的 class 语法是向 JavaScript 中引入了一种新的“类”机制,其 实不是这样。class 基本上只是现有 [[Prototype]](委托!)机制的一种语法糖。 也就是说,class 并不会像传统面向类的语言同样在声明时静态复制全部行为。
class 语法没法定义类成员属性(只能定义方法)。class 语法仍然面临意外屏蔽的问题。class 很好地假装成 JavaScript 中类和继承设计模式的解决方案,可是它实际上起到了反做 用:它隐藏了许多问题而且带来了更多更细小可是危险的问题。  
相关文章
相关标签/搜索