CoffeeScript scope issue

2019-05-02 00:05发布

What am I doing wrong here? I'm using the fat arrows => for my callbacks, however when the code reaches cb.onEndElement and calls @returner I get an null object exception. So why doesn't @returner exist?

class Parser
    constructor: () ->
        @returner = (data) ->

    searchParser: new xml.SaxParser (cb) =>
        cb.onStartElementNS (elem, attrs, prefix, url, ns) =>
            if elem is "results" then @results = []
            else if elem is "title" then @curr = "title"
            else @curr = "none"
        cb.onCdata (cdata) =>
            if @curr is "title" then @book.title = cdata
        cb.onEndElementNS (elem, prefix, url) =>
            @results.push @book if elem is "book"
        cb.onEndDocument =>
            @returner @results

    search: (str, callback) ->
        @returner = callback
        @searchParser.parseString str

p = new Parser
p.search "somexml", (data) ->
    console.log JSON.stringify data

2条回答
Deceive 欺骗
2楼-- · 2019-05-02 00:37

First off, the concept you're talking about isn't "scope"—it's this, also informally called the "context." And it's certainly one of the trickiest concepts in JavaScript (and thus CoffeeScript), even though the rules are fairly simple. Perhaps it's because the word this itself, which doesn't seem like its meaning should be so easily changed depending on how a function is called...

Nicholas' answer is dead-on, but I recommend that you also read up on this and try to really understand it, rather than just using => all the time (it's a great tool, but not always the right one). A few resources I recommend:

查看更多
虎瘦雄心在
3楼-- · 2019-05-02 00:41

Your method search needs a fat-arrow => in order to bind it to instances of Parser.

Additionally, although the line searchParser: new xml.SaxParser (cb) => compiles, it's probably not doing what you want, because the fat arrow is binding the callback to Parser and not this. You have two options:

  1. you should probably put @searchParser = new xml.SaxParser (cb) => ... in your constructor instead, given the way you are calling it.
  2. otherwise you could use searchParser: () => new xml.SaxParser (cb) => and call it with parens lower down: @searchParser().parseString str, which would create a searchParser method bound to this

As an example, here are my two solutions, as well as your original line, slightly simplified, as well as the compiled code, for compare and contrast purposes:

Simplified example in CoffeeScript:

class Parser
  constructor: () -> @searchParser1 = new xml.SaxParser (x) => console.log(x)
  searchParser2: () => new xml.SaxParser (x) => console.log(x)
  searchParser: new xml.SaxParser (x) => console.log(x)

Compiled JavaScript:

var Parser;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Parser = (function() {
  function Parser() {
    this.searchParser2 = __bind(this.searchParser2, this);    
    this.searchParser1 = new xml.SaxParser(__bind(function(x) {
      return console.log(x);
    }, this));
  }
  Parser.prototype.searchParser2 = function() {
    return new xml.SaxParser(__bind(function(x) {
      return console.log(x);
    }, this));
  };
  Parser.prototype.searchParser = new xml.SaxParser(__bind(function(x) {
    return console.log(x);
  }, Parser));
  return Parser;
}).call(this);

Note how searchParser1 and searchParser2 have their callbacks bound to this and searchParser's is bound to Parser.

As always, the "Try CoffeeScript" button at the CoffeeScript homepage (http://jashkenas.github.com/coffee-script/) is your friend!

查看更多
登录 后发表回答