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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions