Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript问题集锦 #2

Open
creeperyang opened this issue Jul 20, 2015 · 27 comments
Open

JavaScript问题集锦 #2

creeperyang opened this issue Jul 20, 2015 · 27 comments

Comments

@creeperyang
Copy link
Owner

creeperyang commented Jul 20, 2015

原博客迁移过来(有更改),并将保持更新。


关于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.xa中的a都指向了{n: 1}。至此,至关重要或者说最迷惑的一步明确。(a = {n: 2})执行完成后,变量a指向{n: 2},并返回{n: 2};接着执行a.x = {n: 2},这里的a就是b(指向{n: 1}),所以b.x就指向了{n: 2}

搜索此题答案时,颜海镜的一篇博客关于此题也有讲述,不过没有讲清楚(或许是我没有领会 :P)。


*以 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,目前还没找到解决方法。

@creeperyang
Copy link
Owner Author

creeperyang commented Jul 20, 2015

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访问

@creeperyang
Copy link
Owner Author

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/#x11.2.1:

2.Let baseValue be GetValue(baseReference).

GetValue规范 https://es5.github.io/#x8.7.1:

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..."

@creeperyang
Copy link
Owner Author

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 好不好?:joy:

调试!打了几个断点,找到了原因:

data = data != null ? jQuery.makeArray( data ) : [];
data.unshift( event );

trigger中的self会被makeArray处理,这本来没什么,坑爹的是 self有个属性: self.length = 100,然后悲剧了,makeArray之后data变成[undefined, undefined, ...]

好了,debug完毕,问题看起来很简单,但总结出个道理:错误常常出现在你想不到/忽视的地方。

@creeperyang
Copy link
Owner Author

14. delete操作符

问题: 试着解释下面代码的结果?

(function(x){
    delete x;
    return x;
})(1);
// 返回 1

在QQ群看到这个问题,想想还是有点坑在里面的。翻了翻MDN,加深/重新记忆下对delete的理解。

delete是删除对象的属性。严格模式下,删除non-configurable的属性报错,非严格则返回false,其它任何情况都返回true

回到本题,delete x其实是尝试删除局部变量x局部变量是non-configurable,所以无法删除,在严格模式下,题中代码将报错。

更正:对局部变量和函数名delete是无效的,delete只能删除属性。delete obj.propName 才是合法的形式。下面以代码详细解释:

// ========例1========
(function(x) {
    'use strict';
    delete x;
})(1);
// Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
// 在严格模式下,删除不合法的标识符x(x是变量)

// ========例2========
(function () {
    'use strict';
    var obj = {};
    Object.defineProperty(obj, 'x', {
        configurable: false,
        writable: true,
        enumerable: true,
        value: 'hi'
    });
    delete obj.x;
})()
// Uncaught TypeError: Cannot delete property 'x' of #<Object>
// 合法的删除形式,但属性x是non-configurable的,严格模式下报错

// ========例3========
window.x = 100;
window.y = 100;
(function(x) {
    console.log(x, window.x, window.y);
    console.log(delete x, delete y);
    console.log(x, window.x, window.y);
    return x;
})(1);
// 1 100 100
// false true
// 1 100 undefined

// delete x失败,因为x是变量(函数内局部变量x覆盖全局变量x),delete y成功,y是全局对象window的属性。
// 非严格模式下,可以 delete y 的写法,但此时是尝试删除全局对象的同名属性y(y在作用域中不是变量或函数名)。

@creeperyang
Copy link
Owner Author

creeperyang commented Nov 18, 2015

15. jQuery.fn.offset不支持获取和设置display:none元素的坐标

不支持获取很好理解,display:none元素不在文档流中,获取位置自然是undefined。而$(hiddenElement).offset()会返回{top: 0, left: 0}以提高程序健壮性。

不过问题核心是为什么无法正确设置display:none元素的坐标,具体点,即为什么
$(hiddenElement).offset(options)设置的真实值是指定值的两倍

var offset = {top: 10, left: 10};
$dom.offset(offset); // $dom 是 display none 的
// 实际情况,$dom被设置成 left: 20px; top: 20px;

如上面代码所示,隐藏的元素实际被设置打坐标是给定值的2倍,为什么?来看下jQuery关于offset部分的源码:

curOffset = curElem.offset();
curCSSTop = jQuery.css( elem, "top" );
curCSSLeft = jQuery.css( elem, "left" );
...
if ( options.top != null ) {
    props.top = ( options.top - curOffset.top ) + curTop;
}
if ( options.left != null ) {
    props.left = ( options.left - curOffset.left ) + curLeft;
}

if ( "using" in options ) {
    options.using.call( elem, props );
} else {
    curElem.css( props );
}

原因就是$el.offset(options)并不简单采用options.leftoptions.top,而会计算( options.top - curOffset.top ) + curTop作为topleft同理)。隐藏元素的 css属性部分的left和top可以正常取到,但curOffset则是{top: 0, left: 0},所以( options.top - curOffset.top ) + curTop --> options.top + curTop造成一倍误差

@creeperyang
Copy link
Owner Author

16. 找出字符串中出现最多的字母

这个问题看起来用到的地方挺多,至少我遇到过不止一次,索性在这里讲一讲。先具体描述下问题:

假设字符串'ababccdeajxac',请找出出现次数最多的字符?

最先想到的解法是用map纪录每个字符的次数,然后找出最多的即可:

function getMaxNumberOfChar(str) {
    return (str + '').split('').reduce(function(pre, cur, index, arr) {
        cur in pre ? pre[cur]++ : (pre[cur] = 1);
        pre[cur] > pre.value && (pre.char = cur, pre.value = pre[cur]);
        return pre;
    }, {value: 0});
}
getMaxNumberOfChar('ababccdeajxac') // Object {value: 4, a: 4, char: "a", b: 2, c: 3…}

此外,可以考虑用正则来辅助处理:

function getMaxNumberOfChar(str) {
    return (str + '').split('').sort().join('').match(/(\w)\1*/g).reduce(function(pre, cur) {
        return cur.length > pre.value ? {value: cur.length, char: cur[0]} : pre;
    }, {value: 0})
}
getMaxNumberOfChar('ababccdeajxac') // Object {value: 4, char: "a"}

@creeperyang
Copy link
Owner Author

17. storage event

当你用localStoragesessionStorage的API去更改Storage时,会触发storage事件:

window.addEventListener('storage', function(e) {
        console.log('storage', e);
});

这里没有什么特别的,但基本所有问题的根源,或者说要特别注意的是:本页面更改Storage只能在同域名的其它页面去捕获storage事件。

The StorageEvent is fired whenever a change is made to the Storage object. This won't work on the same page that is making the changes — it is really a way for other pages on the domain using the storage to sync any changes that are made. Pages on other domains can't access the same storage objects.

@creeperyang
Copy link
Owner Author

18. 一个函数柯里化问题及更多

C君出了这样一个题,要求实现sum函数如下:

sum(1) // 1
sum(1)(2) // 3
sum(1)(2)(3) // 6

第一眼,这不是函数柯里化吗,小case,然后我挥笔写下:

function sum(item) {
    var cur = item;
    var inner = function(next) {
        return next == null ? cur : (cur += next, inner);
    };
    return item == null ? undefined : inner;
}

运行下:

2016-02-16 10 50 24

perfect!

然后我看了看答案,又看了看题目 😕 ❓

好吧,题目根本没有最后的()。所以最后答案是:

function sum(item) {
    var cur = item;
    var inner = function(next) {
        if (next != null) cur += next;
        return inner;
    };
    inner.toString = function() {
        return cur;
    }
    return inner;
}

如上,改写toString达成魔法效果。

@creeperyang
Copy link
Owner Author

creeperyang commented Feb 23, 2016

19. 运算符优先级问题

普通的运算符优先级对大家应该不成问题,另外也推荐用括号来明确运算符优先级和代码意图,写出更可读的代码

但之所以有这个问题,是涉及到 typeof 运算符,比较新颖。

问题:

var str = 'why I am ' + typeof + ''; // so what is str?

strwhy I am number,思考一下,上面的代码应该等同于'why I am ' + (typeof (+ ''))。好,现在问题归结到运算符优先级,查看文档验证:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

typeof运算符优先级高于+,并且是right-to-left

@creeperyang
Copy link
Owner Author

20. Prefix Increment Operator(++)的问题

关于前自增运算符的一个有意思的问题:

++'52'.split('')[0] //返回的是?

这道题来自Another JavaScript quiz第8题,主要是优先级问题,应该返回6,看完答案应该没什么难理解的。但是,题目的某个注意点:

++'5'
// Uncaught ReferenceError: Invalid left-hand side expression in prefix operation

却非常有意思。所以问题是为什么++'5'报错而++'52'.split('')[0]可以正确执行?

阅读http://es5.github.io/#x11.4.4,可以看到_Prefix Increment Operator_操作的第5步PutValue(expr, newValue)要求expr是引用。

而在这里,

  • '5'是值,不是引用,所以报错。
  • '52'.split('')[0]返回的是['5','2'][0],对象的属性访问返回的是引用,所以可以正确执行。
var x = '5';
++x  // 6

++'5'[0] // 6

@creeperyang
Copy link
Owner Author

21. 你真的了解String.prototype.split吗?

var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
'div.btn#submit'.split(classIdSplit); 

// -> ["", "div", "", ".btn", "", "#submit", ""]

上面代码段的输出使我很疑惑(什么鬼)... 问题简化下,就是split所用的正则有捕获时怎么处理?

查API,str.split([separator[, limit]]):

separator: Optional. Specifies the character(s) to use for separating the string. The separator is treated as a string or a regular expression. If separator is omitted, the array returned contains one element consisting of the entire string. If separator is an empty string, str is converted to an array of characters.

If separator is a regular expression that contains capturing parentheses, then each time separator is matched, the results (including any undefined results) of the capturing parentheses are spliced into the output array. However, not all browsers support this capability.

按API,var result = str.split(separator);

separator是带捕获的正则时,每次命中,都添加到结果数组result中。

@rambo-panda
Copy link

前端面试题,利用给定接口获得闭包内部对象
这道题违背了 闭包 的原则。 根本就不应该拿来面试, 只会误导!

@creeperyang
Copy link
Owner Author

@rambo-panda 谈不上违背,从闭包中获取数据,可以加深对闭包的理解。

拿来面试可以考察一些原理,对新手而言,不看这些也是好的,免得混淆其它东西。

@lovetingyuan
Copy link

超棒!

@Grace-lekaro
Copy link

可以理解,有点难.

@ghost
Copy link

ghost commented Feb 12, 2017

第9题浮点数精度的问题可以用 Number.EPSILON,也就是你所说的那个常数,不过内置了。

@creeperyang
Copy link
Owner Author

@Leonard-Peng 感谢提醒,的确内置了,是在ES2015中被标准化的。

The value of Number.EPSILON is the difference between 1 and the smallest value greater than 1 that is representable as a Number value, which is approximately 2.2204460492503130808472633361816 x 10‍−‍16.

Number.EPSILON:数字1和大于数字1的最小数字的差值,近似2.2204460492503130808472633361816 x 10‍−‍16 。

@creeperyang
Copy link
Owner Author

creeperyang commented Feb 15, 2017

22. 异步的throw将不会被Promise捕获

我们知道,在Promise内部throw,promise将会变成rejected状态,并且通过catch可以捕获错误(也就是说,我们可以不用显示调用reject)。示例如下:

const promise = new Promise((resolve, reject) => {
  // alternative: reject('hi')
  throw 'hi'
})
// 这里的 reason 就是 hi
promise.catch(reason => console.log(reason))

但请注意,异步的throw将不会被捕获到。

function timeoutPromiseThrow(delay) {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            throw ( "Timeout!" );
        }, delay );
    } );
}

const th = timeoutPromiseThrow(2000).catch((err) => { console.log("throw " + err); })
// th 将永远是 pending 状态。

解释:promise内部有类似try{} catch...的机制,但显然,异步的throw是无法被捕获的,异步请显式调用reject

来源: getify/You-Dont-Know-JS#961

@MRLP0524
Copy link

MRLP0524 commented Mar 7, 2017

请问是否可以转载呢?会在文章开头注明作者和出处

@creeperyang
Copy link
Owner Author

@MRLP0524 可以

@zhuzhuaicoding
Copy link

zhuzhuaicoding commented Mar 24, 2017

第3个原文的评论解释的挺好的

@creeperyang
Copy link
Owner Author

23. JSON.parse的一个摸不着头脑的报错Unexpected token o in JSON

错误如下:

SyntaxError: Unexpected token o in JSON at position 1
    at Object.parse (native)
    at Promise.then.e (/root/projects/axure-site/server/api/index.js:14:15)

看起来是我们传给JSON.parse作为参数的字符串有问题,但有时候,我们的字符串里完全没有字符o,所以这个报错让人非常迷惑。

原因:

JSON.parse(obj) // obj.toString() ---> "[object Object]"

当我们传给JSON.parse的参数不是字符串时,参数首先会转为字符串,比如obj.toString() ---> "[object Object]",然后JSON.parse("[object Object]")

这种错误很弱智,但如果没注意到,会被这个报错信息误导...

在此记录一下。

@creeperyang
Copy link
Owner Author

creeperyang commented Jul 6, 2017

24. JavaScript 中参数是值传递还是引用传递?

https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language --- stackoverflow 有同样的问题,高票回答一针见血:

JavaScript 既不是值传递,也不是引用传递,而是 call by sharing .

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);   // 10
console.log(obj1.item);    // changed
console.log(obj2.item);    // unchanged

如 demo 所示,假设:

  1. 如果是 Call by value (pass by value),那么 obj1.item 应该是 unchanged
  2. 如果是 Call by reference (pass by reference),那么 c = {item: "changed"} 应该是起效果的,即 obj2.item 应该是 changed

由以上两点可知,JavaScript 既不是值传递 (复制值),也不是引用传递 (可以直接操作作为参数的变量),而是 call by sharing。

那么什么是 call by sharing?

  1. 相比引用传递,对参数的赋值对 caller 是不可见的。
  2. 相比值传递,对参数(如果是对象)的修改(属性修改)对 caller 是可见的,因为对象是 shared,而不是 copied。

结合下面 c 语言的例子总结下:

#include <stdio.h>
// 传引用
void log(int *p1, int *p2){
    printf("%#X, %#X\n", p1, p2);
}
// 传值
void log2(int p1, int p2) {
    printf("%#X, %#X\n", &p1, &p2);
    printf("%d, %d\n", p1, p2);
}
int main(){
    int a = 66, b = 99;
    printf("%#X, %#X\n", &a, &b);
    log(&a, &b);
    log2(a, b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

/*
0X5B769758, 0X5B769754
0X5B769758, 0X5B769754
0X5B76972C, 0X5B769728
66, 99
a = 66, b = 99
*/

如上例所示:

  1. 传值:参数指向新内存(新内存复制了值),操作参数和原来的变量(指向的内存)没有半毛钱的关系。
  2. 传引用:参数指向同一份内存(没有复制值),操作参数即操作原来的变量(指向的内存)。
  3. call by sharing:参数指向新内存,但新内存复制了原来的内存地址,直接赋值的话相当于覆盖了新内存的内容,不会影响原来的变量;但是改变对象的属性其实还是操作了原来的对象。

@ChenYouping
Copy link

  1. JavaScript 中参数是值传递还是引用传递?
    ==========>
    这就是Java里的值传递. 只是类型分为 primitive / object 两种, 导致看起来不一致. 其实本质是一样的.

@creeperyang
Copy link
Owner Author

@ChenYouping 你说的没问题。Java 中传参数的本质是赋值操作,primitive 就是本身的值,object 就是内存地址。

不过鉴于C语言的传引用 (void swap(int &a,int &b)),区分 3 种传参方式并没有问题。

补充阅读:

@TaroSunn
Copy link

TaroSunn commented Feb 4, 2018

博主您好,{ name: "mc", id: 1 }我在chrome下测试不报错啊,您说是在什麽情况下的报错啊

@creeperyang
Copy link
Owner Author

@TaroSunn 应该是chrome新版本跟以前的处理逻辑不一致了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants