Chinaunix首页 | 论坛 | 博客
  • 博客访问: 395646
  • 博文数量: 69
  • 博客积分: 1984
  • 博客等级: 上尉
  • 技术积分: 953
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-28 00:43
个人简介

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: JavaScript

2015-05-29 17:49:02

原文地址:


since: v0.9

dojo/_base/lang 包含多个函数用于支持多种泛型以及语言结构,dojo toolkit 的其他部分都是基于此。

用法

同其他基于 dojo/_base 的模块类似,如果你使用传统方式运行 Dojo loader (即使用参数 async:false),则本模块可以自动加载。在模块自动加载完毕后,你还可以随时通过 require 来引用此模块,访问其函数:

  1. require(["dojo/_base/lang"], function(lang){
  2.   // lang now contains the module features
  3. });

特性

clone() - 克隆

克隆 objects 或 nodes,返回一个新的实体出来(具体看输入的是什么)。注意返回的是实体,不是引用。即你给 clone()传入任何实体,它就创造并返回该实体的一个新版本。


  1. require(["dojo/_base/lang"], function(lang){
  2.   // clone an object
  3.   var obj = { a:"b", c:"d" };
  4.   var thing = lang.clone(obj);
  5.   // clone an array
  6.   var newarray = lang.clone(["a", "b", "c"]);
  7. });

Usage

 

有时你会想克隆一个 DOM 节点。最简单的方法就是用  定位 DOM 节点,确保在克隆后修改其 id(因为 ID 在文档中是唯一的,而且文档就是使用 ID 来组织的)。


  1. require(["dojo/_base/lang", "dojo/dom", "dojo/dom-attr"], function(lang, dom, attr){
  2.   var node = dom.byId("someNode");
  3.   var newnode = lang.clone(node);
  4.   attr.set(newnode, "id", "someNewId");
  5. });

If you have a pointer to some node already, or want to avoid IDs all together,  may be useful:

  1. require(["dojo/_base/lang", "query()", "dojo/dom-construct", "dojo/_base/window"], function(lang, query, ctr, win){
  2.   // get a reference to some node
  3.   var n = query(".someNode")[0];
  4.   // create 10 clones of this node and append to body
  5.   var i = 10;
  6.   while(i--){
  7.     ctr.place(lang.clone(n), win.body());
  8.   }
  9. });

clone() 总是“深度复制”的。 基于速度及空间考虑,Cyclic (e.g., circular or DAG) 环路引用是明确不支持的。
  • 如果你想对某个 object 做个浅拷贝: y = lang.mixin({}, x);
  • 如果你想对某个数组做个浅拷贝: y = arrayUtil.map(x, "return value;");
  • 其他的场合就需要做深度拷贝了: y = lang.clone(x);

TODOC clone and event objects.

delegate() -委派


根据传入的 object  生成一个新的对象,这个新的对象没有的属性都会到传入的  object 中去查找,你可以传入一组属性来设置返回的新对象。

这是 JavaScript 中 Boodman/Crockford 委派模式的一个实现子集。一个中间对象构造器会调解返回对象的原型链,当对象本身某个属性找不到的时候,会向下委派到 delegate() 中传入的那个对象进行查找。这个非常类似于 ES4 中的 swrap() ,除了它是基于纯粹的对象而非 types 来运作的。

  1. require(["dojo/_base/lang", function(lang){
  2.   var myNewObject = lang.delegate(anOldObject, { myNewProperty: "value or text"});
  3. });

此方法的函数签名如下:

Name 类型 描述
obj Object The object to delegate to for properties not found directly on the return object or in props.
props Object... An object containing properties to assign to the returned object.

Usage


  1. require(["dojo/_base/lang", function(lang){
  2.   var anOldObject = { bar: "baz" };
  3.   var myNewObject = lang.delegate(anOldObject, { thud: "xyzzy"});
  4.   myNewObject.bar == "baz"; // delegated to anOldObject
  5.   anOldObject.thud == undefined; // by definition
  6.   myNewObject.thud == "xyzzy"; // mixed in from props
  7.   anOldObject.bar = "thonk";
  8.   myNewObject.bar == "thonk"; // still delegated to anOldObject's bar
  9. });

exists()


Check if all objects in a dot-separated string object path exist, such as "A.B.C".

exists() is a convenience function, particularly useful for testing long object paths. It accepts a string as its first parameter, and walks down the path it represents. You can optionally provide a root for the path as a second parameter, otherwise it will use a default value of the global object. Each portion of the .delimited string is tested for defined-ness, returning true only if each object exists as defined in the strong.

  1. require(["dojo/_base/lang"], function(lang){
  2.   if( lang.exists("myns.widget.Foo") ){
  3.     console.log("myns.widget.Foo exists");
  4.   }
  5. });

The second root parameter is optional, exists() will use the value of  by default (which is usually the current window). You can use it to root the path in a different window object, or a particular namespace:


  1. require(["dojo/_base/lang", "dijit/dijit"], function(lang, dijit){
  2.   var widgetType = "form.Button";
  3.   var myNamespace = docs;

  4.   if( lang.exists(widgetType, myNamespace) ){
  5.     console.log("There's a docs.form.Button available");
  6.   }else if( lang.exists(widgetType, dijit) ){
  7.     console.log("Dijits form.Button class is available");
  8.   }else{
        console.log("No form.Button classes are available");
  9.   }
  10. });

 

extend()

extend() works much like , though works directly on an object’s prototype. extend() mixes members from the right-most object into the first object, modifying the object directly.

This can be used to extend functionality into existing classes. Consider the following:


require(["dojo/_base/lang", "dijit/TitlePane"], function(lang, TitlePane){ lang.extend(TitlePane, { randomAttribute:"value" }); }); 

The way the  works, a custom attribute on the node will be recognized, as in the interest of performance, only declared members are mixed as part of the parsing process. Before the above extend()call, this sample would not recognize the follow markup:


 data-dojo-type="dijit/TitlePane" data-dojo-props="randomAttribute:'newValue'">

After the extend, any new instances of a dijit/TitlePane will have the randomAttribute member mixed into the instance. extend() affects all future instances of a class or prototyped Object.

Extending dijit/_WidgetBase

A potentially confusing result of the above actually provides us a lot of flexibility. All Dijit widgets inherit from  in one way or another. Some widgets, like the  can contain arbitrary widgets, though require a region parameter on the contained widget, though rather than manually adding a region parameter to each declaration across Dijit, the BorderContainer simply extendsdijit/_WidgetBase with the member, and anyone using any widget within a BorderContainer can specify aregion:


require(["dojo/_base/lang", "dijit/_WidgetBase"], function(lang, _WidgetBase){ lang.extend(_WidgetBase, { region: "center" }); }); 

The side-effect of this is a documentation nightmare. Now every widget appears to have a region variable, when in fact it is just there for the benefit of BorderContainer. As a side note, this has been addressed in the API Viewer and other documentation as “extension” properties, methods and events and can be easily identified and filtered out.

extend() vs. mixin()


require(["dojo/_base/lang", "dojo/json"], function(lang, json){ // define a class var myClass = function(){ this.defaultProp = "default value"; }; myClass.prototype = {}; console.log("the class (unmodified):", json.stringify(myClass.prototype)); // extend the class lang.extend(myClass, {"extendedProp": "extendedValue"}); console.log("the class (modified with lang.extend):", json.stringify(myClass.prototype)); var t = new myClass(); // add new properties to the instance of our class lang.mixin(t, {"myProp": "myValue"}); console.log("the instance (modified with lang.mixin):", json.stringify(t)); }); 

getObject()

getObject() returns the property of an object from a dot-separated string such as A.B.C.

The simplest way to use getObject() is to pass a dot-separated string as shown below:


require(["dojo/_base/lang"], require(lang){ // define an object (intentionally global to demonstrate) foo = { bar: "some value" }; lang.getObject("foo.bar"); // returns "some value" }); 

getObject() also takes an optional boolean parameter which, if true, will create the property if it does not exist. Any other properties along the path will also be created along the way. The default value is false.


require(["dojo/_base/lang"], function(lang){ // define an object (intetionally global to demonstrate) foo = { bar: "some value" }; // get the "foo.baz" property, create it if it doesn't exist lang.getObject("foo.baz", true); // returns foo.baz - an empty object {} /*  foo == {  bar: "some value",  baz: {}  }  */ }); 

You can also pass an object as the third parameter. This will define the context in which to search for the property. By default, the context is .


require(["dojo/_base/lang"], function(lang){ // define an object var foo = { bar: "some value" }; // get the "bar" property of the foo object lang.getObject("bar", false, foo); // returns "some value" }); 

hitch()

hitch() returns a function that will execute a given function in a given context. This function allows you to control how a function executes, particularly in asynchronous operations. Sometimes code will be written like this:


require(["dojo/on"], function(on){ var processEvent = function(e){ this.something = "else"; }; on(something, "click", processEvent); }); 

Only to have it fail with a cryptic error about an unresolved variable? Why does that occur? Well, because in asynchronous callbacks such as above, the context that the code is executing in has changed. It will no longer refer to the object that originally provided it, but its context will now refer to the enclosing object, the callback. To get around this, you can use hitch() to force the function to retain its original context. The same code done properly will look like:


require(["dojo/on", "dojo/_base/lang"], function(on, lang){ var processEvent = function(e){ this.something = "else"; }; on(something, "click", lang.hitch(this, processEvent)); }); 

And now when the event fires and runs the function, this will refer to the context that is expected.

Examples

RunSource

A simple example.


require(["dojo/_base/lang"], function(lang){ var myObj = { foo: "bar" }; var func = lang.hitch(myObj, function(){ console.log(this.foo); }); func(); }); 

Looking in the console, bar should be printed. That is because the scope provided in hitch() was myObj, so inside the function, this refers to myObj.

To call a method in a given context that is already in scope, just the method name as a string can be passed as the second argument:

RunSource

Passing method name as string.


require(["dojo/_base/lang"], function(lang){ var myObj = { foo: "bar", method: function(someArg){ console.log(this.foo); } }; var func = lang.hitch(myObj, "method"); func(); }); 

The console output should be bar.

Arguments can also be passed to the function that is being called:

RunSource

Passing arguments to a function.


require(["dojo/_base/lang"], function(lang){ var myObj = { foo: "bar", method: function(someArg){ console.log(someArg + " " + this.foo); } }; var func = lang.hitch(myObj, "method", "baz"); func(); }); 

The output in the console should be baz bar. Any arguments provided after the first two will be passed to the function.

mixin()

mixin() is a simple utility function for mixing objects together. Mixin combines two objects from right to left, overwriting the left-most object, and returning the newly mixed object for use. mixin() is very similar to  but only works on objects, whereas extend explicitly extends an object’s prototype.

Note: In case of nested objects and arrays, mixin does not combine, only overwrite.

Simple Mixes

Merge two objects (join two objects) together with mixin():


require(["dojo/_base/lang"], function(lang){ var a = { b: "c", d: "e" }; lang.mixin(a, { d: "f", g: "h" }); console.log(a); // b: c, d: f, g: h }); 

This example overwrites the d member from the second object, leaving the variable a with three members: b, d, and g. To expand on this, we can illustrate how to use mixin to overwrite defaults for some function:


require(["dojo/_base/lang", "dojo/_base/fx"], function(lang, baseFx){ var generatedProps = { node: "someNode", onEnd: function(){ /*code*/ } }; var defaultProps = { duration: 1000 }; baseFx.fadeIn(lang.mixin(generatedProps, defaultProps)).play(); }); 

This will create and play a fadeIn animation passing and onEnd function and node, using a default duration.

Creating New Objects

Mixin modifies the first object in the list, mixing in second object. If you wish to make an entirely new object from the mixed results, you have a couple options. First, clone the existing object with , and then mix:


require(["dojo/_base/lang"], function(lang){ var newObject = lang.mixin(lang.clone(a), b); }); 

Here, the return from clone() is a new object, then b is mixed in.

Alternately, you can pass an empty object as the first mix, and mix another object into it. You can then repeat this pattern as often as you’d like:


require(["dojo/_base/lang"], function(lang){ var newObject = lang.mixin({}, b); lang.mixin(newObject, c); lang.mixin(newObject, lang.mixin(e, f)); // and so on }); 

Just remember the object instance in the first position will always be overwritten, and the right-most object will take precedence in the mix.

Mixins with Classes

A common pattern when creating class objects is to pass an object-hash of properties to the constructor.mixin() provides a technique for easy override of default in you own classes. Consider the follow class declaration:


define(["dojo/_base/lang", "dojo/_base/declare"], function(lang, declare){ var Thinger = declare(null, { defaultValue: "red", constructor: function(args){ lang.mixin(this, args); } }); return Thinger; }); 

Now, any time we create a new instance of a Thinger, it will have a member variable defaultValue set to red. If we provide a new defaultValue, the constructor will immediately overwrite the existing one:


require(["my/Thinger"], function(Thinger){ var thing = new Thinger({ defaultValue: "blue" }); }); 

Mixing into Instances

Sometimes is it useful to mix custom variables and members into instances of widgets and other objects. Mixing into an instance allows you to easily add arbitrary references or overwrite functionality after instantiation.


require(["dojo/_base/lang", "dijit/layout/ContentPane"], function(lang, ContentPane){ var cp = new ContentPane(); lang.mixin(cp, { _timeCreated: new Date() }); }); 

Now, that instance of the ContentPane as a Date object attached in the _timeCreated member, which is accessible to the widget as this._timeCreated.

Mixing Methods

If you want to mix in some methods into an instance using two previous techniques, be aware that decorates them, while mixin() does not, which may affect how this.inherited()works, if used in mixed-in methods. Use , which correctly handles all properties in dojo/_base/declare-compatible way.

partial()

partial() is related to  in that it is a function that returns a function. What it does is allow manipulation of the arguments being passed to a function. It allows the first n arguments to be fixed to a specific value, but the remaining arguments to vary.

Let’s take a quick look at a pseudo-code example of using partial:


require(["dojo/request"], function(request){ var dataLoaded = function(someFirstParam, data, ioArgs){}; request.get("foo").then(dataLoaded); }); 

Okay, so that will invoke the dataLoaded function when the request.get() function is fullfulled... but the success callback expects to pass on data, ioArgs. So how the heck do we make sure that the expectations are honored even with that new first param called someFirstParam? Use partial(). Here’s how you would do it:


require(["dojo/_base/lang", "dojo/request"], function(lang, request){ var dataLoaded = function(someFirstParam, data, ioargs){}; request.get("foo").then(lang.partial(dataLoaded, "firstValue")); }); 

What that does is create a new function that wraps dataLoaded and affixes the first parameter with the value firstValue. Note that partial() allows you to do n parameters, so you can keep defining as many values as you want for fixed-value parameters of a function.

Example

RunSource

Let’s look at a quick running example:


require(["dojo/_base/lang", "dojo/dom", "dojo/dom-construct", "dojo/on", "dojo/domReady!"], function(lang, dom, domConst, on){ var myClick = function(presetValue, event){ domConst.place("
										

"

+ presetValue + "


", "appendLocation"); domConst.place(" ", "appendLocation"); }; on(dom.byId("myButton"), "click", lang.partial(myClick, "This is preset text!")); });


 type="button" id="myButton">Click me to append in a preset value!  id="appendLocation">

replace()

This function provides a light-weight foundation for substitution-based templating. It is a sane alternative to string concatenation technique, which is brittle and doesn’t play nice with localization.

With Dictionary

If the second argument is an object, all names within braces are interpreted as property names within this object. All names separated by . (dot) will be interpreted as sub-objects. This default behavior provides greater flexibility:

RunSource

require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){ dom.byId("output").innerHTML = lang.replace( "Hello, {name.first} {name.last} AKA {nick}!", { name: { first: "Robert", middle: "X", last: "Cringely" }, nick: "Bob" } ); }); 


 id="output">
																	



You don’t need to use all properties of an object, you can list them in any order, and you can reuse them as many times as you like.

With Array

In most cases you may prefer an array notation effectively simulating the venerable printf:

RunSource

require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){ dom.byId("output").innerHTML = lang.replace( "Hello, {0} {2} AKA {3}!", ["Robert", "X", "Cringely", "Bob"] ); }); 


 id="output">
																	



With a Function

For ultimate flexibility you can use replace() with a function as the second argument.

Essentially these arguments are the same as in String.replace() when a function is used. Usually the second argument is the most useful one.

Let’s take a look at example where we are calculating values lazily on demand from a potentially dynamic source.

This code in action:

RunSource

require(["dojo/_base/array", "dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(array, lang, dom){ // helper function function sum(a){ var t = 0; array.forEach(a, function(x){ t += x; }); return t; } dom.byId("output").innerHTML = lang.replace( "{count} payments averaging {avg} USD per payment.", lang.hitch( { payments: [11, 16, 12] }, function(_, key){ switch(key){ case "count": return this.payments.length; case "min": return Math.min.apply(Math, this.payments); case "max": return Math.max.apply(Math, this.payments); case "sum": return sum(this.payments); case "avg": return sum(this.payments) / this.payments.length; } } ) ); }); 


 id="output">
																	



With Custom Pattern

In some cases you may want to use different braces, for example because your interpolated strings contain patterns similar to {abc}, but they should not be evaluated and replaced, or your server-side framework already uses these patterns for something else. In this case you should replace the pattern:

RunSource

require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){ dom.byId("output").innerHTML = lang.replace( "Hello, %[0] %[2] AKA %[3]!", ["Robert", "X", "Cringely", "Bob"], /\%\[([^\]]+)\]/g ); }); 


 id="output">
																	



It is advised for the new pattern to be:

  • Global
  • It should capture one substring, usually some text inside “braces”.

Escaping Substitutions

This example escapes substituted text for HTML to prevent possible exploits. Dijit templates implement similar technique. We will also borrow Dijit syntax: where all names starting with ! are going to be placed as is (e.g., {!abc}), while everything else is going to be escaped.

RunSource

require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){ function safeReplace(tmpl, dict){ // convert dict to a function, if needed var fn = lang.isFunction(dict) ? dict : function(_, name){ return lang.getObject(name, false, dict); }; // perform the substitution return lang.replace(tmpl, function(_, name){ if(name.charAt(0) == '!'){ // no escaping return fn(_, name.slice(1)); } // escape return fn(_, name). replace(/&/g, "&"). replace(/, "<"). replace(/>/g, ">"). replace(/"/g, '"'); }); } // we don't want to break the Code Glass widget here var bad = "{script}alert('Let\' break stuff!');{/script}"; // let's reconstitute the original bad string bad = bad.replace(/\{/g, "<").replace(/\}/g, ">"); // now the replacement dom.byId("output").innerHTML = safeReplace("
{0}, [bad]); });


 id="output">Hello

Formatting Substitutions

Let’s add a simple formatting to substituted fields. We will use the following notation in this example:

  • {name} - use the result of substitution directly.
  • {name:fmt} - use formatter fmt to format the result.
  • {name:fmt:a:b:c} - use formatter fmt with optional parameters a, b, and c. Any number of parameters can be used. Their interpretation depends on a formatter.

In this example we are going to format numbers as fixed or exponential with optional precision.

RunSource

require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){ function format(tmpl, dict, formatters){ // convert dict to a function, if needed var fn = lang.isFunction(dict) ? dict : function(_, name){ return lang.getObject(name, false, dict); }; // perform the substitution return lang.replace(tmpl, function(_, name){ var parts = name.split(":"), value = fn(_, parts[0]); if(parts.length > 1){ value = formatters[parts[1]](value, parts.slice(2)); } return value; }); } // simple numeric formatters var customFormatters = { f: function(value, opts){ // return formatted as a fixed number var precision = opts && opts.length && opts[0]; return Number(value).toFixed(precision); }, e: function(value, opts){ // return formatted as an exponential number var precision = opts && opts.length && opts[0]; return Number(value).toExponential(precision); } }; // that is how we use it: var output1 = format( "pi = {pi}
pi:f = {pi:f}
pi:f:5 = {pi:f:5}"
, {pi: Math.PI, big: 1234567890}, customFormatters ); dom.byId("output1").innerHTML = format( "pi = {pi}
pi:f = {pi:f}
pi:f:5 = {pi:f:5}"
, {pi: Math.PI, big: 1234567890}, customFormatters ); dom.byId("output2").innerHTML = format( "big = {big}
big:e = {big:e}
big:e:5 = {big:e:5}"
, {pi: Math.PI, big: 1234567890}, customFormatters ); });


 id="output1">
																


id="output2">



setObject()

Set a property from a dot-separated string, such as A.B.C. In JavaScript, a dot separated string likeobj.parent.child refers to an item called child inside an object called parent inside of obj. setObject() will let you set the value of child, creating the intermediate parent objects if they don’t exist.

Without setObject(), it is often handle like this:


// ensure that intermediate objects are available if(!obj["parent"]){ obj.parent ={}; } if(!obj.parent["child"]){ obj.parent.child={}; } // now we can safely set the property obj.parent.child.prop = "some value"; 

Whereas with setObject(), we can shorten that to:


require(["dojo/_base/lang"], function(lang){ lang.setObject("parent.child.prop", "some value", obj); }); 

trim()

This function implements a frequently required functionality: it removes white-spaces from both ends of a string. This functionality is part of ECMAScript 5 standard and implemented by some browsers. In this case trim() delegates to the native implementation. More information can be found here: String.trim() at MDC.

trim() implementation was informed by Steven Levithan’s blog post. It was chosen to implement the compact yet performant version. If your application requires even more speed, check out, which implements the fastest version.

RunSource

require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){ function show(str){ return "|" + lang.trim(str) + "|"; } dom.byId("output1").innerHTML = show("   one"); dom.byId("output2").innerHTML = show("two "); dom.byId("output3").innerHTML = show("   three "); dom.byId("output4").innerHTML = show("\tfour\r\n"); dom.byId("output5").innerHTML = show("\f\n\r\t\vF I V E\f\n\r\t\v"); }); 


 id="output1">
																


id="output2">


id="output3">


id="output4">


id="output5">



The following methods are deprecated. See  for advice on how to differentiate between different types of objects without using methods(). The methods below are deprecated:

  • isString()

    Checks if the parameter is a String

  • isArray()

    Checks if the parameter is an Array

  • isFunction()

    Checks if the parameter is a Function

  • isObject()

    Checks if the parameter is a Object

  • isArrayLike()

    Checks if the parameter is like an Array

  • isAlien()

    Checks if the parameter is a built-in function


阅读(1397) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~