-
类型转换中
Number(undefined)
结果为NaN
,Number(null)
结果为 0 -
**
作为求幂运算符也可作为开方使用,2 ** 3
等于 8,8 ** (1/2)
等于 3 -
大多运算符执行后都会返回当前值如
+5
,-3
,赋值运算符=
也一样,如console.log(x = 5)
,x 被赋值为5,并打印出返回的 x 的值,在一些判断中常看到,如:where(x = y - 1){ ... }
还有链式赋值中也经常用到,如:
let a, b, c; a = b = c = 2 + 2;
但不建议在代码中这么写,可读性差
-
逗号运算符
,
是比赋值运算符优先级还要低的一种运算符,const a = (1 + 2, 3 + 4);
a 的结果为 3 ,1 + 2 和 3 + 4 都会执行,但是只有最后的语句的结果会被返回,1 + 2 运行了但结果被丢弃了。这样的运算符在把几个行为放在一行上来进行复杂的运算时非常有用,如:for (a = 1, b = 3, c = a * b; a < 10; a++) { ... }
-
空值合并运算符
??
和逻辑运算符||
类似,但是??
只判断第一个值是否为null
或undefined
,如果是第一值个是undefined
或null
,结果才为第二个值const a = null; const b = undefined; const c = 0; const d = ''; const v = 'value'; console.log(a ?? v , a || v); // 结果为 'value' 'value' console.log(c ?? v , c || v); // 结果为 0 'value' console.log(d ?? v , d || v); // 结果为 '' 'value'
-
对象有
.
和[]
两种查询和更改属性的方式,如obj.key
、obj[key]
,当需要用到复杂属性(变量、表达式、多词属性等)时大多用[]
如:const key1 = 'key1'; const key2 = 'key2'; const key3 = 'key3 key3'; const obj = { key1: 'value1', key2: 'value2', 'key3 key3': 'value3', [key1+key2]: 'value12', }; console.log(obj.key1,obj.key2); // 'value1' 'value2' console.log(obj[key1+key2],obj[key3]); // 'value12' 'value3'
-
可选链
?.
是一种访问嵌套对象属性的安全的方式。如获取一个user对象的 address 中的 street,必须对 user 和 address 做是否存在的判断,否则当 user 或 address 为 null / undefined 时都将触发TypeError。可选链?.
如果前面的部分是undefined
或者null
,它会停止运算并返回const getUserStreet1 = user => { if(user && user.address) { return user.address.street; } } const getUserStreet2 = user => { return user?.address?.street; }
也可以对函数、数组使用可选链
?.()
、?.[]
const print1 = undefined; const print2 = x => x; console.log(print1?.('test')); // undefined console.log(print2?.('test')); // test const getArrValue = (arr, index) => arr?.[index]; console.log(getArrValue(undefined, 1)); // undefined console.log(getArrValue([1,2], 1)); // 2
-
Symbol([desc])
函数可以创建一个唯一的 symbol类型的值,相同的描述文字也将创建出不同的值const symbol1 = Symbol('test'); const symbol2 = Symbol('test'); console.log(symbol1 === symbol2); // false
可以用于对象的私有属性,不对外部开放。如果希望同名描述下的 Symbol值相等可以使用Symbol全局注册表
Symbol.for(key)
如果同样的key将返回之前这个值,否则将创建一个新的值返回const symbol1 = Symbol.for('test'); const symbol2 = Symbol.for('test'); console.log(symbol1 === symbol2); // true
js 中有许多系统 Symbol,如
Symbol.iterator
、Symbol.toPrimitive
等,可以使用Symbol.toPrimitive
(须返回一个原始值) 来设置对象原始值的转换,优先级高于toString()
、valueOf()
const obj = {}; // hint = "string"、"number" 和 "default" 中的一个 obj[Symbol.toPrimitive] = function(hint) { // 必须返回一个原始值(string、number、boolean等) }
hint
参数为 "string"、"number" 和 "default" 中的一个,当进行数学计算、比较或显示进行Number()
转换时,hint
为 "number";进行alert()
打印,对象属性赋值等明显字符串操作时hint
为 "string";当运算符“不确定”期望值的类型时hint
为 "default",如二元+
可以用于数字,也可以用于字符就不能确定结果const obj = {}; obj[Symbol.toPrimitive] = function(hint) { return hint === "number" ? 8 : hint; } console.log(+obj, Number(obj)); // 8 8 const test = { [obj]: 'test', } console.log(String(obj), Object.keys(test)); // 'string' ['string'] console.log(obj + 5); // 'default5'
如果要满足
a == 1 && a == 2 && a == 3
或+a === 1 && +a === 2 && +a === 3
可以用到类型转换(() => { const a = { v: 0 }; a[Symbol.toPrimitive] = function(hint) { this.v += 1; return hint === "number" ? this.v : hint; } console.log(+a === 1 && +a === 2 && +a === 3); // true console.log(+a === 1); // false,因为对象 a 中的 v 值每次在递增 })();
求解sum(1)(2)(3)(4)...结果为1+2+3+4+...
const sum = arg => { const result = arg2 => sum(arg+arg2); result[Symbol.toPrimitive] = () => arg; return result; } // 使用了闭包、递归、Symbol.toPrimitive console.log(+sum(1)(2)(3)(4)); // 10 console.log(+sum(1)(2)(3)(4)(5) === 15); // true
Symbol.iterator
要求这个方法必须返回一个迭代器(一个有next()
方法的对象),next()
方法返回的结果的格式必须是{done: Boolean, value: any}
,当done=true
时,表示迭代结束,否则value
是下一个值。const obj = {}; obj[Symbol.iterator] = function() { // 必须返回一个返回一个迭代器 return { next:() => { ... // 返回的数据格式必须是 `{done: Boolean, value: any}` } } }
比如迭代
const range = { from: 1, to: 5 };
const range = { from: 1, to: 5 }; range[Symbol.iterator] = function() { let indexItem = this.from; return { next: () => { if(indexItem <= this.to) { return {done: false, value: indexItem++} }else{ return {done: true} } } } } for(let item of range) { console.log(item); // 1 2 3 4 5 }
或者用 generator 改写如下:
const range = { from: 1, to: 5 }; range[Symbol.iterator] = function* () { let indexItem = this.from; for(let indexItem = this.from; indexItem <= this.to; indexItem++){ yield indexItem; } } for(let item of range) { console.log(item); // 1 2 3 4 5 }
可以使用
Object.prototype.toString
或者instanceof获取类型,Symbol.toStringTag
属性可以自定义对象toString方法获取得类型console.log(Object.prototype.toString.call([])); // [object Array] const user = { [Symbol.toStringTag]: "User", } console.log(Object.prototype.toString.call(user)); // [object User] console.log(window[Symbol.toStringTag]); // Window
-
调用数字的方法时除了可以使用包装器(如:
Number(3).toFixed(1)
)外还可以使用..
直接调用,如3..toFixed(1)
,3..toString(2)
,js 语法隐含了第一个点之后的部分为小数部分。如果再放一个点,那么 js 就知道小数部分为空,所以当前面的数字本身就是小数时就不能使用..
,如3.5..toFixed(2)
会抛出错误,已经有小数点时可直接调用数字的方法3.5.toFixed(2)
,也可以使用括号(3.5).toFixed(2)、(3).toFixed(2)
-
Array.from()
可以将一个可迭代对象(包含Symbol.iterator
属性)或一个类数组对象(有索引和length
属性)转换为正在的数组const arrayLike = { 0: "Hello", 1: "World", length: 2 }; const arr1 = Array.from(arrayLike); console.log(arr1); // ['Hello', 'World'] const iteratorObj = { from: 1, to: 5 }; iteratorObj[Symbol.iterator] = function() { let indexItem = this.from; return { next: () => { if(indexItem <= this.to) { return {done: false, value: indexItem++} }else{ return {done: true} } } } } const arr2 = Array.from(iteratorObj); console.log(arr2); // [1, 2, 3, 4, 5]
-
map
是类似对象的可迭代数据结构,它的key
可以是任意值,获取和修改值通过get()、set()
方法,通过对象的entries
数据格式可以和对象相互转换const obj = { key1: 'a', key2: 'b', }; const objEntries = Object.entries(obj); const map = new Map(objEntries); map.set('key3','c'); const mapEntries = map.entries(); const resObj = Object.fromEntries(mapEntries); console.log(resObj); // {key1: 'a', key2: 'b', key3: 'c'}
-
set
是一堆 “值的集合”(没有键),也是可迭代对象,它的每一个值只能出现一次。通过add()、delete()
方法来操作集合,通过has()
方法来判断值是否存在const set = new Set(); const user1 = {name: 'zhangsan', age: 18}; const user2 = {name: 'lisi', age: 28}; const user3 = {name: 'wangwu', age: 38}; set.add(user1); set.add(user1); set.add(user2); set.add(user2); set.add(user3); set.add(user3); console.log(set.size); // 3 console.log(set.has(user1)); // true console.log(set.has({name: 'zhangsan', age: 18})); // false
-
使用
setTimeout
的嵌套可以实现setInterval
,更可控每次执行的频率,比如跟随系统访问量降低轮询请求的频率等(() => { let mockVisitNum = 0; const request = () => console.log('mock request'); let delay = 1000; const loopReq = () => { setTimeout(function fun() { request(); const nextDelay = delay + 500 * Math.trunc(mockVisitNum/10); console.log(`mockVisitNum:${mockVisitNum},delay:${nextDelay}`); setTimeout(fun, nextDelay); }, delay); } loopReq(); setInterval(() => { mockVisitNum += 1 }, 500); })()
-
bind
、apply
、call
都可以改变函数中this的指向bind(context, arg1, arg2…)
: 调用后改变了函数内部this的指向为context,并生成新的函数,新的函数并不会有原来函数的属性call(context, arg1, arg2…)
:调用后改变了函数内部this的指向为context,并立即执行函数apply(context, args)
:和call
的作用一样,区别在于传参的不同,apply
接受的第二个参数是类数组参数,而call
后面的参数是以逗号隔开的
例:实现
lodash
里面类似partial(func, [partials])
的功能,这个方法类似bind
,但它不会绑定this
,如下:const greet = function(greeting, name) { return greeting + ' ' + name; }; const sayHelloTo = _.partial(greet, 'hello'); sayHelloTo('fred'); // => 'hello fred'
使用箭头函数和
bind
的可以很容易实现const partial = (fn, ...args) => fn.bind(null,...args);
,需要注意箭头函数没有this
的问题,如下:const user1 = { name: 'userTest', print: function() { console.log(this.name); } } const user2 = { name: 'userTest', print: () => { console.log(this.name); } } user1.print(); // 'userTest' user2.print(); // undefined,箭头函数中的this被指向执行时的全局上下文了(globalThis)
但被操作函数是对象里的函数时,
const partial = (fn, ...args) => fn.bind(null,...args);
这种方法会导致内部的this
丢失const partial = (fn, ...args) => fn.bind(null,...args); const user = { name: 'userTest', print: function(job, age) { console.log(job, this.name,age); } } user.print = partial(user.print, 'teacher'); user.print('18'); // teacher undefined 18,this.name获取失败
所以实现的考虑函数可能调用this的情况,而这里不能直接将
null
改为this
,此时的this
是全局对象,而一但绑定函数fn内部的this
将全部被持久化到全局对象,而不是执行时的上下文。可以返回一个具有运行时上下文的函数(非箭头函数),在调用时分配内部this
到执行的上下文就能保证内部this
的正确性const partial = (fn, ...args) => { // 本身返回了一个function,所以内部的返回值需要立即执行fn return function(...fnArgs) { // 此出的this根据调用时决定 return fn.call(this, ...args, ...fnArgs); } } const greet = function(greeting, name) { return greeting + ' ' + name; }; const sayHelloTo = partial(greet, 'hello'); sayHelloTo('fred'); // 'hello fred' function print(job, age) { console.log(job, this.name, age); } const teachers = [{name: 't1', print: partial(print, 'teacher')}]; const students = [{name: 's1', print: partial(print, 'student')}]; teachers[0].print('38'); // teacher t1 38 students[0].print('18'); // student s1 18
-
对象有两种属性,普通的数据属性和访问器属性(
getter/setter
),访问器属性的本质上是用于获取和设置值的函数(可以对要设置或获取的属性进行拦截、过滤、加工等操作),但从外部代码来看就像常规属性const user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { [this.name, this.surname] = value.split(' '); }, }; console.log(user.fullName); // John Smith user.fullName = 'test fullName'; console.log(user.fullName); // test fullName
-
对象中操作原型的方法有
Object.getPrototypeOf(obj)
返回对象obj
的[[Prototype]]
。Object.setPrototypeOf(obj, proto)
将对象obj
的[[Prototype]]
设置为proto
。Object.create(proto, [descriptors])
利用给定的proto
作为[[Prototype]]
和可选的属性描述来创建一个空对象。
应该使用上述方法来替代
obj.__proto__
,__proto__
本身只是作为[[Prototype]]
的访问器属性,它会 set/get[[Prototype]]
。正是这个原因,如果有一个对象将要实现接受所有的属性时会发现__proto__
作为字符串时并不能正确的赋值到对象中,因为__proto__
被作为对象的原型对象访问器属性只允许为null
或一个对象const obj = {}; obj.__proto__ = 1234; console.log(obj.__proto__); // 一个原型对象 console.log(obj.toString); // [object Object]
如果要解决这个问题,可以使用
Object.create(null)
创建了一个空对象,这个对象没有原型([[Prototype]]
是null
),这个对象非常的干净比{}
还简单,同样也有他的缺点,他就没有了对象本身的一些像toString
之类的方法,此时__proto__
紧紧作为一个对象的属性而不能再操作对象的原型了,需要用getPrototypeOf(obj)、 setPrototypeOf(obj, proto)
才能操作。const obj = Object.create(null); obj.__proto__ = 1234; console.log(obj.__proto__); // 1234 console.log(obj.toString); // undefined
-
class
关键字是构造函数的变体,但不完全是语法糖,会有一些额外的标识等区别class User { constructor(name){ this.name = name; } getName(){ return this.name; } } const user = new User('zhangsan'); console.log(user.getName()); // zhangsan
这段用class编写的代码相当于如下代码
function User(name) { this.name = name; } User.prototype = { constructor:User, getName() { return this.name; } }
但是在他们的
[[Prototype]]
可以看到一些差别____________________
类方法不可枚举。 类定义将
"prototype"
中的所有方法的enumerable
标志设置为false
。 -
js中有 常见的几种继承方式,对于
class
类也有继承,通过extends
关键字实现,原理上差不多,都是通过原型链的方式实现class Animal { constructor(name) { this.name = name; } getName() { return this.name; } } class Rabbit extends Animal { constructor(name, age) { super(name); this.age = age; } getInfo() { return { name: super.getName(), age: this.age, } } } const rabbit = new Rabbit('xiaobai', 3); console.log(rabbit.getInfo()); // {name: "xiaobai", age: 3}
在子类的 constructor 函数中必须在 this 对象属性赋值(如:this.age = age)前调用
super
class Animal { name = 'animal'; constructor() { console.log(this.name); } } class Rabbit extends Animal { name = 'rabbit'; } new Animal(); // animal new Rabbit(); // animal
对于基类 Animal 的属性初始化发生在构造函数调用前,所以构造函数调用的时候能拿到 this.name,否则获取到的 this.name 为 undefined,而在子类 Rabbit 的属性初始化中,会首先初始化父类 Animal 的属性,然后调用其(父类)构造函数(所以 Rabbit 父类构造函数中打印的 this.name 为 “animal”),最后才初始化自己的属性;所以按照这个执行的步骤,需要在 this 对象属性赋值前调用
super
。在类的多层级继承中class A { name = 'A'; speak() { console.log(this.name); console.log('A speak'); } } class B extends A { name = 'B' speak() { super.speak(); console.log('B speak'); } } class C extends B { name = 'C' speak() { super.speak(); console.log('C speak'); } } (new C()).speak();
执行后得到如下结果:
const a = { name: 'A', speak() { console.log(this.name); console.log('A speak'); } } const b = { name: 'B', speak() { a.speak(); console.log('B speak'); } } const c = { name: 'C', speak() { b.speak(); console.log('C speak'); } } c.speak();
只要 c.speak() 调用时打印出的结果和
extends
执行的结果相同即可,同时在a、b、c的方法调用里面不能出现其他的对象(如在b的speak中出现a.speak()),可以通过bind
、call
、apply
的方式实现,添加原型和修改this指向后的代码如下:const a = { name: 'A', speak() { console.log(this.name); console.log('A speak'); } } const b = { name: 'B', speak() { Object.getPrototypeOf(this).speak.call(this); console.log('B speak'); } } const c = { name: 'C', speak() { Object.getPrototypeOf(this).speak.call(this); console.log('C speak'); } } Object.setPrototypeOf(c, b); // c 的原型是 b Object.setPrototypeOf(b, a); // b 的原型是 a c.speak();
执行发现上述代码出现了死循环,但是“逻辑上并没有问题”;实际上在对象 c 的 speak 中 Object.getPrototypeOf(this).speak.call(this) 将自身传递给了自己的基类,所以在基类 b 的 speak 函数中 中的 this 其实是 c,所以在 b 对象 的 speak 函数中相当于是调用了Object.getPrototypeOf(c).speak.call(c),和 c 对象中的 speak 函数形成了循环,而 b 对象中的 speak 函数想做的其实是调用它基类的speak函数,并将自己它获取到的 this 传递过去。显示的在每个speak函数中增加一个记录自己环境的变量就可以解决(本应该是this,但是通过call、bind、apply会改变this)
const a = { name: 'A', speak() { const environmentRecord = a; console.log(this.name); console.log('A speak'); } } const b = { name: 'B', speak() { const environmentRecord = b; Object.getPrototypeOf(environmentRecord).speak.call(this); console.log('B speak'); } } const c = { name: 'C', speak() { const environmentRecord = c; Object.getPrototypeOf(environmentRecord).speak.call(this); console.log('C speak'); } } Object.setPrototypeOf(c, b); // c 的原型是 b Object.setPrototypeOf(b, a); // b 的原型是 a c.speak();
上述代码就实现了
super
的功能,但是每个对象的函数里面有一段记录自身环境的代码,为了提供解决方法,JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]
。我们实现的 environmentRecord 和此类似,而调用super.speak()时相当于是 Object.getPrototypeOf(environmentRecord).speak.call(this)。详细参考ECMAScript® 2015 Language Specification中的[[HomeObject]]
-
js中的类存在静态属性和静态方法,它们不属于任何对象而是类的本身
class A { constructor(name) { this.name = name; } static create(name) { return new this(name); } getName() { return this.name; } } const a1 = new A('a1'); const a2 = A.create('a2'); console.log(a1.getName(), a2.getName()); // "a1" "a2"
不仅常规属性和方法可以继承,静态属性和方法也可以继承
class A { constructor(name) { this.name = name; } static create(name) { return new this(name); } static type = 'static property'; getName() { return this.name; } } class B extends A {} const b1 = new B('b1'); const b2 = B.create('b2'); console.log(B.type, b1.getName(), b2.getName()); // "static property" "b1" "b2"
在对象创建的过程中,普通的方法被创建在函数的
prototype
属性中,当通过new
创建对象时,对象的原型等于函数的prototype
属性,在继承过程当中子类的prototype
属性对象的原型等于基类的prototype
属性,实现了普通函数的继承;子类和基类本身是函数也是对象,所以静态函数和静态属性就挂载在对象中,继承时子类的原型等于基类,实现了静态属性和静态方法的继承class A { constructor(name) { this.name = name; } static create(name) { return new this(name); } static type = 'static property'; getName() { return this.name; } } const a = new A('a'); console.log(Object.getPrototypeOf(a) === A.prototype); // true class B extends A {} console.log(Object.getPrototypeOf(B.prototype) === A.prototype); // true console.log(Object.getPrototypeOf(B) === A); // true
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!