《你不知道的javascript(上卷)》——读书笔记

《你不知道的javascript(上卷)》

摘要:
        我觉得这本数对javascript的一些难点有深层次的分析,遂打算近日拿来学习. 
        本书目录: 
               第一部分: 作用域和闭包
               第二部分: this和对象原型


第一部分:

  • 作用域

概念:引擎、编译器、作用域
编译器术语:RHS(retrieve his source value)取得它的源值,LHS是指赋值操作的目标是谁.
二者都会顺着作用域链往上找,直到全局
RHS:如果无法找到变量,则为未申明的变量,抛出referenceError
LHS:如果无法找到变量,则新建全局变量
referenceError:作用域判别失败
typeerror:作用域判别成功,对结果的操作不合理(如访问null的属性)

作用域共有两种主要的工作模型: 1.被大多数编程语言所采用的词法 作用域   2. 动态作用域
  •  词法作用域
    大部分编译器的第一个工作阶段叫作词法化( 也叫单词化)
    词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。 编译的词法分析阶段 基本能够知道全部标识符在哪里以及是如何声明的, 从而能够预测在执行过程中如何对它
们进行查找。
的位置决定。

    欺骗词法作用域(性能损耗严重,一般避免使用,strict模式下不可用)
    JavaScript 中有两个机制可以“ 欺骗” 词法作用域: eval(..) 和 with。 前者可以对一段包 含一个或多个声明的“ 代码” 字符串进行演算, 并借此来修改已经存在的词法作用域( 在 运行时)。 后者本质上是通过将一个对象的引用当作作用域来处理, 将对象的属性当作作 用域中的标识符来处理, 从而创建了一个新的词法作用域( 同样是在运行时)。
    性能问题: JavaScript 引擎会在编译阶段进行数项的性能优化其中有些优化依赖于能够根据代码的,  词法进行静态分析, 并预先确定所有变量和函数的定义位置, 才能在执行过程中快速找到 标识符。 eval和with会新建作用域的位置 打破原本的顺序 , 造成代码运行缓慢. 这两个机制的副作用是引擎无法在编译时对作用域查找进行优化, 因为引擎只能谨慎地认 为这样的优化是无效的。 使用这其中任何一个机制都将导致代码运行变慢。  

  • 函数作用域和块作用域
    利用函数作用域模块化
    变量最小权力原则
    利用函数作用域避免变量名冲突
    
  • 匿名函数 1.不具有函数名.  2.只能用argument.callee调用自己, 引用自身的例子, 是在事件触发后事件监听器需要解绑自身. 3.缺乏了可读性

(function IIFE( global ) {
var a = 3;
console.log( a ); // 3
console.log( global.a ); // 2
})( window );

上面的函数自执行可以将外部变量传入函数内 , 解决 undefined 标识符的默认值被错误覆盖导致的异常

  • 块级作用域: 
    1.从 ES3 开始,try/catch 结构在 catch 分句中具有块作用域
 try {
undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {
console.log( err ); // 能够正常执行!

onsole.log( err ); // ReferenceError: err not found
2.{ }内出现的let和const
    if (..) { let a = 2; } 会声明一个劫持了 if 的 { .. } 块的变量, 并且将变量添加到这个块 中。
    有利于垃圾清理,块状作用域中运行完就可以进行垃圾清理了


  • 变量提升----------先声明变量,后赋值

  • 闭包  函数内部返回一个函数,母函数在执行之后,并未进行垃圾回收机制销毁变量,母函数的作用域仍被子函数访问,子函数在自己定义的词法作用域以外的地方执行。子函数依然持有对母函数作用域的引用,这个引用称为闭包。闭包使得函数还能继续访问定义时的作用域,使js的词法作用域完整.   总结: 当函数可以记住并访问所在的词法作用域, 即使函数是在当前词法作用域之外执行, 这时就产生了闭包


  • 动态作用域
    要明确的是, 事实上 JavaScript 并不具有动态作用域。 它只有词法作用域, 简单明了。但是 this 机制某种程度上很像动态作用域。 主要区别: 词法作用域是在写代码或者说定义时确定的, 而动态作用域是在运行时确定 的。





第二部分    this和对象原型

  • this
this 提供了一种更优雅的方式来隐式“ 传递” 一个对象引用
this 是在运行时进行绑定的, 并不是在编写时绑定, 它的上下文取决于函数调 用时的各种条件。 this 的绑定和函数声明的位置没有任何关系, 只取决于函数的调用方式

  • 默认绑定
函数内部的this指向window


  • 隐式绑定-------this丢失 : 一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象, 也就是说它会应用默认绑定, 从而把 this 绑定到全局对象或者 undefined 上, 取决于是否是严格模式。(赋值操作或者函数传参 , 回调函数丢失 this 绑定是非常常见的)  

  • 显式绑定 ---------call,apply  也称为硬绑定
function foo(something) {
console.log( this.a, something );
return this.a + something;
} /
/ 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
由于硬绑定是一种非常常用的模式, 所以在 ES5 中提供了内置的方法 Function.prototype. bind

原生bind的poly-fill实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// 与 ECMAScript 5 最接近的
// 内部 IsCallable 函数
throw new TypeError(
"Function.prototype.bind - what is trying " +
"to be bound is not callable"
);
}
var aArgs = Array.prototype.slice.call( arguments, 1 ),
fToBind = this,
fNOP = function(){},
fBound = function(){
return fToBind.apply(
(
this instanceof fNOP &&
oThis ? this : oThis
),
aArgs.concat(
Array.prototype.slice.call( arguments )
);
}
; f NOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}


[1, 2, 3].forEach( foo, obj );
api调用的时候,第二个参数可以指定回调函数中使用的this  为了方便~

  • new绑定
使用 new来调用函数, 或者说发生构造函数调用时, 会自动执行下面的操作。
1. 创建( 或者说构造) 一个全新的对象。
2. 这个新对象会被执行 [[ 原型 ]] 连接。
3. 这个新对象会绑定到函数调用的 this。
4. 如果函数没有返回其他对象, 那么 new 表达式中的函数调用会自动返回这个新对象。

  • 4种绑定的优先级
1. 由 new 调用? 绑定到新创建的对象。
2. 由 call 或者 apply( 或者 bind) 调用? 绑定到指定的对象。
3. 由上下文对象调用? 绑定到那个上下文对象。
4. 默认: 在严格模式下绑定到 undefined, 否则绑定到全局对象。


  • 例外: 
     1.null 或者 undefined 作为 this 的绑定对象传入 call、 apply 或者 bind, 这些值 在调用时会被忽略, 实际应用的是默认绑定规则
     2.“ 间接引用”, 在这 种情况下, 调用这个函数会应用默认绑定规则
  1. 箭头函数不使用 this 的四种标准规则, 而是根据外层( 函数或者全局) 作用域来决定 this。箭头函数最常用于回调函数中, 例如事件处理器或者定时器






第三部分    对象

  • 深拷贝:
var newObj = JSON.parse( JSON.stringify( someObj ) );  
深拷贝完美实现
            var cloneObj    = function(obj){
            var str, newobj = obj.constructor === Array ? [] : {};
            if(typeof obj   !== 'object'){
            return;
            } else if(window.JSON){
            str             = JSON.stringify(obj), //系列化对象
            newobj          = JSON.parse(str); //还原
            } else { for(var i in obj){
            newobj[i]       = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i];
            }
            }
            return newobj;
            };
  • 浅拷贝:
var newObj = Object.assign( {}, myObject );  

  • 判断对象是否有属性
in会查询属性是否在对象及其原型链中,
而hasOwnProperty()会判断是否在本身中,不涉及原型

  • 数组新增方法区别:
for…of 访问对象的iterator迭代器对象,并调用next()来遍历返回所有值
foreach遍历数组所有值没有返回值
every遍历直到返回false
some遍历直到返回true

  • 遍历对象
for...in
Object.keys

  • 手动定义对象的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)
                    };
                }
            };
        }
    } );

总结:
对象就是键 / 值对的集合。 可以通过 .propName 或者 ["propName"] 语法来获取属性值。 访 问属性时, 引擎实际上会调用内部的默认 [[Get]] 操作( 在设置属性值时是 [[Put]]), [[Get]] 操作会检查对象本身是否包含这个属性, 如果没找到的话还会查找 [[Prototype]]






第四部分   混合对象"类"

面向类的设计模式: 实例化( instantiation)、 继承( inheritance) 和 ( 相对) 多态( polymorphism)。
类: 结构清晰、 组织良好. 类并不是必须的编程基础, 而是一种可选 的代码抽象。其他语言中的类和 JavaScript 中的“ 类” 并不一样 , 实际上js没有"类"





第五部分   原型

属性查找顺序:
[[Get]] 和 for...in 如果在本身上找不到对应的属性, 都会去 [[Prototype]]链上找
var anotherObject = {
a:2
};
// 创建一个关联到 anotherObject 的对象
var myObject = Object.create( anotherObject );
myObject.a; // 2
它会创建一个 对象并把这个对象的 [[Prototype]] 关联到指定的对象


两种关联prototype的方法:

  1. Bar.prototype = Object.create()
Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你 指定的对象      
poly-fill代码:
if (!Object.create) {
     Object.create = function(o) {
     function F(){}
          F.prototype = o;
          return new F();
     };
}

  1. Object.setPrototypeOf( Bar.prototype, Foo.prototype );


总结:
1.[[Get]] 操作( 参见第 3 章) 就会查找对象内部 [[Prototype]] 关联的对象。 这个关联关系实际上定义了一条“ 原型链”,在查找属性时会对它进行遍历。

2.所有普通对象都有内置的 Object.prototype, 指向原型链的顶端( 比如说全局作用域), 如 果在原型链中找不到指定的属性就会停止。 toString()、 valueOf() 和其他一些通用的功能 都存在于 Object.prototype 对象上, 因此语言中所有的对象都可以使用它们

3.关联两个对象最常用的方法是使用 new 关键词进行函数调用, 在调用的 4 个步骤( 第 2 章) 中会创建一个关联其他对象的新对象 .. 使用 new 调用函数时会把新对象的 .prototype 属性关联到“ 其他对象”。 带 new 的函数调用 通常被称为“ 构造函数调用”

4. 虽然这些 JavaScript 机制和传统面向类语言中的“ 类初始化” 和“ 类继承” 很相似, 但 是 JavaScript 中的机制有一个核心区别, 那就是不会进行复制, 对象之间是通过内部的 [[Prototype]] 链关联的 , 因为对象之间的关系不是复制而是委托




第六部分 委托

将原本通过构造函数继承模式改为对象间通过手动设置__proto__联系
Task = {
setID: function(ID) { this.id = ID; },
outputID: function() { console.log( this.id ); }
};
// 让 XYZ 委托 Task
XYZ = Object.create( Task );
XYZ.prepareTask = function(ID,Label) {
this.setID( ID );
this.label = Label;
};
XYZ.outputTaskDetails = function() {
this.outputID();
console.log( this.label );
};
对象和对象之间相关联(委托), XYZ通 过 Object. create (..) 创建, 它的 [[Prototype]] 委托了 Task 对象( 参见第 5 章)
  相对于面向对象,这种编码风格称为"对象关联"     特点1:  id 和 label 数据成员都是直接存储在 XYZ 上   

委托行为意味着某些对象( XYZ) 在找不到属性或者方法引用时会把这个请求委托给另一 个对象( Task)。

用 Object. setPrototypeOf (..) 来修改它的 [[Prototype]] // 现在把 AuthController 关联到 LoginController
Object.setPrototypeOf( AuthController, LoginController );


扩展:
  • bind(..) 可以对参数进行柯里化( 预先设置一些参数)
function foo(p1,p2) {
this.val = p1 + p2;
} /
/ 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2



猜你喜欢

转载自blog.csdn.net/weixin_40821790/article/details/78863511