文章目录
- 《深入理解现代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
区别 let const var 不能重复声明,会报SyntaxError错 const 定义常量,值不能修改的变量叫做常量,一定要赋初始值,因为不能修改。 可以重复声明 块级作用域 拥有 拥有 不拥有 会不会污染全局变量(挂载在window上) 不会(不是全局变量的属性) 不会 会(是全局变量的属性) 作用域和作用域链
作用域:一个代码段所在的区域,是静态的,在编写代码时就确定了。
作用:变量绑定在这个作用域内有效,隔离变量,不同作用域下同名变量不会有冲突。
作用域分类
- 全局作用域
- 函数作用域:函数的作用域在声明的时候就已经决定了,与调用位置无关
- 块级作用域
作用域链:多个作用域嵌套,就近选择,先在自己作用域找,然后去就近的作用域找。
提升和暂时性死区
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参数
arguments rest 类数组 数组 箭头函数的一个局部变量 箭头函数和非箭头函数都可以使用 包含所有实参,还会有一些附加属性(如callee属性) 只包含那些没有对应形参的实参 类数组格式与数组结构类似,拥有 length 属性,可以通过索引来访问或设置里面的元素,但是不能使用数组的方法。
类的新特性
class 学习笔记:https://blog.csdn.net/qq_41370833/article/details/135292235
class可以看作只是一个语法糖,只是让对象原型的写法更加清晰,更像面向对象编程的语言。class是构造函数的另外一种写法。
- class类声明按照let/const声明的方式被提升,类的特点与let/const相似。
语法特点
- 如果不显示提供构造函数,默认提供空构造函数。构造函数必须通过new和Reflect.construct调用
- 原型方法不可枚举,且JS引擎不会给他添加prototype属性和关联属性。
ES5中,所有函数都可能被用作构造函数,所以需要添加一个与对象关联的prototype属性。
- 类的所有方法都定义在类的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; } }
- 子类继承父类的静态方法/属性:子类构造器(Child)的隐式原型__proto__ 指向父类构造函数(Parent)
- 子类继承父类的方法:子类的显式原型对象(Child.prototype)的隐式原型__proto__指向父类的显式对象(Parent.prototype)
- 子类构造器里调用父类构造器,继承父类的属性:子类构造函数Child继承父类构造函数Parent的里的属性,使用super调用的。
// 1.构造器原型链:继承静态方法 Child.__proto__ === Parent; // true // 2.实例原型链:继承显示原型上的方法 Child.prototype.__proto__ === Parent.prototype; // true
两条隐式原型继承链
- Child -> Parent -> Function.prototype -> Object.prototype
- 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类型,按照对象属性的创建顺序
- 不会将类的名称添加到当前作用域中,赋值的结果是一个函数(类的构造函数)
- 类的声明的特点
- class类声明按照let/const声明的方式被提升,类的特点与let/const相似。
还没有评论,来说两句吧...