JavaScript 问题集锦

发布于 2022-11-22 14:04:24 字数 7514 浏览 667 评论 3

关于 JavaScript,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。这篇文章记录了一些有价值的问题。

1. 对象字面值不能正确解析

问题{a:1}.a 报错,错误 Uncaught SyntaxError: Unexpected token .

解决

({a:1}.a) // 或({a:1}).a

原因

MDN: Object literals

An object literal is a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}). You should not use an object literal at the beginning of a statement. This will lead to an error or not behave as you expect, because the { will be interpreted as the beginning of a block.

简单说,就是声明对象字面值时,语句开头不应该用{,因为js解释器会认为这是语句块(block)的开始。

同理,类似问题 { name: "mc", id: 1 } 会报错 Uncaught SyntaxError: Unexpected token : 也是这个道理。({ name: "mc", id: 1 }) 即可正确解析。但稍注意下,{name: "mc"} 是不会报错的,它等同于 name: "mc",并返回一个字符串 "mc"

2. 数字的点操作符

问题123.toFixed(2) 报错,错误 Uncaught SyntaxError: Unexpected token ILLEGAL

解决

(123).toFixed(2) // >> "123.00"
// 以下两种都可以,但完全不推荐
123..toFixed(2)
123 .toFixed(2)

原因:很简单,js 解释器会把数字后的.当做小数点而不是点操作符。

3. 连等赋值问题

问题:尝试解释下连等赋值的过程。下面的代码为什么是这样的输出?

var a = {n: 1};  
var b = a; 
a.x = a = {n: 2};  
console.log(a.x);// --> undefined  
console.log(b.x);// --> {n:2}

原因

我们可以先尝试交换下连等赋值顺序(a = a.x = {n: 2};),可以发现输出不变,即顺序不影响结果。

那么现在来解释对象连等赋值的问题:按照 es5规范,题中连等赋值等价于 a.x = (a = {n: 2});,按优先获取左引用(lref),然后获取右引用(rref)的顺序,a.x 和 a 中的a都指向了{n: 1}。至此,至关重要或者说最迷惑的一步明确。(a = {n: 2})执行完成后,变量a指向{n: 2},并返回 {n: 2};接着执行 a.x = {n: 2},这里的 a 就是 b(指向 {n: 1}),所以 b.x 就指向了 {n: 2}


以 AST 的角度更清晰地解释此题(2017-11-06)esprima 提供解析 JS 到 AST 的功能,我们可以借此看一下这段代码在引擎眼里到底是什么。(其实 node 从 8 开始开始支持编译 JS 到 AST 了 (V8 ignition interpreter),不过 node 好像没有提供接口给我们使用)。

下面是我拿到的上面代码的 AST:

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "a"
          },
          "init": {
            "type": "ObjectExpression",
            "properties": [
              {
                "type": "Property",
                "key": {
                  "type": "Identifier",
                  "name": "n"
                },
                "computed": false,
                "value": {
                  "type": "Literal",
                  "value": 1,
                  "raw": "1"
                },
                "kind": "init",
                "method": false,
                "shorthand": false
              }
            ]
          }
        }
      ],
      "kind": "var"
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "b"
          },
          "init": {
            "type": "Identifier",
            "name": "a"
          }
        }
      ],
      "kind": "var"
    },
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "AssignmentExpression",
        "operator": "=",
        "left": {
          "type": "MemberExpression",
          "computed": false,
          "object": {
            "type": "Identifier",
            "name": "a"
          },
          "property": {
            "type": "Identifier",
            "name": "x"
          }
        },
        "right": {
          "type": "AssignmentExpression",
          "operator": "=",
          "left": {
            "type": "Identifier",
            "name": "a"
          },
          "right": {
            "type": "ObjectExpression",
            "properties": [
              {
                "type": "Property",
                "key": {
                  "type": "Identifier",
                  "name": "n"
                },
                "computed": false,
                "value": {
                  "type": "Literal",
                  "value": 2,
                  "raw": "2"
                },
                "kind": "init",
                "method": false,
                "shorthand": false
              }
            ]
          }
        }
      }
    }
  ],
  "sourceType": "script"
}

可以清晰地得到,代码等价于:a.x = (a = {n: 2});。然后核心的知识点是:引用解析发生在实际赋值之前

4. 逗号操作符

问题: 下面的代码返回什么,为什么?

var x = 20;
var temp = {
    x: 40,
    foo: function() {
        var x = 10;
        return this.x;
    }
};
(temp.foo, temp.foo)(); // 20,而不是40

原因

MDN逗号操作符:

The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.

即逗号操作符会从左到右计算它的操作数,返回最后一个操作数的值。所以(temp.foo, temp.foo)();等价于var fun = temp.foo; fun();fun调用时this指向window,所以返回20。

5. parseInt传入数字

问题: parseInt传入数字时为什么有以下输出?

parseInt(0.000008) // >> 0
parseInt(0.0000008) // >> 8

原因

parseInt(arg) 时会调用 arg.toString()

(0.000008).toString() // "0.000008"
(0.0000008).toString() // "8e-7"

6. 前端面试题,利用给定接口获得闭包内部对象

问题前端面试题,利用给定接口获得闭包内部对象

var o = (function() {
    var person = {
        name: 'Vincent',
        age: 24,
    };
    return {
        run: function(k) {
            return person[k];
        },
    }
}());

在不改变上面的代码情况下, 怎么得到原有的 person 对象?

解决

Object.defineProperty(Object.prototype, 'self', 
    {
        get: function() {
            return this;
        },
        configurable: true
    });
o.run('self'); // 输出 person

但如果加上 person.__proto__ = null,目前还没找到解决方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

瑾兮 2022-05-04 13:27:23

13. jQuery.triggerextraParametersundefined的问题

问题: 下面slider为什么是undefined

// self 为 某jQuery对象
$slider.trigger('slideend', self);

// 监听
$dom.on('slideend', function(ev, slider) {
    // Uncaught TypeError: Cannot read property 'value' of undefined
    console.log(slider.value); // slider 为 undefined
});

刚看到错误时感觉莫名其妙:怎么就错了?jQuery用的很 6 好不好?

╰◇生如夏花灿烂 2022-05-03 13:26:25

10. Function.prototype.call/apply 的 this 问题

问题: 下列情况中this为什么是这样的?

function nsm() {console.log(this);}
nsm(); // Window{top: xxxx}
nsm.call(null/undefined); // Window{top: xxxx}
nsm.call(1); // Number {[[PrimitiveValue]]: 1}

function sm() {'use strict'; console.log(this);}
sm(); // undefined
sm.call(null); // null
sm.call(undefined); // undefined
sm.call(1); // 1

原因

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

The value of this provided for the call to fun. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode code, null and undefined will be replaced with the global object, and primitive values will be boxed.

非严格模式下,this默认指向全局对象,call/apply显式指定this参数时也会强制转换参数为对象(如果不是对象)。其中,null/undefined被替换为全局对象,基础类型被转换为包装对象。

严格模式下,this默认为undefined,且call/apply显式指定this参数时也不会有强制转换。

11. 给基础类型添加属性无效

问题: 为什么给基础类型添加属性不报错但又无效?

var num = 1;
num.prop = 2;
num.prop // undefined

原因

Property Accessors规范 https://es5.github.io/:

2.Let baseValue be GetValue(baseReference).

GetValue规范 https://es5.github.io/:

4.If IsPropertyReference(V), then ...

最后可知,num.prop 等同于 Object(num).prop,也就是说我们添加属性到一个新建的对象上,与num没任何关系。

12. 数组的展开/扁平

问题[1,2,[2,3,[4,5]]]--->[1,2,2,3,4,5]

function flatten(arr) {
    if(!isArray(arr) || !arr.length) {
        return [];
    } else {
        return Array.prototype.concat.apply([], arr.map(function(val) {
            return isArray(val) ? flatten(val) : val;
        }));
    }

    function isArray(arr) {
        return Object.prototype.toString.call(arr).slice(8, -1).toLowerCase() === 'array';
    }
}
flatten([1,2,[2,3,[4,5]]]);
// [1, 2, 2, 3, 4, 5]

另外,看到一种方法:利用array.toString()然后重新解析也可以完成,但此时数组元素类型丢失。这种方法利用了ToString(array)会转换成"x,y,z..."

笨死的猪 2022-04-30 04:06:12

7. 原型链导致的属性更改无效

问题: 看下面的代码,为什么obj.x = 100的赋值无效?

var proto = Object.create({}, {
    x: {
        value: 1,
        writable: false
    }
});
var obj = Object.create(proto);
obj.x = 100; // 无效,strict mode下报错: Uncaught TypeError
console.log(obj.x); // 1

原因

es5 assignment

When an assignment occurs within strict mode code, its LeftHandSide must not evaluate to an unresolvable reference. If it does a ReferenceError exception is thrown upon assignment. The LeftHandSide also may not be a reference to a data property with the attribute value {[[Writable]]:false}, to an accessor property with the attribute value {[[Set]]:undefined}, nor to a non-existent property of an object whose [[Extensible]] internal property has the value false. In these cases a TypeError exception is thrown.

本题中,obj.x实际指向proto.xwritable: false阻止了赋值。

8. 位操作符

问题: 实现浮点数转整数,或者说取出数字的整数部分。比如-12.921 --> -1212.921 --> 12等等。

解决

function convertToInt(num) {
      return num >> 0;
}
convertToInt(-Math.PI); // -3
convertToInt(12.921); // 12

原因

没有什么高达上,就是神奇的位操作符:有符号右移>>The Signed Right Shift Operator ( >> )

...
5. Let lnum be ToInt32(lval).
...

本题利用了有符号右移会将左操作数转换为32位整数。

补充

同理,num | 0也是可以的。

9. IEEE-754精度问题

问题0.1 + 0.2 !== 0.3 // true

原因

所有使用IEEE-754数字实现的编程语言都有这个问题。

0.10.2的二进制浮点数表示并不是精确的,所以相加后不等于0.3。这个相加的结果接近0.30000000000000004

那么你一定想要比较相加后的结果和预期数字怎么办?宽容比较,即允许一定误差,这个误差值一般是2^-52 (2.220446049250313e-16)。

update 2017-02-13: ES2015标准化了这个值,可通过Number.EPSILON访问

~没有更多了~

关于作者

冰雪之触

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

烙印

文章 0 评论 0

singlesman

文章 0 评论 0

独孤求败

文章 0 评论 0

晨钟暮鼓

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文