跳到主要内容

JS | 你知道的JS

修改一下,如何使下列等式成立? 🤔

function usr(name, age){
this.name = name
this.age = age
}

console.log(new usr('李4', 20) + new usr('赵2', 40))
// new usr('李4', 20) 李4赵2

console.log(new usr('赵2', 40) / new usr('李4', 20))
// 2

原始值 toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数

在 Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个

所以这个问题可以改成

function usr(name, age){
this.name = name
this.age = age
this[Symbol.toPrimitive] = function(hint) {
switch (hint) {
case 'number':
return this.age
default:
return this.name
}
}
}

在看个例子, 如何使该等式成立

var a;
// 修改一下
if(a === 1 && a === 2 & a === 3){
// 需要执行这里
console.log('dosomthing')
}

我们也可以这样做,当然我们也可以使用 valueOf/toString 的方式去修改

var a = {
i: 0,
[Symbol.toPrimitive]:function(hint){
return ++this.i
}
}
if(a === 1 && a === 2 & a === 3){
// 需要执行这里
console.log('dosomthing')
}

a 是什么

function foo(){
try{
var a = 50
throw ""
}catch(e){
console.log(a)
var a = 250
}
}
foo()

因为在执行函数 foo 的时候创建了一个单独的 EC 栈帧,在抛出异常的时候,栈帧内 Local 的 a 为 50.在抛出异常前, 所以 console.log 的结果是 50

break 能退出所有循环么

for(let i = 0; i< 10; i++) {
console.log(i)
for(let j = 0; j < 10; j ++) {
if(j > 2) {
break // ? 这里能直接跳出这个循环么,执行下边 sucess
}
console.log(j)
}
}
console.log('success')
// 答案是不可以

如何修改呢?先了解下标签声明

标签声明

JavaScript中的标签就是一个标识符。标签可以与变量重名而互不影响,因为它是另一种独立的语法元素(既不是变量,也不是类型),其作用是指示“标签化语句(labeled statement)”。

而它的声明也很简单:一个标识符后面跟一个冒号“:”。可以在多数语句前面加上这样的标签以使该语句被“标签化(labeled)”,

但是标签语句不能用作注释语句、模块导入, 实际上表示一个“语句/语句块”

在 JavaScript 中,标签只能被 break 语句与 continue 语句所引用。前者表明停止语句执行,并跳转到 break 所指示标签的范围之外;后者表明停止当前循环,并跳转到continue所指示标签的范围起点

// 带标签的代码块
test: {
console.log(2222)
}
// 不带标签的代码块
{
console.log(3333)
}

所以上述例子可以修改为:

out: for(let i = 0; i< 10; i++) {
console.log(i)
for(let j = 0; j < 10; j ++) {
if(j > 2) {
break out
}
console.log(j)
}
}
// fine
console.log('success')

// 或者 在外层循环在加上 break
for(let i = 0; i< 10; i++) {
console.log(i)
for(let j = 0; j < 10; j ++) {
if(j > 2) {
break
}
console.log(j)
}
break
}
// fine
console.log('success')
// 当然也可以修改为 使用 return 语句退出, 但是大多数时候,我们可能需要执行后边的 success

function test() {
for(let i = 0; i< 10; i++) {
console.log(i)
for(let j = 0; j < 10; j ++) {
if(j > 2) {
return
}
console.log(j)
}
}
console.log('success')
}

逗号“,”的二义性

逗号“,”既可以是语法分隔符,又可以是运算符。

连续运算符

const a = ('hellojs', 20)
console.log(a) // ??

逗号运算符是二元运算符,它能够先执行运算符左侧的操作数,然后再执行右侧的操作数,最后返回右侧操作数的值

所以 a 最后的结果是 20

语法分隔符

var a,b;
a = 20;
b = 30;

加号“+”的二义性

如果表达式中存在字符串,则优先按字符串连接进行运算

var l = 'hello' + 'js'
var n = '520'
var num = 1
var test_sum += 1
var a = n + num // 5201

由于加号“+”进行的是值运算,因此当对象(例如a)参与运算时将调用方法x.valueOf()来确定操作数的类型(T),并在类型T不符合要求时调用x.toString()来再次尝试 可以理解为先获取该项 T 的原始值类型,然后继续做运算,如果存在字符串,按照 如果表达式中存在字符串,则优先按字符串连接进行运算

看个例子

[]+{}

var t_a = [] + {} // ?
// 结果为 "[object Object]"
var t_b = [] + 1 // ?
// step
// ([]).valueOf() => [] // 不符合原始值的集中类型 string number boolean symbol undefined
// ([]).valueOf().toString() => ''
// t_b = '' + 1 // 那么安照提醒,如果表达式中存在字符串,则优先按字符串连接进行运算
// 所以最后结果是 '1'

{}+[]

var t = {} + [] // 0 ?why

// 分解一下, 如果讲上边的代码改动一下,结果将会不一样
// t = ({}) + [] // "[object Object]"
// 丢失的 {} 去了哪里,其实上边的代码是因为 js 解析执行的问题,一个关于; 的问题,上述代码会被当成 {};+ [] 来执行
// t = {}; + []
// t = +[] // [].valueOf().toString() => ""
// t = + "" === 0

括号“()”的二义性

下列表达式test的结果是什么?

var test = ('hellojs', 520)
// 520
// 优于括号是强制运算符,但是受到逗号运算符的影响,最终会返回最右侧的表达式的结果,所以是 520

括号“{}”的二义性

可以先看看下列这个例子

由于语句末尾的大括号的前后都可以省略 “;”(分)号,因此下面这行代码就值得回味了:

// 会得到什么
{1,2,3,4}
// 4 由于 逗号 连续运算符影响

所以关于 {} 大概可以分为 6 种。

复合语句块

// 自定义标签后表示复合语句块
customLabel: {
...
}
if(condition) {
...
}else {
...
}

解构

var test = {
type: 'a',
price: 23
}

var { type, price } = test
console.log(type, price) // 'a', 23

声明对象字面量

var book = {
name: '测试',
author: '小李'
}

函数声明

function test() { ... }
var test = function() { ... }

结构化异常

try{
...
}catch(e){
...
}
finally{
...
}

模版字符串赋值

var b = 20
var str = `this book price is ${b}`

消失的 a

a 会是什么

var x = {
a:100
}
x.a = x = 1
// x.a: undefined

注意一个概念:赋值运算符(=)”的优先级低于属性存取运算符(.) 所以改一下

var r = x = {
a:100
}

x.a = x = 1
// x.a = (x = 1)
// x 1
// x.a: undefined
// 实际上是先运行 (x = 1) ,对属性 x.a 进行了暂存,然而又对 x 进行了重写,所以,就有了消失的 a,但是我们之前缓存了这个 x。所以 r.a 得到正确的赋值
r.a = 1

关于 new 的问题

问题

function Foo() {

}
Foo.getName = function() { return 1 }
Foo.prototype.getName = function() { return 2 }

new Foo.getName(); // ?
new new Foo().getName(); // ?

解释

// 1,
// 因为 js 中 点的运算符会很高(17),而 new 在函数没有参数是,优先级是(17), 如果存在参数会变成(18), 从左自右执行
// 所以会变成 new (Foo.getName())
// 2
// 所以这一步可以变成 new (new (Foo().getName())) // 2

参考

《javascript 语言精髓与编程实战》 周爱民