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

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

块级作用域let和const

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

作用域和作用域链

作用域:一个代码段所在的区域,是静态的,在编写代码时就确定了
作用:变量绑定在这个作用域内有效,隔离变量,不同作用域下同名变量不会有冲突。

作用域分类

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

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

提升和暂时性死区

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

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

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

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. 如果不显示提供构造函数,默认提供空构造函数。构造函数必须通过newReflect.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.该方法会跳过值为nullundefined的参数
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,类似ArrayMap 或者其它实现了可迭代协议的可迭代对象。
说明: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-inObject.keysObject.valuesObject.entriesES20for-in已经增加属性顺序。
1.数字字符串类型的键,数字顺序排序
2.其他字符串类型的键,按照对象属性的创建顺序
3.Symbol类型,按照对象属性的创建顺序

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值