【问题标题】:How to make a method that can affect any value type?如何制作可以影响任何值类型的方法?
【发布时间】:2015-04-18 21:48:45
【问题描述】:

警告:创建原生对象和/或属性的扩展被认为是错误的形式,并且必然会导致问题。如果它用于您不只为您使用的代码,或者如果您不知道如何正确使用它,请不要使用它


我知道你可以使用ObjectStringNumberBoolean等来定义一个方法,像这样:

String.prototype.myFunction = function(){return this;} //only works on strings.

但我需要做的是在任何值上使用它,并访问函数中的值。

我用谷歌搜索了here,但找不到合适的东西。

2/18/15 编辑:如果我使用 Object.prototype,是否有任何解决方法可以让它成为任何对象的属性?
每个请求,这里是用于isString()的当前函数

function isString(ins) {
    return typeof ins === "string";
}

根据几个答案,我想出了一些代码和由此引起的错误。

Object.prototype.isString = function() {
    return typeof this === "string";
}
"5".isString() //returns false
"".isString()  //returns false
var str = "string"
str.isString() //returns false

【问题讨论】:

  • “无法访问对象”是什么意思?
  • 另外,ObjectStringNumberBoolean 是不同的类型,因此您需要分别定义它们的原型成员。
  • 扩展全局对象原型通常被认为是不好的形式。
  • 是的,但是尝试访问原语上的任何属性都会导致其转换(通过ToObject)到对象。
  • @Dai:根据这种论证,原始数值也不会继承自 Number.prototype :-)

标签: javascript object methods prototype


【解决方案1】:

“点运算符函数”称为方法。在 JavaScript 中创建适用于任何数据类型的方法的最简洁方法是创建包装器。例如:

var Wrapper = defclass({
    constructor: function (value) {
        this.value = value;
    },
    isString: function () {
        return typeof this.value === "string";
    },
    describe: function () {
        if (this.isString()) {
            alert('"' + this.value + '" is a string.');
        } else {
            alert(this.value + " is not a string.");
        }
    }
});

var n = new Wrapper(Math.PI);
var s = new Wrapper("Hello World!");

n.describe(); // 3.141592653589793 is not a string.
s.describe(); // "Hello World!" is a string.

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

通过创建您自己的包装构造函数,您可以确保:

  1. 您的代码不会与其他人的代码混淆。
  2. 其他人的代码不会与您的代码混淆。
  3. 保持全局范围和原生原型干净。

underscorelodash 等几个流行的 JavaScript 库为此目的创建了包装构造函数。

【讨论】:

  • 这是如何工作的?我想我不是很明白,你能给我解释一下吗?
  • @Wyatt 当然。包装器是一种数据类型,仅用于包装另一种数据类型,以便您可以为其提供不同的方法。例如,您可能想要创建一个类似于ArrayVector 数据类型,但预定义的length 不会改变。原因是您可能希望能够add 或找到向量的dot 乘积(不应该在任意数组上做的事情,只有向量)。使用包装器数据类型会提升composition over inheritance,这是一件好事。希望对您有所帮助。
  • 如果您想学习关于继承以及defclass 函数如何工作的速成课程,那么您应该阅读以下答案:stackoverflow.com/a/17893663/783743
  • 有没有办法在没有new Wrapper 的情况下使这些可调用?我正在尝试制作类似于原生方法的东西。
  • 由于每个对象都继承自 Object.prototype,因此您可以在 @Eclecticist 的回答中描述的 Object.prototype 上定义 isString。然而,在原生原型上定义方法是不受欢迎的,因为它可能会无意中导致问题。阅读,Why is extending native objects a bad practice?
【解决方案2】:

首先,为什么不赞成在 Object(或其他内置类型)上定义属性 - 它们出现在意想不到的地方。这是一些输出某些字符的总英尺数的代码:

var feetMap = {
  jerry : 4,
  donald : 2,
  humpty: 0  
}

function countFeet(feetMap) {
  var allFeet = 0;
  for (var character in feetMap) {
    allFeet += feetMap[character];
  }
  return allFeet;
}


console.log('BEFORE DEFINITION', countFeet(feetMap));
Object.prototype.isString = function() {
    return typeof this === "string";
};
console.log('AFTER DEFINITION', countFeet(feetMap));

请注意,简单地定义 isString 函数将如何影响 countFeet 函数的结果,该函数现在迭代一个意外属性。当然,如果迭代受到 hasOwnProperty 检查的保护,或者属性被定义为不可枚举,则可以避免这种情况。

避免在内置类型上定义属性的另一个原因是冲突的可能性。如果每个人都定义了自己的 isNumber 方法,该方法会根据用例给出略有不同的结果——一个人可以将字符串“42”视为一个数字,而另一个人可能会说它不是——当人们使用多个库时,细微的错误会到处出现。

问题是 - 为什么需要一个可以影响任何值类型的方法?方法应该是它所属的对象类所固有的东西。对 Number 对象使用 isString 方法没有任何意义——它与 Numbers 没有任何关系。

更有意义的是有一个函数/方法可以返回给它的值的类型作为参数:

var Util = Util || {};
Util.isString(value) {
  return typeof value === "string";
}

Util.isString('test') // true
Util.isString(5) // false

你当前代码的原因

Object.prototype.isString = function() {
    return typeof this === "string";
}
"5".isString() //returns false
"".isString()  //returns false
var str = "string"
str.isString() //returns false

不起作用是因为当您访问原始值的属性时,JS 会创建适当类型的包装器对象并解析该包装器对象上的属性(之后将其丢弃)。这是一个应该说明它的例子:

Object.prototype.getWrapper = function(){
  return this;
}

console.log((5).getWrapper()); // Number [[PrimitiveValue]]:5
console.log("42".getWrapper()); // String [[PrimitiveValue]]:"42"

请注意,原始值 5 和对象 new Number(5) 是不同的概念。

您可以通过返回原始值的类型来更改您的函数以使其大部分工作。另外,不要忘记将其设为不可枚举,这样当您遍历随机对象时它就不会出现。

Object.defineProperty(Object.prototype, 'isString', {
   value : function() {
             return typeof this.valueOf() === "string";
           },
   enumerable : false
});
"5".isString() //returns true
"".isString()  //returns true
var str = "string"
str.isString() //returns true

【讨论】:

  • 这并没有回答问题,只是一个错误,以一种不需要的方式。
  • @Wyatt 如果你在红灯时过马路并且受伤了,那么你会问为什么你宁愿回答是因为你被车撞了,或者因为你应该在红灯时过马路灯是绿色的。由于答案中指出的原因,破坏封装是一件坏事。
  • @HMR 如果您阅读了这个问题,您就会知道我知道这一点,并试图找到使其成为可能、更安全的方法。
  • @Wyatt 您阅读了答案并在最后一个代码块中找到了解决方法。即使您不应该像您自己和许多其他人指出的那样这样做。所以最安全的方法是使用 Util 方式或忽略 OOP 的基本基础(打破封装)并更改 Object.prototype。
  • @Wyatt 我解释了为什么我认为你正在做的事情是不安全的,我认为这是解决你问题的更好方法。在答案的第二部分中,我解释了为什么您当前的解决方案有问题,并且我解决了这两个问题(通过访问原始值并将属性定义为不可枚举)。你还有什么我错过的吗?
【解决方案3】:
Object.prototype.isString = function() {
    return typeof this === "string";
}
"5".isString() //returns false
"".isString()  //returns false
var str = "string"
str.isString() //returns false

如果有人可以解释作为任何对象属性的函数的解决方法,以及为什么当前方法不起作用,我将提供 125 个代表。

答案:

在 javascript 中,当您调用对象的子方法/属性时,
例如“myFunction”(object.myFunction 或 object[“MyFunction”])
它将开始查看对象本身是否具有它。
如果不是:它将遵循原型链(如普通 oop 中的超类),
直到找到具有方法/属性的“父/超类”。
此原型链的最后一步是 Object。
如果 Object 没有该方法,它将返回“未定义”。

当你扩展 Object 类本身时,它总是会查看 任何将方法调用为对象的对象(在oop中:所有类也是对象,除了是自己的类类型) 这就像在普通 OOP 中缺少“强制转换”。

所以您的函数返回 false 的原因是它在此上下文中是“对象”而不是“字符串”
尝试制作这个函数:

Object.prototype.typeString = function() {
    return typeof this;
}
"5".typeString() //returns "object"

正如大家所说,扩展任何原生 JS 类确实是个坏主意,但解决方法可以从以下内容开始:

Object.prototype.objectTypeString = function(){
   return Object.prototype.toString.call(this);
}

这是一个小提琴: http://jsfiddle.net/fwLpty10/13/

请注意,null 没有原型,而 NaN (NotANumber) 被视为数字!!! 这意味着您将始终需要检查变量是否为空, 在调用这个方法之前!

Object.prototype.isString = function(){
   return Object.prototype.toString.call(this) === "[object String]";
};

最终小提琴:http://jsfiddle.net/xwshjk4x/5/

这里的技巧是 this 方法返回 toString 方法的结果,用“this”调用,这意味着在 toString 方法的上下文中,你调用它的对象是它自己的类(不是原型链中的任何超类型)

【讨论】:

    【解决方案4】:

    扩展 Object 原型的代码工作,如果更正。

    但是,它对this 在被调用方法中的内容做出了错误的假设。使用发布的代码,以下输出是正确的并且是可以预期的(除了一些旧的实现错误);

    "5".isString() //returns false
    

    这是因为 JavaScript 会“包装”“提升”原始值到相应的对象类型之前它调用方法 - this 实际上是一个字符串object,而不是一个字符串value。 (JavaScript 有效地根据原始值伪造调用方法。)

    将函数替换为:

    Object.prototype.isString = function() {
        return this instanceof String;
    }
    

    然后:

    "5".isString() // => true (a string was "promoted" to a String)
    (5).isString() // => false (`this` is a Number)
    

    另一个解决方案也是使用多态性;与修改标准原型相同的“陷阱”1

    Object.prototype.isString = function () { return false; }
    String.prototype.isString = function () { return true; }
    

    1使用defineProperty 默认创建一个“不可枚举”属性可以缓解向全局原型添加新的可枚举属性的担忧。

    简单的改变

    x.prototype.y = ..
    

    Object.defineProperty(x.prototype, 'y', { value: .. })
    

    (我不是为修改原型的使用辩护;只是解释原始有问题的输出并指出一种防止枚举行为的方法。)

    【讨论】:

    • 如何解决for(i instance of someObject){}
    • 有人在此答案上方某处提供了一个简短的答案,并在for 循环中使用instanceof,发现该方法是任何对象的属性。
    • Nvm 发现它:不以这种方式将属性添加到 Object.prototype 的一个很好的理由是,这将在任何对象的所有迭代中拾取 isString 属性,如 x = {}; for (prop in x) {}, jsfiddle.net/jfriend00/5qLwa9L4。这很容易破坏现有代码。 – jfriend00 2 月 18 日 3:02
    • 好的,那就完美了。
    • 最好使该方法成为严格模式函数,以便this 确实是一个原始值。 instanceof String 可能不可靠。
    【解决方案5】:

    给你看一些例子:

    String.prototype.myFunction = function() {
        return this+"asd";
    };
    

    myFunction()被调用时,这个函数会在每个字符串中添加"asd"

    var s = "123";
    s = s.myFunction();
    //s is now "123asd"
    

    【讨论】:

    • 如上面写的“戴”。在 javascript 中,没有像 Java 中那样的“主对象”。所以在这种情况下最好使用普通函数(不需要原型)。如果必须这样做,则必须为每种数据类型定义它。另一种解决方案是编写一个包含其他不同数据类型的包装器对象……但这不是一种好的编码风格,而且比简单的函数需要更多的读写工作。
    • @Cracker0dks javascript中有一个master对象;只是Object。看我的回答:)
    【解决方案6】:

    在我们开始之前,需要记住和注意一些重要的语句(对于所有字符串文字/基元、字符串对象、数字文字/基元、数字对象等都是如此):

    • JavaScript 中的所有对象都继承自 Object,并从 Object.prototype – StringNumber 等继承方法和属性(很像 Java)。
    • JS 有 6 种基本类型——stringnumberbooleannullundefined 和 符号
    • JS 有它们对应的包装对象——StringNumberBooleanSymbol
    • 正如您在上面看到的,JS 有字符串作为原语以及Object
    • 原语不是 Object 类型。
    • 字符串字面量是原语,String 对象的类型为 Object
    • instanceof 运算符测试对象的原型链中是否具有构造函数的原型属性。 (这里获得 TRUE 的第一个条件是 instanceof 应该用于对象或其子类
    • typeof 运算符返回一个字符串,指示未计算的操作数的类型。

    字符串作为原语:
    字符串原语或文字可以通过以下方式构造:

    var str1 = “Hello”;
    var str2 = String(“Hello”);
    typeof str1;                    //Output = "string"
    typeof str2;                    //Output = "string"
    str1 instanceof (String || Object)      //Output = false because it is a primitive not object
    str2 instanceof (String || Object)      //Output = false because it is a primitive not object
    

    字符串作为对象:
    可以通过从新对象调用其构造函数来构造字符串对象:

    var str3 = new String(“Hello”);
    typeof str3;        //Output = "string"
    str3 instanceof (String)        //Output = true because it is a String object
    str3 instanceof (Object)        //Output = true because it is an Object
    

    以上所有内容可能看起来不太明显,但有必要奠定基础。
    现在,让我谈谈你的情况。

    Object.prototype.isString = function() {
        return typeof this === "string";
    }
    "5".isString() //returns false
    "".isString()  //returns false
    var str = "string"
    str.isString() //returns false
    

    由于称为自动装箱的概念,您将 FALSE 作为 o/p。当您在字符串文字上调用任何方法时,它会被转换为字符串对象。 Read this 来自 MSDN -“字符串文字的方法”,请自行确定。

    因此,在您的原型中,当您使用 typeof 检查类型时,它永远不会是文字 (typeof == "string"),因为它已经被转换为对象。这就是您这样做的原因假的,如果你检查typeof 是否有object,那么你会得到真,我将在下面详细讨论:

    • typeof 将提供有关实体类型的信息 - 对象或原语(字符串、数字等)或函数。
    • instanceof 将提供有关它是什么类型的 JS 对象的信息 - 对象或字符串或数字或布尔值或符号

    现在让我谈谈提供给您的解决方案。它说要进行instanceof 检查,这是 100% 正确的,但请注意,在到达您的原型时,它可能是对象类型或函数类型。因此,我在下面提供的解决方案将为您提供相同的图片。

    我的建议是有一个通用函数,它会返回实例的类型,然后你可以根据它是数字还是字符串等做任何你想做的事情。isString 很好,但你必须写isNumber 等等,所以只有一个函数可以返回实例的类型,甚至可以处理函数类型。
    以下是我的解决方案:

    Object.prototype.getInstanceType = function() {
        console.log(this.valueOf());
        console.log(typeof this);
        if(typeof this == "object"){
            if(this instanceof String){
                return "String";
            } else if(this instanceof Boolean){
                return "Boolean";
            } else if(this instanceof Number){
                return "Number";
            }  else if(this instanceof Symbol){
                return "Symbol";
            } else{
                return "object or array";   //JSON type object (not Object) and array
            }
        } else if(typeof this == "function"){
            return "Function";
        } else{
            //This should never happen, as per my knowledge, glad if folks would like to add...
            return "Nothing at all";
        }
    }
    

    输出:

    new String("Hello").getInstanceType()               //String
    "Hello".getInstanceType()               //String
    (5).getInstanceType()               //Number
    (true).getInstanceType()            //Boolean
    Symbol().getInstanceType()          //Symbol
    var ddd = function(){}
    var obj = {}
    obj.getInstanceType()               //object or array
    var arr = []
    arr.getInstanceType()               //object or array
    ddd.getInstanceType()               //Function
    ($).getInstanceType()               //Function, because without double quotes, $ will treated as a function
    ("$").getInstanceType()             //String, since it came in double quotes, it became a String
    

    总结一下:您的 2 个问题如下

    但我需要做的是在任何值上使用它,并访问 函数中的值。

    您可以使用this 访问函数中的值。在我的解决方案中你可以看到console.log(this.valueOf());

    是否有任何解决方法可以让它成为任何对象的属性,如果 我使用 Object.prototype?

    您可以按照上述解决方案从Object.prototype.getInstanceType 实现它,并且您可以在任何有效的 JS 对象上调用它,您将获得所需的信息。

    希望这会有所帮助!

    【讨论】:

    • 请注意,还有一些对象不是来自Object
    • @Bergi 是的,只有 Object 的实例才会从 Object 继承。 JSON 对象(var obj = {};) 是对象,但不从 Object 继承。
    • 不,我不是这个意思。通过评估对象文字构造的对象确实像所有其他对象一样从Object.prototype 继承。但是像Object.create(null) 这样的对象不会。
    • @Bergi 是的,因为在 JS 中“null”是一个原语,并且没有相同的包装器对象。所以,Object.create(null) instanceof Object 将产生 false
    【解决方案7】:

    来自MDN Description of Object

    JavaScript 中的所有对象都是 Object 的后代

    因此,您可以向Object.prototype 添加方法,然后可以在anything 上调用这些方法。例如:

    Object.prototype.isString = function() {
        return this.constructor.name === 'String';
    }
    
    console.log("hi".isString()); //logs true
    console.log([].isString()); //logs false
    console.log(5..isString()); //logs false

    如果需要,您可以为每种类型的原语创建这个 isX 函数。无论哪种方式,您可以为每种类型添加方法,因为 JavaScript 中的所有内容都源自 Object

    希望有帮助,祝你好运:)

    --编辑--

    我确实想指出,仅仅因为你可以这样做并不意味着你应该。扩展 JavaScript 的内置功能通常是一种不好的做法,对于其他人将使用的库来说更是如此。不过,这取决于您的用例。祝你好运。

    【讨论】:

    • 不以这种方式向Object.prototype 添加属性的一个很好的理由是,这将在x = {}; for (prop in x) {}jsfiddle.net/jfriend00/5qLwa9L4 中的任何对象的所有迭代中获取isString 属性。这很容易破坏现有代码。
    • @jfriend00 有您知道的解决方法吗?我现在正在编辑问题以要求提供一个。
    • @Wyatt - 我自己没试过,但你可以在原型上使用Object.defineProperty() 并将属性创建为enumerable: false。这仍然不意味着使用您自己的自定义方法(不是标准的一部分)扩展内置对象是一个好主意。所有流行的框架都出于正当理由不再这样做。
    • @jfriend00 我知道这可能不安全,只是想办法让它更安全,并测试它是否会在某种标准使用水平上变得安全。
    • @jfriend00 库(例如下划线、lodash 甚至 jquery)依赖于从对象返回包装器的通用函数(例如 _$)。例如,如果您的库名为 toto,您可以提供 toto(anything).extendedMethod()
    【解决方案8】:

    除了讨论之外,这不是好的做法,也不是像包装构造函数那样的常用方法,您应该通过询问构造函数的名称来实现这一点:

    Object.defineProperty(Object.prototype, 'isString', {
      value: function() { return this.constructor.name === "String"; }
    });
    

    或者使用已经提到的 instanceof 方法:

    Object.defineProperty(Object.prototype, 'isString', {
      value: function() { return this instanceof String; }
    });
    

    this post 中解释了为什么您的方法不起作用。

    如果您希望新定义的属性可枚举、可配置或可写,您应该查看docs for defineProperty

    【讨论】:

      【解决方案9】:

      正如其他一些人指出的那样,您的代码几乎是正确的,除了 typeof this === 'string' 部分,由于 JavaScript 在原语与对象方面的古怪行为,该部分不起作用。测试对象是否为字符串的最可靠方法之一是使用Object.prototype.toString.call(this) === '[object String]'(查看this article)。考虑到这一点,您可以像这样简单地编写isString 的实现:

      Object.prototype.isString = function () {
          return Object.prototype.toString.call(this) === '[object String]';
      };
      
      "abc".isString(); // ==> true
      "".isString(); // ==> true
      1..isString(); // ==> false
      {}.isString(); // ==> false    
      

      【讨论】:

        【解决方案10】:

        这是因为字符串字面量是原生字符串类型,实际上并不是 String 对象的实例,所以实际上你不能从 Object 或 String 原型调用任何方法。

        发生的情况是,当您尝试对字符串类型的变量调用任何方法时,Javascript 会自动将该值强制转换为新构造的字符串对象

        所以

        "abc".isString();
        

        等同于:

        (new String("abc")).isString();
        

        这样做的副作用是您在 isString() 方法中收到的是一个(类型变量)对象,它也是 String 对象的一个​​实例。

        也许你可以通过一个简化的例子更清楚地看到它:

        var a = "Hello";
        console.log(typeof a); // string
        
        var b = new String("Hello");
        console.log(typeof b); // object
        

        顺便说一句,正如许多其他人所说,您必须在函数中检测字符串的最佳机会是检查它是否是 String 对象的实例:

        foo instanceof String
        

        如果你还想检查其他可能的类型,你应该像下面这样仔细检查:

        function someOtherFn(ins) {
            var myType = typeOf ins;
            if (myType == "object" && ins instanceof String) myType = "string";
            // Do more stuf...
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-05-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-26
          相关资源
          最近更新 更多