Adding the replaceAll method to the ActionScript S

2019-05-31 10:59发布

问题:

I need some help in adding the replace all functionality into my Flex project. I would prefer doing this in a way that is as natural as possible.

What I want to achieve is to be able to run this code (with the Flex compiler) "aabbaaba".replaceAll("b","c") and get "aaccaaca". Also I want to chain replaceAll calls.
Note: I won't be actually replacing bs with cs, but various string that will not be known at coding time!

What I don't want:
1. Use regular expressions with the global flag. The tokens to replace are determined ad run time and transforming them into a regular expression isn't straight forward.
2. Use the StringUtil.replace method. It's a static method and chaining is ugly.
3. Chain split and join. Because it may be confusing for others when reading the code.
4. Disable strict type checking. I want to have type checking for the rest of my code.


Here is what I have so far:

String.prototype.replaceAll = function(replace:String, replaceWith:String):String{
    return this.split(replace).join(replaceWith);
}

And can be called like this:

"aababacaaccb"["replaceAll"]("b", "c")["replaceAll"]("c", "a");

Also, are there any recommendations against extending objects through the prototype? I will also accept an answer that has strong arguments against extending the String object through the prototype.

Thank you,
Alin

回答1:

I think you got all the technical answers possible. I will elaborate in what I think is the best way to approach this language-wise.

Prototypes are not recommended in an OOP language such as AS3 (mostly because they defy encapsulation). You imply that you don't want something "confusing to others" (in relation to split.join); well, prototypes in AS3 are very confusing. Just as an example of this, a prototype declaration can be done from anywhere in your code, and as such is not obvious where it should reside. If "others" encounter "foo".replaceAll() in your code, it's not at all obvious where one can find that method and check what it really does.

A static function amends this and is pretty straight forward. Sure, you need an extra argument, and you can't really chain properly, but is this such a bad thing?

If you need performance, split.join is the way to go. I would bet good money that more AS3 devs know about split.join than the use of prototypes.

On the other hand, I think the most semantic and pragmatic way would be to use the own language method (hence my previous answer). You are trying to replace all needles in a string with another string in AS3, and for that the language has the String::replace method with the global flag. I'm pretty sure there is a way to easily parse and use any string in regexp.

I concur that in some cases a helper method (like replaceAll) could be necessary, but I would strongly encourage you to not use prototypes, and instead use a more standard way, like StringUtil.replace.



回答2:

The second answer is this:

write the wrapper class StringEx for String and you can define replaceAll to be chained like this

public function replaceAll(replace:String, replaceWith:String):StringEx {
   string = string.replace(new RegExp(replace, 'g'), replaceWith);
   return this;
}

by providing toString() you can easily change StringEx object into String

var result:String = "" + new StringEx("aaccaaca").replaceAll('b', 'c').replaceAll('c', 'a');

you can get full-version here: OOP way of prototype extension - wonderfl build flash online



回答3:

What I don't want: 1. Use regular expressions with the global flag

Why not? for simple string replacements is quite straight forward and performant:

"aababacaaccb".replace(/b/g, "c");

or if you prefer:

var needle:String="b";
"aababacaaccb".replace(new RegExp(needle,"g"), "c");

I wouldn't recommend using prototype for this, it's not very OOP nor standard... it feels too hacky for such a simple operation.



回答4:

I can't think of a way to meet the 4 requirements you posted. But I think if that your main goal is replacing various tokens in one go (what you wanted to achieve with chaining calls) while being able to use any string as a token, you can try something like this:

public class StringUtil {
    public static function replaceAll(source:String,map:Object):String {
        for(var replaceToken:String in map) {
            source = source.split(replaceToken).join(map[replaceToken]);
        }
        return source;
    }
}

var str:String = "a1ccca111a";

str = StringUtil.replaceAll(str,{
    'a' : 'b',
    '1' : '2'
});
trace(str);

There's one caveat to keep in mind. With an Object object, the order is not guaranteed. In most cases, this isn't important, but if in your case it is, you could use an Array instead, and in each slot store both the token and its replacement (as an Object, for instance).



回答5:

"aabbaaba".split('b').join('c') returns 'aaccaaca'

UPD:
i wonder why it's unacceptable for you (however it doesn't support regexp): it works pretty fast (checked it in severe loops) and does exactly what you want.

and also there's a couple of solutions that's not mentioned in your blacklist (btw static function is better then messing with prototype - imho):

  • you can extend String class with only replaceAll method and super() call. instances of this class will return true if you check (myStringInstance is String)
  • and another solution: create a wrapper instead of extending: a class that will store a string for you providing additional functionality (though i can't imagine one for a string) and maybe making some of its' properties read-only. or it might have just a getter and setter for one


回答6:

I have two answers for you. The first would be what you want. But I recommend the second.

enabling prototypes in flex is very easy and can be done by setting flex-config

ReplaceAllTest.as

package {
    import flash.display.Sprite;
    import flash.text.TextField;
    public class ReplaceAllTest extends Sprite
    {
        public function ReplaceAllTest() 
        {
            var tf:TextField = new TextField;
            String.prototype.replaceAll = function(replace:String, replaceWith:String):String{
                return this.split(replace).join(replaceWith);
            }
            // now the strict mode is off compiler does NOT warn the following code
            tf.text = "aababacaaccb".replaceAll("b", "c")
                                    .replaceAll("c", "a");
            addChild(tf);
        }
    }
}

ReplaceAllTest-config.xml

<?xml version="1.0" encoding="utf-8" ?>
<flex-config>
    <compiler>
       <as3>false</as3>
       <es>true</es>
       <strict>false</strict>
    </compiler>
    <static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries>
</flex-config>

I still discourage this way, because enabling prototyping and turning off strict mode will slow down your code.



回答7:

I know this is an old thread, but this is what I came up with which can do any regular string.

    public static function replaceAll(p_string:String, p_search:String, p_replaceWith:String):String{

    //Dummy Control
        if(p_string == null)
            return '';

    //Iterates through the string from right to left so we don't go over what we are changing.
        var index:int = p_string.lastIndexOf(p_search);
        while(index != -1){

        //Splits the string at the index
            p_string = p_string.slice(0, index) + p_replaceWith + p_string.slice(index+p_search.length);

        //Attempts to find the next index
            index = p_string.lastIndexOf(p_search, index-1);//We -1 so we always move down the line
        }

    //Returns the modified p_string
        return p_string;
    }