资讯专栏INFORMATION COLUMN

你不知道的javascript(上卷)读后感(二)

Ali_ / 855人阅读

摘要:词法熟悉语法的开发者,箭头函数在涉及绑定时的行为和普通函数的行为完全不一致。被忽略的作为的绑定对象传入,使用的是默认绑定规则。使用内置遍历数组返回迭代器函数普通对象不含有,无法使用,可以进行改造,个人博客地址

this词法

熟悉ES6语法的开发者,箭头函数在涉及this绑定时的行为和普通函数的行为完全不一致。跟普通this绑定规则不一样,它使用了当前的词法作用域覆盖了this本来的值。

误解

this理解成指向函数本身,函数对象的属性(Fun.X)不是this.X 【X】
this指向函数的作用域 【X】
this是运行时进行绑定的,而不是编写时绑定的,它的执行上下文取决于函数调用时的各种条件,this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式(调用位置),具有动态性,简单地说,函数执行过程中的调用位置决定了this的绑定对象。

何为执行上下文?

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

this绑定规则

默认绑定,this指向全局对象(严格模式,不允许使用)

隐式绑定,会把函数调用的this绑定到所在的上下文对象,对于隐式绑定,常常伴随着隐式丢失的问题,函数别名传入回调函数函数传入内置函数均会造成此问题

显式绑定,就是常见callapply方法,仍无法解决绑定丢失问题,但有个变种方法,叫做硬绑定

└── 硬绑定,函数bar中强制绑定(显示绑定)绑定foo在obj中执行

├── 包裹函数

├── 辅助函数(bind基本实现原理,下面有详细实现)

new绑定,过程如下(发生在构造函数调用时):

1、创建(或者说构造)一个新对象
2、这个新对象会被执行[[Porototype]]连接
3、这个新对象会被绑定到函数调用的this
4、如果函数没有返回其他的对象,那么new表达式的函数调用会自动返回新对象

规则优先级:默认绑定 < 隐式绑定 < 显式绑定 < new绑定

☆☆☆☆☆☆ 辅助函数与bind函数的实现 ☆☆☆☆☆☆

    function bind(fn, obj) {
        return function() {
            fn.apply(obj, arguments);
        }
    }

摘自MDN:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== "function") {
      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() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          // 关键代码,如果是new调用,就是使用新创建的this替换硬绑定的this,说明了new绑定优先级大于硬绑定
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}
更安全的this

bind辅助函数修改改变this的同时,可以使用Object.create(null),如.call/.apply(Object.create(null), ...argument),这样可以有效防止修改全局对象。

被忽略的this

null/undefined作为this的绑定对象传入callapply,使用的是默认绑定规则。

软绑定

上述对硬绑定的介绍,会强制把this强制绑定到指定的对象上,防止了函数调用应用默认绑定规则,但是造成的问题就是,不够灵活,使用不了隐式绑定和显式绑定来修改this,于此同时,软绑定便出现了。

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj) {
        var fn = this;
        // 捕获所有curried参数
        var curried = [].slice.call(arguments, 1);
        var bound = function() {
            return fn.apply(
                (!this || this === (window || global)) ? obj : this,
                curried.concat.apply(curried, arguments)
            );
        }
        bound.prototype = Object.create(fn.prototype);
        return bound;
    }
}

软绑定DEMO:

function foo() {
    console.log("name:" + this.name);
}
var obj = { name: "obj" };
var obj2 = { name: "obj2" };
var obj3 = { name: "obj3" };
var fooBj = foo.softBind(obj);
fooBj(); // name: obj
obj2.foo = foo.softBind(obj2);
obj2.foo(); // name: obj2
fooBj.call(obj3); // name: obj3
setTimeout(obj2.foo, 10); // name: obj
基本类型、内置对象

基本类型:string、number、boolean、null、undefined、object
内置对象(也是内置函数): String、Number、Boolean、Object、Function、Array、Date、RegExp、Error

日常开发,大家可能会有疑问,比如var a = "i am string"a变量,它可以使用a.length、a.charAt(0)等属性和方法。它本来是一个字符串,为什么会有类似对象的特性呢,这里会涉及到一个基本包装对象,可以怎么理解这样的一个概念性知识,一瞬间的引用类型,用完即毁,真正的引用类型会一直存在内存中,而基本包装对象只会存在一瞬间。

衍生方法:typeofinstanceofObject.prototype.toString.call

对象

对象的内容是由一些存储在特定命名位置的值组成的,叫做属性(指针),有两种访问方式,myObject["a"]myObject.a,对象属性同时也是无序的。

ES6可计算属性名:

const prefix = "a";
const myObject = {
    [prefix + "bar"]: "xxxx"
}

记住,函数永远不属于一个对象,只是一个引用,只是函数的this,会因为隐性绑定,在上下文做一层绑定而已。

复制对象、属性描述符

深复制,JSON.parse(JSON.stringify(obj))

浅复制,Object.assign(...),会遍历一个或多个源对象的所有可枚举的自由键到目标对象,源对象的一些特性(如writable)不会被复制

属性描述符,即writableconfiguableenumerablevalue
获得属性描述符,Object.getOwnPropertyDescriptor(myObject, "a")
设置属性描述符,Object.defineProperty(myObject, "a", {...})
configuable配置为false,不能使用delete关键字
enumerable控制属性是否可枚举

访问描述符,(Getter/Setter),可以改写默认的value

var myObject = {
    get a() {
        return 2;
    }
}
myObject.a // 2
Object.defineProperty(myObject, "b", {
    get: function() {
        return this.a * 2 // 指向当前对象
    }
});
myObject.b // 4
遍历对象属性的几种方法

for...in,遍历对象自身及原型上可枚举的属性

Object.keys(),遍历对象自身可枚举的属性

Object.getOwnPropertyNames,遍历对象自身的属性

Object.getOwnPropertySymbol,遍历对象自身Symbol类型属性

Reflect.ownkeys,遍历对象自身的属性(包含不可枚举属性,Symbol类型属性)

对象不可变性

如果你希望对象属性或是对象是不可改变,可以通过配置对象的属性描述符来实现,但是这种不可变是一种浅不可变,如果对象中属性的引用是对象、数组、函数,那么它们是可变的,实现方式有如下:

对象常量,writable:false | configuable:false

禁止扩展,Object.preventExtensions(obj)

密封,Object.seal(obj)调用禁止扩展,且不能重新配置,及删除属性,及configuable:false

冻结,Object.freeze(obj),在密封的基础上将writable:false

存在性

in操作符,检查属性是否存在对象里或是[[Prototype]]原型链上

Object.hasOwnProperty,只会检查对象

问题:myObject.hasOwnProperty()可能会报错,myObject可能是Object.create(null)生成不带[[Prototype]]原型链,而Object.hasOwnProperty是由[[Prototype]]委托,所以可以这样,Object.prototype.hasOwnProperty.call(myObject, "a")

有坑:

4 in [2,4,6] // false
4 in [2,2,6,8,0] // true

判断是否可枚举 myObject.propertyIsEnumerable("a")

@@iterator迭代器对象

for...of被访问的对象请求一个迭代器对象,然后通过.next()方法遍历所有返回来的值
数组内置有@@iterator,所以可以直接使用for...of
使用内置@@iterator遍历数组:

var myArray = [1,2,3];
var it = myArray[Symbol.iterator](); // 返回迭代器函数
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { done: true }

普通对象不含有@@iterator,无法使用for...of,可以进行改造,

var myObject = { a: 2, b: 3 };
Object.defineProperty(myObject, Symbol.iterator, {
    enumerable: false,
    writable: false,
    configuabale: false,
    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)
                };
            }
        }
    }
});
vat it = myObject[Symbol.iterator]();
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { done: true }

个人博客地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106580.html

相关文章

  • 你不知道javascript(上卷)后感(一)

    摘要:遮蔽效应作用域查找会在找到第一个匹配的标识符时停止,不会继续往上层作用域查找,这就会产生遮蔽效应。会发现每一次输出的都是为啥勒所有的回调函数回在循环结束后才会执行事件循环。 三剑客 编译,顾名思义,就是源代码执行前会经历的过程,分三个步骤, 分词/词法分析,将我们写的代码字符串分解成多个词法单元 解析/语法分析,将词法单元集合生成抽象语法树(AST) 代码生成,抽象语法树(AST)转...

    zhaofeihao 评论0 收藏0
  • 你不知道JavaScript上卷之作用域与闭包·读书笔记

    摘要:的分句会创建一个块作用域,其声明的变量仅在中有效。而闭包的神奇作用是阻止此事发生。依然持有对该作用域的引用,而这个引用就叫做闭包。当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。 date: 16.12.8 Thursday 第一章 作用域是什么 LHS:赋值操作的目标是谁? 比如: a = 2; RHS:谁是赋值操作的源头? 比如: conso...

    Raaabbit 评论0 收藏0
  • 十分钟快速了解《你不知道 JavaScript》(上卷

    摘要:最近刚刚看完了你不知道的上卷,对有了更进一步的了解。你不知道的上卷由两部分组成,第一部分是作用域和闭包,第二部分是和对象原型。附录词法这一章并没有说明机制,只是介绍了中的箭头函数引入的行为词法。第章混合对象类类理论类的机制类的继承混入。 最近刚刚看完了《你不知道的 JavaScript》上卷,对 JavaScript 有了更进一步的了解。 《你不知道的 JavaScript》上卷由两部...

    赵春朋 评论0 收藏0
  • 重读你不知道JS (上) 第一节五章

    摘要:词法作用域的查找规则是闭包的一部分。因此的确同闭包息息相关,即使本身并不会真的使用闭包。而上面的创建一个闭包,本质上这是将一个块转换成一个可以被关闭的作用域。结合块级作用域与闭包模块这个模式在中被称为模块。 你不知道的JS(上卷)笔记 你不知道的 JavaScript JavaScript 既是一门充满吸引力、简单易用的语言,又是一门具有许多复杂微妙技术的语言,即使是经验丰富的 Jav...

    worldligang 评论0 收藏0
  • 重读你不知道JS (上) 第一节四章

    摘要:如果提升改变了代码执行的顺序,会造成非常严重的破坏。声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。要注意避免重复声明,特别是当普通的声明和函数声明混合在一起的时候,否则会引起很多危险的问题 你不知道的JS(上卷)笔记 你不知道的 JavaScript JavaScript 既是一门充满吸引力、简单易用的语言,又是一门具有许多复杂微妙技术的语言,即使是经验丰富的 Ja...

    chanjarster 评论0 收藏0

发表评论

0条评论

Ali_

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<