原文:http://blog.mgechev.com/2015/03/09/build-learn-your-own-light-lightweight-angularjs/

Build Your own Simplified AngularJS in 200 Lines of JavaScript

My practice proved that there are two good/easy ways to learn a new technology:

  • Re-implement it by your own
  • See how the concepts you already know fit in it

In some cases the first approach is too big overhead. For instance, if you want to understand how the kernelworks it is far too complex and slow to re-implement it. It might work to implement a light version of it (a model), which abstracts components that are not interesting for your learning purposes.

The second approach works pretty good, especially if you have previous experience with similar technologies. A proof for this is the paper I wrote - “AngularJS in Patterns”. It seems that it is a great introduction to the framework for experienced developers.

However, building something from scratch and understanding the core underlying principles is always better. The whole AngularJS framework is above 20k lines of code and parts of it are quite tricky. Very smart developers have worked with months over it and building everything from an empty file is very ambitious task. However, in order to understand the core of the framework and the main design principles we can simplify the things a little bit - we can build a “model”.

Scientific modelling is a scientific activity, the aim of which is to make a particular part or feature of the world easier to understand, define, quantify, visualize, or simulate by referencing it to existing and usually commonly accepted knowledge. It requires selecting and identifying relevant aspects…

We can achieve this simplification by:

  • Simplifying the API
  • Removing components, which are not essential for our understanding of the core concepts

This is what I did in my “Lightweight AngularJS” implementation, which is hosted on GitHub. The code is only with educational purpose and should not be used in production otherwise a kitty somewhere will suffer. I used this method of explaining AngularJS in classes I taught at HackBulgaria and Sofia University. You can also find slides from my talk “Lightweight AngularJS” in the bottom of the blog post.

Before reading the rest of the article I strongly recommend you first to get familiar with the basics of AngularJS. A good start could be this short overview of AngularJS.

Here are some links with code snippets/demos for the following article:

So lets begin with our implementation!

Main Components

Since we are not following the AngularJS implementation completely we will define a set of components and make references to their sources from the original implementation. Although we will not have 100% compatible implementation we will implement most of our framework in the same fashion as it is implemented in AngularJS but with simplified interface and a few missing features.

The AngularJS components we are going to be able to use are:

  • Controllers
  • Directives
  • Services

In order to achieve this functionality we will need to implement the $compile service, which we will call DOMCompiler, the $provider and the $injector, grouped into our component called Provider. In order to have two-way data-binding we will implement the scope hierarchy.

This is how the relation between ProviderScope and DOMCompiler will look like:

Provider

As mentioned above, our provider will union two components from the original framework:

  • $provide
  • $injector

It will be a singleton with the following responsibilities:

  • Register components (directives, services and controllers)
  • Resolve components’ dependencies
  • Initialize components

DOMCompiler

The DOMCompiler is a singleton, which will traverse the DOM tree and find directives. We will support only directive, which could be used as attributes. Once the DOMCompiler finds given directive it will provide scope management functionality (since given directive may require a new scope) and invoke the logic associated to it (in our case the link function). So the main responsibilities of this component will be:

  • Compile the DOM

    • Traverse the DOM tree
    • Finds registered directives, used as attributes
    • Invoke the logic associated with them
    • Manages the scope

Scope

And the last major component in our Lightweight AngularJS, will be the scope. In order to implement the data-binding logic we need to have $scope to attach properties. We can compose these properties into expressions and watch them. When we discover that the value of given expression has changed we can simply invoke a callback (observer) associated with the expression.

Responsibilities of the scope:

  • Watches expressions
  • Evaluates all watched expressions on each $digest loop, until stable
  • Invokes all the observers, which are associated with the watched expression

Theory

In order to have better understanding of the implementation, we need to dig a bit in theory. I’m doing this mostly for completeness, since we will need only basic graph algorithms. If you’re familiar with the basic graph traversal algorithms (Depth-First Search and Breath-First Search) feel free to skip this section.

First of all, what actually graphs are? We can think of given graph as pair of two sets: G = { V, E }, E ⊆ V x V. This seems quite abstract, I believe. Lets make it a bit more understandable. We can think of the set V as different Tinder users and the set E as their matches. For example, if we have the users V = (A, B, C, D) and we have matches between E = ((A, B), (A, C), (A, D), (B, D)), this means not only that A swipes right everyone but also that the edges inside our graph are these matches. Our “social graph” will look like this:

This is an example for undirected graph, since both users like each other. If we have partial match (only one of the users like the other one), we have directed graph. In the case of directed graph, the connections between the nodes will be arrows, to show the direction (i.e. which is the user who is interested in the other one).

Graph theory in AngularJS

But how we can apply graph theory in our AngularJS implementation? In AngularJS instead of users we have components (services, controllers, directives, filters). Each component may depend (use) another component. So the nodes in our AngularJS graph are the different components and the edges are the relations between them. For example, the graph of the dependencies of the $resource service, will look something like:

There are two more places we are going to use graphs - the DOM tree and the scope hierarchy. For example, if we turn the following HTML:

<html>
<head>
</head>
<body>
<p></p>
<div></div>
</body>
</html>

into a tree, we will get:

For discovering all directives in the DOM tree, we need to visit each element and check whether there is registered directive associated with its attributes. How we can visit all nodes? Well, we can use the depth-first search algorithm, which is used in AngularJS:

1  procedure DFS(G,v):
2 label v as discovered
3 for all edges from v to w in G.adjacentEdges(v) do
4 if vertex w is not labeled as discovered then
5 recursively call DFS(G,w)

Implementation

Since we are done with theory, we can begin our implementation!

Provider

As we said the Provider will:

  • Register components (directives, services and controllers)
  • Resolve components’ dependencies
  • Initialize components

So it will has the following interface:

  • get(name, locals) - returns service by its name and local dependencies
  • invoke(fn, locals) - initializes service by its factory and local dependencies
  • directive(name, fn) - registers a directive by name and factory
  • controller(name, fn) - registers a controller by name and factory. Note that controllers are not part of the AngularJS’ core. They are implemented through the $controller service.
  • service(name, fn) - registers a service by name and factory
  • annotate(fn) - returns an array of the names of the dependencies of given service

Registration of components

var Provider = {
_providers: {},
directive: function (name, fn) {
this._register(name + Provider.DIRECTIVES_SUFFIX, fn);
},
controller: function (name, fn) {
this._register(name + Provider.CONTROLLERS_SUFFIX, function () {
return fn;
});
},
service: function (name, fn) {
this._register(name, fn);
},
_register: function (name, factory) {
this._providers[name] = factory;
}
//...
};
Provider.DIRECTIVES_SUFFIX = 'Directive';
Provider.CONTROLLERS_SUFFIX = 'Controller';

The code above provides a simple implementation for registration of components. We define the “private” object called _providers, which contains all factory methods of the registered directives, controllers and services. We also define the methods directiveservice and controller, which delegate their call to _register. In controllerwe wrap the passed controller inside a function for simplicity, since we want to be able to invoke the controller multiple times, without caching the value it returns after being invoked. The method controller will get more obvious after we review the get method and the ngl-controller directive. The only methods left are:

  • invoke
  • get
  • annotate
var Provider = {
// ...
get: function (name, locals) {
if (this._cache[name]) {
return this._cache[name];
}
var provider = this._providers[name];
if (!provider || typeof provider !== 'function') {
return null;
}
return (this._cache[name] = this.invoke(provider, locals));
},
annotate: function (fn) {
var res = fn.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '')
.match(/\((.*?)\)/);
if (res && res[1]) {
return res[1].split(',').map(function (d) {
return d.trim();
});
}
return [];
},
invoke: function (fn, locals) {
locals = locals || {};
var deps = this.annotate(fn).map(function (s) {
return locals[s] || this.get(s, locals);
}, this);
return fn.apply(null, deps);
},
_cache: { $rootScope: new Scope() }
};

We have a little bit more logic here so lets start with get. In get we initially check whether we already have this component cached in the _cache object. If it is cached we simply return it (see singleton). $rootScope is cached by default since we want only one instance for it and we need it once the application is bootstrapped. If we don’t find the component in the cache we get its provider (factory) and invoke it using the invoke method, by passing its provider and local dependencies.

In invoke the first thing we do is to assign an empty object to locals if there are no local dependencies. What are the local dependencies?

Local Dependencies

In AngularJS we can think of two types of dependencies:

  • Local dependencies
  • Global dependencies

The global dependencies are all the components we register using factoryservicefilter etc. They are accessible by each other component in the application. But how about the $scope? For each controller we want a different scope, the $scope object is not a global dependency registered the same way as lets say $http or $resource. The same for $delegate when we create a decorator. $scope and $delegate are local dependencies, specific for given component.

Lets go back to the invoke implementation. After taking care of null or undefined for locals value, we get the names of all dependencies of the current component. Note that our implementation will support resolving of dependencies only declared as parameter names:

function Controller($scope, $http) {
// ...
}
angular.controller('Controller', Controller);

Once we cast Controller into a string we will get the string corresponding to the controllers definition. After that we can simply take all the dependencies’ names using the regular expression in annotate. But what if we have comments in the Controller’s definition:

function Controller($scope /* only local scope, for the component */, $http) {
// ...
}
angular.controller('Controller', Controller);

A simple regular expression will not work here, because invoking Controller.toString() will return the comments as well, so that’s why we initially strip them by using .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '').

Once we get the names of all dependencies we need to instantiate them so that’s why we have the map, which loops over all the strings in the array and calls this.get. Do you notice a problem here? What if we have component A, which depends on B and C and lets say C depends on A? In this case we are going to have infinite loop or so called circular dependency. In this implementation we don’t handle such problems but you can take care of them by using topological sort or keeping track of the visited “nodes” (dependencies).

And that’s our provider’s implementation! Now we can register components like this:

Provider.service('RESTfulService', function () {
return function (url) {
// make restful call & return promise
};
}); Provider.controller('MainCtrl', function (RESTfulService) {
RESTfulService(url)
.then(function (data) {
alert(data);
});
});

And later we can invoke MainCtrl by:

var ctrl = Provider.get('MainCtrl' + Provider.CONTROLLERS_SUFFIX);
Provider.invoke(ctrl);

Pretty cool, ah? And that’s how we have 1/4 of our Lightweight AngularJS implementation!

DOMCompiler

The main responsibility of the DOMCompiler is to:

  • Compile the DOM

    • Traverse the DOM tree
    • Finds registered directives, used as attributes
    • Invoke the logic associated with them
    • Manages the scope

The following API is enough:

  • bootstrap() - bootstraps the application (similar to angular.bootstrap but always uses the root HTML element as root of the application).
  • compile(el, scope) - invokes the logic of all directives associated with given element (el) and calls itself recursively for each child element of el. We need to have a scope associated with the current element because that’s how the data-binding is achieved. Since each directive may create different scope, we need to pass the current scope in the recursive call.

And here is the implementation:

var DOMCompiler = {
bootstrap: function () {
this.compile(document.children[0],
Provider.get('$rootScope'));
},
compile: function (el, scope) {
var dirs = this._getElDirectives(el);
var dir;
var scopeCreated;
dirs.forEach(function (d) {
dir = Provider.get(d.name + Provider.DIRECTIVES_SUFFIX);
if (dir.scope && !scopeCreated) {
scope = scope.$new();
scopeCreated = true;
}
dir.link(el, scope, d.value);
});
Array.prototype.slice.call(el.children).forEach(function (c) {
this.compile(c, scope);
}, this);
},
// ...
};

The implementation of bootstrap is trivial. It delegates its call to compile with the root HTML element. What happens in compile is far more interesting. Initially we use a helper method, which gets all directives associated to the given element. We will take a look at _getElDirectives later. Once we have the list of all directives we loop over them and get the provider for each directive. After that we check whether the given directive requires creation of a new scope, if it does and we haven’t already instantiated any other scope for the given element we invoke scope.$new(), which creates a new scope, which prototypically inherits from the current scope. After that we invoke the link function of the directive, with the appropriate parameters. What follows after that is the recursive call. Since el.children is a NodeList we cast it to an array by using Array.prototype.slice.call, which is followed by recursive call with the child element and the current scope. What does this algorithm reminds you of? Doesn’t it look just like DFS - yes, that’s what it is. So here the graphs came handy as well!

Now lets take a quick look at _getElDirectives:

// ...
_getElDirectives: function (el) {
var attrs = el.attributes;
var result = [];
for (var i = 0; i < attrs.length; i += 1) {
if (Provider.get(attrs[i].name + Provider.DIRECTIVES_SUFFIX)) {
result.push({
name: attrs[i].name,
value: attrs[i].value
});
}
}
return result;
}
// ...

This method iterates over all attributes of el, once it finds an attribute, which is already registered as directive it pushes its name and value in the result list.

Alright! We’re done with the DOMCompiler. Lets go to our last major component:

Scope

This might be the trickiest part of the implementation because of the dirty checking functionality. In AngularJS we have the so called $digest loop. Basically the whole data-binding mechanism happens because of watched expressions, which are getting evaluated in the $digest loop. Once this loop is called it runs over all the watched expressions and checks whether the last value we have for the expression differs from the current result of the expression’s evaluation. If AngularJS finds that they are not equal, it invokes the callback associated with the given expression. An example for a watcher is an object { expr, fn, last }, where expr is the watched expression, fn is the function, which should be called once the expression has changed and last is the last known value of the expression. For instance, we can watch the expression foo with a callback, which on change is being invoked with the expression’s value and sets the innerHTML of given element (a simplified version of what ng-bind does).

The scope in our implementation has the following methods:

  • $watch(expr, fn) - watches the expression expr. Once we detect change in the expr value we invoke fn(the callback) with the new value
  • $destroy() - destroys the current scope
  • $eval(expr) - evaluates the expression expr in the context of the current scope
  • $new() - creates a new scope, which prototypically inherits from the target of the call
  • $digest() - runs the dirty checking loop

So lets dig deeper the scope’s implementation:

function Scope(parent, id) {
this.$$watchers = [];
this.$$children = [];
this.$parent = parent;
this.$id = id || 0;
}
Scope.counter = 0;

We simplify the AngularJS’ scope significantly. We will only have a list of watchers, a list of child scopes, a parent scope and an id for the current scope. We add the “static” property counter only in order to keep track of the last created scope and provide a unique identifier of the next scope we create.

Lets add the $watch method:

Scope.prototype.$watch = function (exp, fn) {
this.$$watchers.push({
exp: exp,
fn: fn,
last: Utils.clone(this.$eval(exp))
});
};

In the $watch method all we do is to append a new element to the $$watchers list. The new element contains a watched expression, a callback (observer) and the last result of the expression’s evaluation. Since the returned value by this.$eval could be a reference to something, we need to clone it.

Now lets see how we create and destroy scopes!

Scope.prototype.$new = function () {
Scope.counter += 1;
var obj = new Scope(this, Scope.counter);
Object.setPrototypeOf(obj, this);
this.$$children.push(obj);
return obj;
}; Scope.prototype.$destroy = function () {
var pc = this.$parent.$$children;
pc.splice(pc.indexOf(this), 1);
};

What we do in $new is to create a new scope, with unique identifier and set its prototype to be the current scope. After that we append the newly created scope to the list of child scopes of the current scope. In destroy, we remove the current scope from the list of its parent’s children.

Now lets take a look at the legendary $digest:

Scope.prototype.$digest = function () {
var dirty, watcher, current, i;
do {
dirty = false;
for (i = 0; i < this.$$watchers.length; i += 1) {
watcher = this.$$watchers[i];
current = this.$eval(watcher.exp);
if (!Utils.equals(watcher.last, current)) {
watcher.last = Utils.clone(current);
dirty = true;
watcher.fn(current);
}
}
} while (dirty);
for (i = 0; i < this.$$children.length; i += 1) {
this.$$children[i].$digest();
}
};

Basically we run our loop until it is dirty and by default it is clean. The loop “gets dirty” only if we detect that that result of the evaluation of given expression differs from its previously saved value. Once we detect such “a dirty” expression we run a loop over all watched expressions all over again. Why we do that? We may have some inter-expression dependencies, so one expression may change the value of another one. Thats why we need to run the $digest loop until everything gets stable. If we detect that the result of the evaluation of given expression differs from its previous value we simply invoke the callback associated to the expression, update the last value and mark the loop as dirty.

Once we’re done we invoke $digest recursively for all children of the current scope. So one more time we apply what we learned (or already knew) about graph theory! One thing to note here is that we may still have circular dependency (a cycle in the graph), so we should be aware of that! Imagine we have:

function Controller($scope) {
$scope.i = $scope.j = 0;
$scope.$watch('i', function (val) {
$scope.j += 1;
});
$scope.$watch('j', function (val) {
$scope.i += 1;
});
$scope.i += 1;
$scope.$digest();
}

In this case we will see:

at given moment…

And the last (and super hacky) method is $eval. Please do not do that in production, this is a hack for preventing the need of creating our custom interpreter of expressions:

// In the complete implementation there're
// lexer, parser and interpreter.
// Note that this implementation is pretty evil!
// It uses two dangerouse features:
// - eval
// - with
// The reason the 'use strict' statement is
// omitted is because of `with`
Scope.prototype.$eval = function (exp) {
var val;
if (typeof exp === 'function') {
val = exp.call(this);
} else {
try {
with (this) {
val = eval(exp);
}
} catch (e) {
val = undefined;
}
}
return val;
};

We check whether the watched expression is a function, if it is we call it in the context of the current scope. Otherwise we change the context of execution, using with and later run eval for getting the result of the expression. This allows us to evaluate expressions like: foo + bar * baz(), or even more complex JavaScript expressions. Of course, we won’t support filters, since they are extension added by AngularJS.

Directives

So far we can’t anything useful with the primitives we have. In order to make it rocks we need to add a few directives and services. Lets implement ngl-bind (called ng-bind in AngularJS), ngl-model (ng-model), ngl-controller (ng-controller) and ngl-click (ng-click)

ngl-bind

Provider.directive('ngl-bind', function () {
return {
scope: false,
link: function (el, scope, exp) {
el.innerHTML = scope.$eval(exp);
scope.$watch(exp, function (val) {
el.innerHTML = val;
});
}
};
});

ngl-bind doesn’t require a new scope. It only adds a single watcher for the expression used as value of the ngl-value attribute. In the callback, when $digest detects a change, we set the innerHTML of the element.

ngl-model

Our alternative of ng-model will work only with text inputs. So here is how it looks like:

Provider.directive('ngl-model', function () {
return {
link: function (el, scope, exp) {
el.onkeyup = function () {
scope[exp] = el.value;
scope.$digest();
};
scope.$watch(exp, function (val) {
el.value = val;
});
}
};
});

We add onkeyup listener to the input. Once the value of the input is changed we call the $digest method of the current scope, in order to make sure that the change in the property will reflect all other watched expressions, which have the given property as dependency. On change of the watched value we set the element’s value.

ngl-controller

Provider.directive('ngl-controller', function () {
return {
scope: true,
link: function (el, scope, exp) {
var ctrl = Provider.get(exp + Provider.CONTROLLERS_SUFFIX);
Provider.invoke(ctrl, { $scope: scope });
}
};
});

We need a new scope for each controller, so that’s why the value for scope in ngl-controller is true. This is one of the places where the magic of AngularJS happens. We get the required controller by using Provider.get, later we invoke it by passing the current scope. Inside the controller, we can add properties to the scope. We can bind to these properties by using ngl-bind/ngl-model. Once we change the properties’ values we need to make sure we’ve invoked $digest in order the watchers associated with ngl-bind and ngl-model to be invoked.

ngl-click

This is the last directive we are going to take a look at, before we’re able to implement a “useful” todo application.

Provider.directive('ngl-click', function () {
return {
scope: false,
link: function (el, scope, exp) {
el.onclick = function () {
scope.$eval(exp);
scope.$digest();
};
}
};
});

We don’t need a new scope here. All we need is to evaluate an expression and invoke the $digest loop once the user clicks a button.

Wiring Everything Together

In order to make sure we understand how the data-binding works, lets take a look at the following example:

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body ngl-controller="MainCtrl">
<span ngl-bind="bar"></span>
<button ngl-click="foo()">Increment</button>
</body>
</html>
Provider.controller('MainCtrl', function ($scope) {
$scope.bar = 0;
$scope.foo = function () {
$scope.bar += 1;
};
});

Lets follow what is going on in using the following diagram:

Initially the ngl-controller directive is found by the DOMCompiler. The link function of this directive creates a new scope and pass it to the controller’s function. We add bar property, which is equals to 0 and a method called foo, which increments bar. The DOMCompiler finds ngl-bind and adds a watcher for the bar property. It also finds ngl-click and adds click event handler to the button.

Once the user click on the button, the foo method is being evaluated by calling $scope.$eval. The $scope used is the same on, passed as value to MainCtrl. Right after that, ngl-click invokes $scope.$digest$digest loops over all watchers and detects change in the value of the expression bar. Since we have associated callback for it (the one added for ngl-bind) we invoke it and update the value of the span element.

Conclusion

The framework we just built is far from a usable into production one, however some of its features:

  • Data-binding
  • Dependency Injection
  • Separation of Concerns

work in a similar way they do in AngularJS. This helps understanding AngularJS in deep much easier.

But still you should not forget to not use this code in production, much better would be to just bower install angular and enjoy!

【】

And here are the slides from my talk “Lightweight AngularJS” as promised:


Build Your own Simplified AngularJS in 200 Lines of JavaScript was published on March 09, 2015.

【转】Build Your own Simplified AngularJS in 200 Lines of JavaScript的更多相关文章

  1. Vue, React, AngularJS, Angular2 我们对流行JavaScript框架们的选择

    转自<奇舞周刊>,好文章mark一下 分割线 一个有趣的事实是:IBM发表的2017年最值得学习的编程语言名单中,JavaScript榜上有名.这位IT巨头指出,JS在网站中惊人地达到94 ...

  2. [转]25个HTML5和JavaScript游戏引擎库

    本文转自:http://www.open-open.com/news/view/27c6ed 1. The GMP JavaScript Game Engine GMP是一个基于精灵2-D游戏,它可以 ...

  3. 具备 jQuery 经验的人如何学习AngularJS(附:学习路径)

    这是一个来自stackoverflow的问答,三哥直接把最佳回答搬过来了. 都说AngularJS的学习曲线异常诡异~~~ Q: “Thinking in AngularJS” if I have a ...

  4. Getting Started with Django Rest Framework and AngularJS

    转载自:http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html A ReST ...

  5. 如何在 ASP.NET MVC 中集成 AngularJS(1)

    介绍 当涉及到计算机软件的开发时,我想运用所有的最新技术.例如,前端使用最新的 JavaScript 技术,服务器端使用最新的基于 REST 的 Web API 服务.另外,还有最新的数据库技术.最新 ...

  6. AngularJS基础概要整理(下)

    五.AngularJS Scope(作用域) Scope(作用域)是应用在HTML(视图)和JavaScript(控制器)之间的纽带. Scope是一个对象,有可用的方法和属性. Scope可应用在视 ...

  7. angularJs学习笔记-入门

    1.angularJs简介 angularJs是一个MV*的javascript框架(Model-View-Whatever,不管是MVVM还是MVC,统归MDV(model drive view)) ...

  8. Facebook's React vs AngularJS: A Closer Look

    When we launched React | A JavaScript library for building user interfaces two weeks ago there were ...

  9. Build fat static library (device + simulator) using Xcode and SDK 4+

    155down votefavorite 185 It appears that we can - theoretically - build a single static library that ...

随机推荐

  1. java代理的深入浅出(三)-JavaAssist,ASM

    简介 类似字节码操作方法还有ASM.几种动态编程方法相比较,在性能上Javassist高于反射,但低于ASM,因为Javassist增加了一层抽象.在实现成本上Javassist和反射都很低,而ASM ...

  2. VMware 虚拟机(linux)增加根目录磁盘空间

    今天查看学校的监控报修系统,不能访问了!!!系统运行很慢,用top命令查看发现内存使用率90%,用"df -h ”查看“/”目录使用率已达到80%,导致系统运行很慢.我用以下方法扩大根目录磁 ...

  3. PHP:class static

    简介 static关键词的一种常见用途是,当类的某个变量$var被定义为static时,那么$var就是类的变量. 这意味着:1,该类的任意一个实例对其进行修改,在该类的其它实例中访问到的是修改后的变 ...

  4. EF6 第一次,或者相隔一段时间变慢咋办? 我们来优化下

    第一.问题原因分析 EF方面的原因: 1.Code First第一次启动会对比程序中的Model与数据库表(database initializer ),生成Model与数据库的映射视图 2.随着EF ...

  5. 在Linux服务器上增加硬盘没那么简单【转】

    运维案例:HP服务器,LINUX系统在保障数据的前提下扩展/home分区 部门需求:研发部门提出需要在现有的服务器上扩容磁盘空间,以满足开发环境的磁盘需求.现有空间1.6T需要增加到2T. 需求调查分 ...

  6. jetty访问jsp页面出现( PWC6345: There is an error in invoking javac)

    org.apache.jasper.JasperException: PWC6345: There is an error in invoking javac.  A full JDK (not ju ...

  7. Hibernate关于openSession和getCurrentSession的理解

    来源(转载):http://blog.csdn.net/woshisap/article/details/7024482 1:getCurrentSession会把Session和当前的线程关联起来, ...

  8. iOS本地推送与远程推送详解

    一.简介 分为本地推送和远程推送2种.可以在应用没有打开甚至手机锁屏情况下给用户以提示.它们都需要注册,注册后系统会弹出提示框(如下图)提示用户是否同意,如果同意则正常使用:如果用户不同意则下次打开程 ...

  9. drupal错误: Maximum execution time of 240 seconds exceeded

    drupal7.5安装完成,导入汉化包时,出现错误: Fatal error: Maximum execution time of 240 seconds exceeded in D:\phpweb\ ...

  10. JS-如何把字符串转换成数组

    var a = "1,22,33,44"; // 字符串 var b = a.split(","); // 将字符串按照","分割,存入数组 ...