你认为每个程序员都应该知道JavaScript的哪些“隐藏特性”?

在看到以下问题的优质答案后,我认为是时候向JavaScript请求它了。

HTML的隐藏特性 CSS的隐藏特性 PHP的隐藏特性 ASP的隐藏特性。网 c#的隐藏特性 Java的隐藏特性 Python的隐藏特性

尽管JavaScript可以说是目前最重要的客户端语言(问问谷歌就知道了),但令人惊讶的是,大多数web开发人员很少意识到它的强大。


当前回答

我提交的第一个特性与其说是一个隐藏特性,不如说是一个很少使用的属性重定义特性应用程序。因为可以重新定义对象的方法,所以可以缓存方法调用的结果,这在计算开销很大且希望延迟求值的情况下非常有用。这是记忆的最简单形式。

function Circle(r) {
    this.setR(r);
}

Circle.prototype = {
  recalcArea: function() {
        this.area=function() {
            area = this.r * this.r * Math.PI;
            this.area = function() {return area;}
            return area;
        }
    },
  setR: function (r) {
      this.r = r;
      this.invalidateR();
    },
  invalidateR: function() {
        this.recalcArea();
    }
}

重构将结果缓存到方法中的代码,你会得到:

Object.prototype.cacheResult = function(name, _get) {
  this[name] = function() {
    var result = _get.apply(this, arguments);
    this[name] = function() {
      return result;
    }
    return result;
  };
};

function Circle(r) {
    this.setR(r);
}

Circle.prototype = {
  recalcArea: function() {
        this.cacheResult('area', function() { return this.r * this.r * Math.PI; });
    },
  setR: function (r) {
      this.r = r;
      this.invalidateR();
    },
  invalidateR: function() {
        this.recalcArea();
    }
}

如果你想要一个记忆函数,你可以用那个代替。不涉及属性重定义。

Object.prototype.memoize = function(name, implementation) {
    this[name] = function() {
        var argStr = Array.toString.call(arguments);
        if (typeof(this[name].memo[argStr]) == 'undefined') {
            this[name].memo[argStr] = implementation.apply(this, arguments);
        }
        return this[name].memo[argStr];
    }
};

注意,这依赖于标准数组toString转换,通常不能正常工作。解决这个问题留给读者练习。

第二个提交的是getter和setter。我很惊讶他们还没有被提及。因为官方标准不同于事实上的标准(defineProperty vs. define[GS]字母),而且Internet Explorer几乎不支持官方标准,所以它们通常没什么用。也许这就是他们没有被提及的原因。注意,你可以很好地结合getter和结果缓存:

Object.prototype.defineCacher = function(name, _get) {
    this.__defineGetter__(name, function() {
        var result = _get.call(this);
        this.__defineGetter__(name, function() { return result; });
        return result;
    })
};

function Circle(r) {
    this.r = r;
}

Circle.prototype = {
  invalidateR: function() {
        this.recalcArea();
    },
  recalcArea: function() {
        this.defineCacher('area', function() {return this.r * this.r * Math.PI; });
    },
  get r() { return this._r; }
  set r(r) { this._r = r; this.invalidateR(); }
}

var unit = new Circle(1);
unit.area;

有效地组合getter, setter和结果缓存有点混乱,因为你必须防止在set上自动失效或不自动失效,这就是下面的例子所做的。如果改变一个属性会使其他多个属性失效,这是一个主要的问题(假设在这些示例中有一个“diameter”属性)。

Object.prototype.defineRecalcer = function(name, _get) {
  var recalcFunc;
  this[recalcFunc='recalc'+name.toCapitalized()] = function() {
    this.defineCacher(name, _get);
  };
  this[recalcFunc]();
  this.__defineSetter__(name, function(value) {
      _set.call(this, value);
      this.__defineGetter__(name, function() {return value; });
  });
};

function Circle(r) {
    this.defineRecalcer('area',
             function() {return this.r * this.r * Math.PI;},
             function(area) {this._r = Math.sqrt(area / Math.PI);},
    );
    this.r = r;
}

Circle.prototype = {
  invalidateR: function() {
        this.recalcArea();
    },
  get r() { return this._r; }
  set r(r) { this._r = r; this.invalidateR(); }
}

其他回答

如果你试图沙盒javascript代码,并禁用所有可能的方法来求值字符串到javascript代码中,要注意阻塞所有明显的eval/document。Function/setTimeout/setInterval/innerHTML和其他DOM操作是不够的。

给定任何对象o, o.constructor.constructor("alert('hi')")()将弹出一个警告对话框,其中包含单词"hi"。

可以写成

var Z="constructor";
Z[Z][Z]("alert('hi')")();

有趣的东西。

语法糖:内联for循环闭包

var i;

for (i = 0; i < 10; i++) (function ()
{
    // do something with i
}());

几乎打破了Douglas Crockford的所有代码惯例,但我认为它看起来很漂亮,从未减少:)


选择:

var i;

for (i = 0; i < 10; i++) (function (j)
{
    // do something with j
}(i));

你可以在左边使用[]来分配局部变量。如果你想从一个函数中返回多个值,而不需要创建一个不必要的数组,这个方法很方便。

function fn(){
    var cat = "meow";
    var dog = "woof";
    return [cat,dog];
};

var [cat,dog] = fn();  // Handy!

alert(cat);
alert(dog);

它是核心JS的一部分,但不知为何,直到今年我才意识到。

还有一个几乎不为人知的JavaScript语法:

var a;
a=alert(5),7;
alert(a);    // alerts undefined
a=7,alert(5);
alert(a);    // alerts 7

a=(3,6);
alert(a);    // alerts 6

更多信息请点击这里。

大多数时候可以使用对象而不是开关。

function getInnerText(o){
    return o === null? null : {
        string: o,
        array: o.map(getInnerText).join(""),
        object:getInnerText(o["childNodes"])
    }[typeis(o)];
}

更新:如果你担心提前评估的情况是低效的(为什么你在程序设计的早期就担心效率??),那么你可以这样做:

function getInnerText(o){
    return o === null? null : {
        string: function() { return o;},
        array: function() { return o.map(getInnerText).join(""); },
        object: function () { return getInnerText(o["childNodes"]; ) }
    }[typeis(o)]();
}

这比输入(或读取)开关或对象更麻烦,但它保留了使用对象而不是开关的好处,详情见下面的评论部分。这种风格还使它在足够成熟后更直接地旋转成一个适当的“类”。

update2:带有ES的建议语法扩展。接下来,这变成了

let getInnerText = o -> ({
    string: o -> o,
    array: o -> o.map(getInnerText).join(""),
    object: o -> getInnerText(o["childNodes"])
}[ typeis o ] || (->null) )(o);