Skip to content

Object.defineProperty bug #485

@rabit2022

Description

@rabit2022
(function () {
    function PROPERTY(CLASS, name, descriptor) {
        // 确保至少提供了一个 get 或 set 方法
        if (!descriptor.get && !descriptor.set) {
            throw new Error(
                sprintf(
                    "PROPERTY: At least one of 'get' or'set' must be provided for property '%s'.\nExample: descriptor = { get: function() { return this._value; } };",
                    name
                )
            );
        }

        // // 定义属性
        // Object.defineProperty(CLASS.prototype, name, attr);
        var attr = {
            enumerable: true, // 属性可枚举
            configurable: true,// 属性可配置
            get: descriptor.get || undefined,
            set: descriptor.set || undefined
        };
        Object.defineProperty(CLASS.prototype, name, attr);
    }


// 测试用例
    function Person() {
        this._name = "John";
        this._age = 25;
    }

    PROPERTY(Person, "name", {
        get: function() {
            return this._name;
        },
        // set: function(value) {
        //     this._name = value;
        // }
    });

    var p = new Person();
    console.log(p.name); // John
    // p.name = "Mary";
    // console.log(p.name); // Mary
})();

运行这段代码时,当get或set有一个为Undefined时,会报错。

在运行 05.属性jsfl 时,发生以下JavaScript 错误:
第425 ("es5-sham.jsfl"文件中)
SyntaxError: invalid setter usage

对应代码

es5-sham.jsfl

var binder = function () {
                if (this instanceof bound) {
                    // 15.3.4.5.2 [[Construct]]
                    // When the [[Construct]] internal method of a function object,
                    // F that was created using the bind function is called with a
                    // list of arguments ExtraArgs, the following steps are taken:
                    // 1. Let target be the value of F's [[TargetFunction]]
                    //   internal property.
                    // 2. If target has no [[Construct]] internal method, a
                    //   TypeError exception is thrown.
                    // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
                    //   property.
                    // 4. Let args be a new list containing the same values as the
                    //   list boundArgs in the same order followed by the same
                    //   values as the list ExtraArgs in the same order.
                    // 5. Return the result of calling the [[Construct]] internal
                    //   method of target providing args as the arguments.

                    var result = apply.call(
                        target,
                        this,
                        array_concat.call(args, array_slice.call(arguments))
                    );
                    if ($Object(result) === result) {
                        return result;
                    }
                    return this;
                }
                // 15.3.4.5.1 [[Call]]
                // When the [[Call]] internal method of a function object, F,
                // which was created using the bind function is called with a
                // this value and a list of arguments ExtraArgs, the following
                // steps are taken:
                // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
                //   property.
                // 2. Let boundThis be the value of F's [[BoundThis]] internal
                //   property.
                // 3. Let target be the value of F's [[TargetFunction]] internal
                //   property.
                // 4. Let args be a new list containing the same values as the
                //   list boundArgs in the same order followed by the same
                //   values as the list ExtraArgs in the same order.
                // 5. Return the result of calling the [[Call]] internal method
                //   of target providing boundThis as the this value and
                //   providing args as the arguments.

                // equiv: target.call(this, ...boundArgs, ...args)
                return apply.call(
                    target,
                    that,
                    array_concat.call(args, array_slice.call(arguments))    // 425
                );
            };

所以,我的解决方案是

es5-shim.jsfl

    if (!Object.defineProperty || definePropertyFallback) {
        var ERR_NON_OBJECT_DESCRIPTOR = 'Property description must be an object: ';
        var ERR_NON_OBJECT_TARGET = 'Object.defineProperty called on non-object: ';
        var ERR_ACCESSORS_NOT_SUPPORTED =
            'getters & setters can not be defined on this javascript engine';
        var ERR_VALUE_NOT_ALLOWED = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute."

        Object.defineProperty = function defineProperty(object, property, descriptor) {
            // 检查是否同时包含值属性和访问器属性
            if (('value' in descriptor || 'writable' in descriptor) && ('get' in descriptor || 'set' in descriptor)) {
                throw new TypeError(ERR_VALUE_NOT_ALLOWED);
            }
            if (isPrimitive(object)) {
                throw new TypeError(ERR_NON_OBJECT_TARGET + object);
            }
            if (isPrimitive(descriptor)) {
                throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
            }

            // 如果 get 或 set 为 undefined,则移除它们
            if ('get' in descriptor && typeof descriptor.get !== 'function') {
                delete descriptor.get;
            }
            if ('set' in descriptor && typeof descriptor.set !== 'function') {
                delete descriptor.set;
            }

也就是在执行这个函数之前对descriptor进行判断,提前把get与set处理好。
同时做出了优化,当write与get set同时存在时将会报错。这与node JS的行为相同。

这个可能是 bind方法,与原生方法的行为不同的导致的bug。

希望可以采纳

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions