《深入理解现代JavaScrpt》阅读笔记-目前更新第五章

《深入理解现代JavaScrpt》阅读笔记-目前更新第五章

码农世界 2024-05-27 前端 85 次浏览 0个评论

文章目录

  • 《深入理解现代JavaScrpt》阅读笔记
    • 块级作用域let和const
      • 作用域和作用域链
      • 提升和暂时性死区
      • 函数
        • 逗号运算符
        • 函数的属性
        • arguments和rest参数
        • 类的新特性
          • 可计算方法名[]
          • 继承extends
          • 关键字super
          • new.target 函数的调用方式
          • 类声明与类表达式
          • 对象的新特性
            • 可计算属性名
            • 获取和设置对象的隐式原型
            • 对象方法的简写语法以及类之外的super
            • Symbol
            • 对象的新增方法
              • Object.assign(target, …sources) 复制多个对象
              • Object.is(比较对象1,比较对象2) 比较两个值是否为相同值
              • Object对象的遍历
              • ES2019新增`Object.fromEntries()`:把键值对列表转换为一个对象
              • Symbol.toPrimitive 强制类型转换制算法优先调用
              • 属性顺序 - 不建议依赖对象属性顺序

                《深入理解现代JavaScrpt》阅读笔记

                块级作用域let和const

                区别letconstvar
                不能重复声明,会报SyntaxError错const 定义常量,值不能修改的变量叫做常量,一定要赋初始值,因为不能修改。可以重复声明
                块级作用域拥有拥有不拥有
                会不会污染全局变量(挂载在window上)不会(不是全局变量的属性)不会会(是全局变量的属性)

                作用域和作用域链

                作用域:一个代码段所在的区域,是静态的,在编写代码时就确定了。

                作用:变量绑定在这个作用域内有效,隔离变量,不同作用域下同名变量不会有冲突。

                作用域分类

                1. 全局作用域
                2. 函数作用域:函数的作用域在声明的时候就已经决定了,与调用位置无关
                3. 块级作用域

                作用域链:多个作用域嵌套,就近选择,先在自己作用域找,然后去就近的作用域找。

                提升和暂时性死区

                var声明的变量会使声明被提升到顶部,let和const也存在变量提升,只是提升的方式不同。

                • var变量提升:变量的声明提升到顶部,值为undefined
                • let、const变量提升: 变量声明提升到顶部,只不过将该变量标记为尚未初始化
                  //原代码
                  function fn(){
                  	console.log(answer); //undefined
                  	var answer=42;
                  }
                  //变量提升
                  function fn(){
                  	var answer;//声明提前
                  	console.log(answer); 
                  	answer=42;
                  }
                  

                  let、const的暂时性死区

                  let 和 const存在暂时性死区,代码执行过程中的一段时间内,在此期间无法使用标识符,也不能引用外层作用域的变量。

                  let answer;
                  function fn(){
                  	//如果此时没有将变量变量提升到这里,answer应该取外层answer的值
                  	console.log(answer); //Uncaught ReferenceError: Cannot access 'answer' before initialization
                  	let answer=42;
                  }
                  //理解暂时性死区是暂时的与时间相关
                  function temporalExample(){
                  	const f = ()=>{
                  		console.log(value)//这里不会报错
                  	}
                  	let value = 42;
                  	f(); //调用时,value已经声明
                  }
                  

                  块和函数一样存在暂时性死区

                  function temporalExample(){
                  	let p = '外部声明';
                  	if(){//代码块 if while for for-in for-of ....
                  		console.log(p)//报错
                  		let p  = '内部声明' //块内的声明取得了p标识符的使用权
                  	}
                  }
                  

                  每次循环迭代都有自己的块级作用域,类似同一个函数的不同调用都有自己的局部变量一样

                  function loops(){
                  	for(let counter = 1;counter<=3;++counter){//每次循环都会创建一个counter变量,每次迭代都有自己的counter
                  	}
                  }
                  

                  函数

                  逗号运算符

                  逗号运算符是计算其左操作数,丢弃结果。然后计算其右操作时,并将该值最为结果。

                  function returnSecond(a,b){
                  	return a,b;
                  }
                  returnSecond(1,2) //返回2
                  

                  使用场景:逗号左边可以包含具有副作用的表达式(进行其他操作,但不需要返回值)

                  //一般函数
                  handles = handlers.map(function(handler){
                  	unregister(handler);//进行一些操作,不需要返回值
                  	return register(handler.iterm) ;
                  })
                  //箭头函数
                  handles = handlers.map(handler=>{
                  	unregister(handler);//进行一些操作,不需要返回值
                  	return register(handler.iterm) ;
                  })
                  //箭头函数与逗号运算符结合
                  handles = handlers.map(handler=>(unregister(handler),register(handler.iterm)))
                  

                  ES2017支持在形参和实参列表的末尾添加一个逗号

                  function example(a,b,){} //形参个数仍然是2
                  example(2,1,)
                  

                  函数的属性

                  参数的个数:函数名.length

                  函数名.length获取函数的形参个数,默认值不会增加函数的arity(形参的数量),还会阻止后续形参被计算在内。

                  function fn(a,b=32,c){}
                  console.log(fn.length)//1
                  

                  函数的name属性

                  //函数声明
                  function foo(){};  console.log(foo.name)//"foo"
                  //函数表达式
                  const f = function bar(){};  console.log(f.name) //"bar"
                  const fn = ()=>{}; console.log(fn.name) "fn"
                  //例外,将函数赋给现有对象的属性时,没有name属性,因为这种特定用法暴露了过多信息
                  const obj ={}; obj.foo=()=>{}; console.log(obj.foo.name)//""
                  

                  arguments和rest参数

                  argumentsrest
                  类数组数组
                  箭头函数的一个局部变量箭头函数和非箭头函数都可以使用
                  包含所有实参,还会有一些附加属性(如callee属性)只包含那些没有对应形参的实参

                  类数组格式与数组结构类似,拥有 length 属性,可以通过索引来访问或设置里面的元素,但是不能使用数组的方法。

                  类的新特性

                  class 学习笔记:https://blog.csdn.net/qq_41370833/article/details/135292235

                  class可以看作只是一个语法糖,只是让对象原型的写法更加清晰,更像面向对象编程的语言。class是构造函数的另外一种写法。

                  • class类声明按照let/const声明的方式被提升,类的特点与let/const相似。

                    语法特点

                    1. 如果不显示提供构造函数,默认提供空构造函数。构造函数必须通过new和Reflect.construct调用
                    2. 原型方法不可枚举,且JS引擎不会给他添加prototype属性和关联属性。

                      ES5中,所有函数都可能被用作构造函数,所以需要添加一个与对象关联的prototype属性。

                    3. 类的所有方法都定义在类的prototype属性上面。
                    class Color{
                    	//构造函数
                    	constructor(r=0,g=0,b=0){//三个数据属性(r,g,b)
                    		this.r = r;
                    		this.g = g;
                    		this.b = b;
                    	}
                    	//一个访问器属性(rgb)
                    	get rgb(){}
                    	set rgb(value){}
                    	//原型方法
                    	toString()
                    	//静态方法:放在Color类上,不放在Color.prototype上
                    	static fromCss(css){}
                    }
                    const c = new Color();
                    console.log(typeof c.toString.prototype);//"undefined"
                    

                    可计算方法名[]

                    中括号属性访问器语法[]:类似属性访问器的用法

                    使用场景:创建的方法是在运行时确定名称的,而不是在代码中给定的。

                    • 可放入任意表达式
                    • 表达式在类定义时计算
                    • 如果表达式的结果不是Symbol或字符串,将会被转化成一个字符串。对象的key只能是字符串或Symbol类型,Symbol类型可以防止字符串格式key相同的问题
                    • 结果被用作方法名
                      let name = "foo" + Math.floor(Math.random()*1000)
                      class SomeClass{
                      	[name](){
                      		console.log("1")
                      	}
                      }
                      const s = new SomeClass()
                      s[name]()
                      

                      访问类属性的两种方式

                      点属性访问器:object.property property会被解析成“property”,所以点运算符后面不能跟变量。点运算符后面的property为有效标识符时,点运算符才能正常运行。

                      方括号属性访问:object[‘property’] 方括号属性里的表达式会被自计算,所以里面可以使用变量

                      继承extends

                      原型链是隐式原型链,对象隐式原型的值=对应构造函数的显式原型的值

                      继承对于JavaScript来说就是子类继承父类拥有的方法、属性、静态方法等。

                      class Parent{
                          constructor(name){
                              this.name = name;
                          }
                      }
                      class Child extends Parent{
                          constructor(name, age){
                              super(name);
                              this.age = age;
                          }
                      }
                      
                      1. 子类继承父类的静态方法/属性:子类构造器(Child)的隐式原型__proto__ 指向父类构造函数(Parent)
                      2. 子类继承父类的方法:子类的显式原型对象(Child.prototype)的隐式原型__proto__指向父类的显式对象(Parent.prototype)
                      3. 子类构造器里调用父类构造器,继承父类的属性:子类构造函数Child继承父类构造函数Parent的里的属性,使用super调用的。
                      // 1.构造器原型链:继承静态方法
                      Child.__proto__ === Parent; // true
                      // 2.实例原型链:继承显示原型上的方法
                      Child.prototype.__proto__ === Parent.prototype; // true
                      

                      两条隐式原型继承链

                      1. Child -> Parent -> Function.prototype -> Object.prototype
                      2. Child.prototype -> Parent.prototype -> Object.prototype

                      关键字super

                      关键字super的两种使用场景

                      • super():在子类构造器中,把super当作一个函数来调用,创建对象并让其执行超类的初始化流程
                      • super.property 和 super.method(): 通过super访问超类的原型属性和方法

                        HomeObject字段

                        定义在类里的方法使用[[HomeObject]]字段说明该方法定义在什么对象上,但是赋值给属性的传统函数形式则没有该字段。

                        super方法的调用流程

                        1.在b.test内会运行super.test(),也就是说该super对应b.test的[[HomeObject]]的原型。

                        2.b.test的[[HomeObject]]是B.prototype,B.prototype的隐式原型是A.prototype

                        3.super指的是A.prototype,所以在A.prototype寻找test方法

                        class A {
                        	test(){}
                        }
                        Class B extends A{
                        	test(){  return "B" + super.test()}
                        }
                        const b = new B();
                        b.test();
                        

                        new.target 函数的调用方式

                        new.target可以知道函数的调用方式,适用场景:定义抽象类、最终类等

                        • 函数被直接调用,new.target的值为undefiend
                        • 当前函数是new操作符的直接目标,new.target指向的是当前函数
                        • 当前函数通过super调用的,new.target指向最终调用的子类
                          function test1(){
                          	console.log(new.target);
                          }
                          test1();//undefined
                          class Base{
                          	constructor(){
                          		console.log(new.target.name);
                          	}
                          }
                          new Base();//"Base"
                          class Sub extends Base{
                          	constructor(){
                          		super();
                          	}
                          }
                          class Super extends Sub{
                          	constructor(){
                          		super();
                          	}
                          }  
                          new Super();//super
                          

                          类声明与类表达式

                          • 类的声明的特点
                            • 将类的名称添加到当前作用域中
                            • 结尾的大括号不需要加分号
                            • 类似let和const的作用域提升规则
                            • 类的表达式的特点
                              • 不会将类的名称添加到当前作用域中,赋值的结果是一个函数(类的构造函数)
                                //类的声明
                                class Class1{}
                                //类的表达式
                                let Color = class{};
                                //类的匿名表达式
                                let C = class Color2{};
                                conselo.log(Color2); //Color2 is not defined
                                console.log(typeof C)//"function"
                                

                                对象的新特性

                                可计算属性名

                                ES5中,如果想创建一个属性名为变量的对象,必须要创建这个对象,然后为该对象添加属性。

                                let age = "age";
                                let obj = {};
                                obj[age ] = 18
                                

                                新增可计算属性名[表达式]

                                let age = "age";
                                let obj = {
                                	[age]:18
                                };
                                

                                获取和设置对象的隐式原型

                                Object.setPrototypeOf(对象a,对象b):设置对象a的__proto__指向对象b

                                Object.getPrototypeOf(对象):获取指定对象的__proto__指向的值,没有返回null

                                由于实例对象的隐式原型 = 构造函数的显式原型,这里也可以说获取到该对象构造函数的显式原型。

                                实例对象的隐式原型 = 构造函数的显式原型,所以一般说对象的原型指的都是对象的隐式原型。

                                浏览器环境中的__proto__属性

                                __proto__是非标准扩展,在非浏览器环境的JavaScript引擎中,该方法不存在,所以不要使用,可以选择Object.setPrototypeO方法代替。

                                对象方法的简写语法以及类之外的super

                                const obj ={
                                	name:"joe",
                                	say(){
                                		console.log(this.name);
                                	}
                                };
                                obj.say()
                                
                                通过function进行属性初始化通过方法简写语法进行属性初始化
                                可以用作构造函数方法没有指向原型对象的prototype属性,所以不能用作构造函数
                                没有指向定义对象(宿主对象)的链接[[HomeObject]]建立从函数到其宿主对象的链接

                                定义在对象里的方法使用[[HomeObject]]字段说明该方法定义在什么对象上,但是赋值给属性的传统函数形式则没有该字段。(同类方法一致)

                                建立对象方法到其宿主对象链接的目的是在方法中使用super关键字

                                1.获取当前函数的[[HomeObject]]值,比如obj

                                2.获取对象当前的隐式原型,比如obj.proto

                                3.查找并使用原型上的对应属性与方法

                                const obj={
                                	toString(){
                                		return super.toString().toUpperCase();
                                	}
                                }
                                Object.setPrototypeOf(obj,{
                                	toString(){
                                		return "a"
                                	}
                                })
                                console.log(obj.toString()); //A
                                

                                Symbol

                                Symbol是原始数据类型,用于创建一个独一无二的值

                                使用场景:对象的属性名/属性键类型:Symbol | string

                                使用:调用Symbol方法创建一个全新且唯一的Symbol值。调用symbol.description可以查看Symbol的描述信息,只是单纯的描述不包含其他信息,不同的Symbol可以有相同的描述。

                                const mySymbol = Symbol("添加该Symbol的描述信息");
                                const obj = {
                                	[mySymbol] :6
                                }
                                

                                对象的新增方法

                                Object.assign(target, …sources) 复制多个对象

                                语法:Object.assign(target, ...sources)

                                说明

                                1.将一个或多个源对象复制到目标对象,返回修改后的目标对象。

                                2.如果目标对象中存在相同的key则后面的覆盖前面的。

                                3.只拷贝源对象可枚举的自有属性

                                4.该方法会跳过值为null或undefined的参数

                                4.如果赋值期间出错,例如如果属性不可写,则会抛出 TypeError;如果在抛出异常之前已经添加了一些属性,则这些属性会被保留,而 target 对象也会被修改。

                                5.浅拷贝,基本类型直接赋值,引用类型复制地址。

                                const obj1 = { a: 0, b: { c: 0 } };
                                const obj2 = Object.assign({}, obj1);
                                console.log(obj2); // { a: 0, b: { c: 0 } }
                                obj1.a = 1;
                                console.log(obj1); // { a: 1, b: { c: 0 } }
                                console.log(obj2); // { a: 0, b: { c: 0 } }
                                obj2.a = 2;
                                console.log(obj1); // { a: 1, b: { c: 0 } }
                                console.log(obj2); // { a: 2, b: { c: 0 } }
                                obj2.b.c = 3;
                                console.log(obj1); // { a: 1, b: { c: 3 } }
                                console.log(obj2); // { a: 2, b: { c: 3 } }
                                
                                Object.is(比较对象1,比较对象2) 比较两个值是否为相同值

                                Object.is的机制类似====,感觉Object.is更合理一点?

                                区别在以下两个地方

                                console.log(Object.is(+0,-0)); //false
                                console.log(+0===-0); //true
                                console.log(Object.is(NaN,NaN)); //true
                                NaN === NaN; //false
                                
                                Object对象的遍历
                                方法描述遍历不可枚举属性遍历继承属性遍历Symbol属性
                                Object.keys(obj)
                                Object.values(obj)
                                Object.entries(obj)
                                返回给定对象的自身可枚举属性组成的数组×××
                                for-in遍历对象的属性,包括原型链上的可枚举属性××
                                Object.getOwnPropertyNames()返回对象的自身属性的属性名,包括不可枚举属性××
                                Object.getOwnPropertySymbols()只获取对象的Symbol类型的属性键××
                                Object.getOwnPropertyDescriptors()获取对象自身的全部属性
                                使用场景:将该方法返回的对象通过Object.definePropertis()传递给另一个对象
                                ×
                                Reflect.ownKeys(obj)获取对象的属性键,包含不可迭代的key和symbol类型的key×

                                Object.keys() = for in + Object.hasOwnProperty()

                                Reflect.ownKeys() = Object.getOwnPropertyNames() + Object.getOwnPropertySymbols()

                                let data = { name: 'tim', age: 18, [Symbol()]: 123 };
                                console.log(Object.keys(data)); // [ 'name', 'age' ]
                                console.log(Object.values(data)); // [ 'tim', 18 ]
                                console.log(Object.entries(data)); // [ [ 'name', 'tim' ], [ 'age', 18 ] ]
                                

                                对象遍历的次序规则

                                1.遍历所有数值键,按照数值升序排列。

                                2.遍历所有字符串键,按照加入时间升序排列。

                                3.遍历所有 Symbol 键,按照加入时间升序排列。

                                Reflect.ownKeys({ [Symbol()]:0, b: 0, 10: 0, 2: 0, a:0 })
                                // ['2', '10', 'b', 'a', Symbol()]
                                
                                ES2019新增Object.fromEntries():把键值对列表转换为一个对象

                                语法:Object.fromEntries(iterable);

                                参数:iterable,类似Array 、 Map 或者其它实现了可迭代协议的可迭代对象。

                                说明:Object.fromEntries() 执行与 Object.entries 互逆的操作。

                                Map.prototype.entries返回的列表恰好是Object.fromEntries的参数

                                const obj = Object.fromEntries([
                                	["a",1],
                                	["b",2],
                                	["C",3]
                                ]);
                                console.log(obj); // {a:1,b:2,c:3}
                                
                                Symbol.toPrimitive 强制类型转换制算法优先调用

                                [Symbol.toPrimitive](倾向转换的类型) :将对象转换为原始值的内置方法,强制类型转换制算法优先调用对象该属性的方法

                                参数类型:number数字类型,string字符串类型,default没有倾向

                                返回值:该方法必须返回一个原始值,否则会报错。返回值不一定要与暗示值匹配,暗示只是暗示。

                                为什么新增该方法?

                                在对象中将数据转换为原始类型的操作中,传统的方式带有一定的暗示作用。

                                toString():倾向与使用字符串的场景

                                valueOf():用于其他场景与数字或者没有倾向的场景 --无法区分倾向数字或没有倾向

                                有关于类型的隐式转换

                                使用加法运算符,意味没有倾向

                                使用减法运算符,倾向于数字类型

                                String(obj)等,倾向于字符串类型

                                const object1 = {
                                  [Symbol.toPrimitive](hint) {
                                    if (hint === 'number') {
                                      return 42;
                                    }
                                    return null;
                                  },
                                };
                                

                                如果对象没有[Symbol.toPrimitive]属性

                                没有 @@toPrimitive 属性的对象通过以不同的顺序调用 valueOf() 和 toString() 方法将其转换为原始值,

                                [Symbol.toPrimitive](hint){
                                 let methods = hint === "string" ? ["toString","valueOf"] : ["valueOf","toString"]
                                for (const methodName if nmethods){
                                	const method  = this[methodName ]
                                	if(typeof method === "function"){
                                		const result = method.call(this);
                                		if(result === null || typeof result !=="object")
                                			return result;
                                	}
                                }
                                	throw new TypeError();
                                }
                                

                                将对象转换为原始值

                                无偏向:[@@toPrimitive]("default") → valueOf() → toString()

                                偏向转换为数字:[@@toPrimitive]("number") → valueOf() → toString()

                                偏向转换为字符串:[@@toPrimitive]("string") → toString() → valueOf()

                                属性顺序 - 不建议依赖对象属性顺序

                                属性范围:自身属性

                                属性顺序的应用范围:ES15-19没有应用到for-in、Object.keys、Object.values、Object.entries,ES20中for-in已经增加属性顺序。

                                1.数字字符串类型的键,数字顺序排序

                                2.其他字符串类型的键,按照对象属性的创建顺序

                                3.Symbol类型,按照对象属性的创建顺序

转载请注明来自码农世界,本文标题:《《深入理解现代JavaScrpt》阅读笔记-目前更新第五章》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,85人围观)参与讨论

还没有评论,来说两句吧...

Top