所有由管理员发布的文章

javascript封装对象的疑惑点

使用封装对象时有些地方需要特别注意。

比如Boolean:

var a = new Boolean( false );

if (!a) {

console.log( “Oops” ); // 执行不到这里

}

我们为false 创建了一个封装对象,然而该对象是真值(“truthy”,即总是返回true,参见第4 章),所以这里使用封装对象得到的结果和使用false 截然相反。

如果想要自行封装基本类型值,可以使用Object(..) 函数(不带new 关键字):

var a = “abc”;

var b = new String( a );

var c = Object( a );

typeof a; // “string”

typeof b; // “object”

typeof c; // “object”

b instanceof String; // true

c instanceof String; // true

Object.prototype.toString.call( b ); // “[object String]”

Object.prototype.toString.call( c ); // “[object String]”

再次强调,一般不推荐直接使用封装对象(如上例中的b 和c),但它们偶尔也会派上用场。

摘自 《你不知道的JavaScript》中卷

angularjs2会报错“GET http://127.0.0.1/traceur 404 (Not Found)”

直接用编译好的.js文件的话system.config.js是这样的

(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': '/node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: '/app',

      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      // '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',


    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: '../test.main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

其中

main: '../test.main.js',
        defaultExtension: 'js'

里面必须写成js,如果忘了将.ts文件编译成.js文件的话,会报错,提示“GET http://127.0.0.1:8080/traceur 404 (Not Found)”
如果是直接用.ts文件的话,system.config.js应该是这样的

(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },


    paths: {
      // paths serve as alias
      'npm:': '/node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: '/app',

      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      // '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript/lib/plugin.js',
      'typescript':                'npm:typescript/lib/typescript.js',


    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: '../test.main.ts',
        defaultExtension: 'ts'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

相比上面,不但更改了两处.js变成.ts,还增加了两个部分
新增第一部分代码

// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },

新增第二部分代码

      'ts':                        'npm:plugin-typescript/lib/plugin.js',
      'typescript':                'npm:typescript/lib/typescript.js',

如果只增加第二部分代码,不增加第一部分代码,报错仍然是“GET http://127.0.0.1:8080/traceur 404 (Not Found)”,如果只增加第一部分代码,不增加第二部分代码,报错会是“GET http://127.0.0.1:8080/ts 404 (Not Found)”或者“GET http://127.0.0.1:8080/typesript 404 (Not Found)”之类的。
搜狗截图20170126212205
平时测试代码,懒得每次编译ts就直接用这种模式。

angular表单验证$parsers和$validators方式区别

这里只以表单里面的“输入密码”和“确认密码”为例,来看一下再angular1.3之前用的$parsers和1.3之后新增的$validators数组实现验证的方法区别。
先来看看HTML代码部分

<!-- 密码型字段 -->
	<div>
		<label for="_password">密码</label>
		<input type="password" id="_password" name="password" ng-model="vm.form.password" ng-required="true">
	</div>
	<div>
		<label for="_retypedPassword">确认密码</label>
		<input type="password" id="_retypedPassword" ng-model="vm.retypedPassword" bf-field-error bf-assert-same-as="vm.form.password">
	</div>

里面的bf-field-error指令是用来显示错误消息的,而bf-assert-same-as是特地为“确认密码”新增的指令,用来提示“两次的输入密码不符”的错误提示。
下面是指令bf-field-error的代码

.directive('bfFieldError', function($compile){
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, ele, att, ngModelCtrl){
			//创建一个独立作用于的子scope
			var subScope = scope.$new(true);
			subScope.hasError = function(){
				//除了判断是否无效外,还要判断是否已经输入过
				return ngModelCtrl.$invalid && ngModelCtrl.$dirty;
			};
			subScope.errors = function(){
				//先直接显示到界面上,回头再改进为用户友好的方式
				return ngModelCtrl.$error;
			};
			//把一段HTML编译成"活dom",然后把subScope传递给它,这个"活dom"将会跟随subScope的变化自动更新自己
			var hint = $compile('<ul class="bf-field-error" ng-if="hasError()"><li ng-repeat="(name, wrong) in errors()" ng-if="wrong">{{name | errors}}</li></ul>')(subScope);
			//把这段"活dom"追加到当前元素后面,好让它显示出来
			ele.after(hint);
		}
	};
})

接着是我们的主角bf-assert-same-as

.directive('bfAssertSameAs', function(){
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, ele, att, ngModelCtrl){
			var isSame = function(value){
				//取对照值,通过scope.$eval把att.bfAssertSameAs作为一个表达式在当前作用域中求值,否则它只是一个固定的字符串
				var anotherValue = scope.$eval(att.bfAssertSameAs);
				return value === anotherValue;
			};
			
			 //1.2.x只能用$parsers实现验证
			 ngModelCtrl.$parsers.push(function(value){
			 	//调用$setValidity设置验证结果,第一个参数是名字,和$error中的属性名一致,但是取值相反,因为这里表示的是“有效”,而$error中表示“无效”
			 	ngModelCtrl.$setValidity('same', isSame(value));
			 	return true;
			 });
			 // 这是assertSameAs验证器所特有的,因为当对照值(即vm.form.password)发生变化时,也要更新有效性状态
			 scope.$watch(function(){
			 	return scope.$eval(att.bfAssertSameAs);
			 }, function(){
			 	//变化时重新判断并设置验证结果
			 	ngModelCtrl.$setValidity('same', isSame(ele.val()));
			});
		}
	};
})

需要注意的是$setValidity的返回值为 undefined或者false时会在 $error里面会存在parse:true的问题,上面选择了直接返回true
如果用最新的$validators数组则应该这样写,他需要返回布尔类型的值

.directive('bfAssertSameAs', function(){
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, ele, att, ngModelCtrl){
			var isSame = function(value){
				//取对照值,通过scope.$eval把att.bfAssertSameAs作为一个表达式在当前作用域中求值,否则它只是一个固定的字符串
				var anotherValue = scope.$eval(att.bfAssertSameAs);
				return value === anotherValue;
			};
                        //1.3.x增加了专门的$validators数组,可用更好的方式实现验证。这里个数组都是放需要被执行的函数的                        
			ngModelCtrl.$validators.same = function(value){ //返回值必须是boolean类型
				//调用$setValidity设置验证结果,第一个参数是名字,和$error中的属性名一致,但是取值相反,因为这里表示的是“有效”,而$error中表示“无效”
				return isSame(value);
			};
			// 这是assertSameAs验证器所特有的,因为当对照值(即vm.form.password)发生变化时,也要更新有效性状态
			scope.$watch(function(){
				return scope.$eval(att.bfAssertSameAs);
			}, function(newValue){
				
				//变化时重新判断并设置验证结果
				ngModelCtrl.$setValidity('same', isSame(ele.val()));
			});
		}
	};
})

效果如下:baidushurufa_2016-10-21_18-25-20

AngularJS的controllerAs常见用法

我们一般用controllerAs多半是因为要用到我们的controller里面的数据,比如获取一个名叫cells.json,里面包含了所有手机的概况,我们需要获取这些数据以便ng-repeate它,一般我们可以在路由和指令里面使用。
先上模板页面cells.html

<ul>
	<li ng-repeat="cell in vm.cells">{{cell.name}},价格:{{cell.price}}</li>
</ul>

路由内容

'use strict';

angular
.module('myApp', ['ngRoute', 'app.directive'])
.config(['$routeProvider', function($routeProvider) {
	$routeProvider
	.when('/', {
		template: 'index'
	})
	.when('/cells', {
		template: '<cells-list></cells-list>',
		controller: ['Cells', 
		    function (Cells) {
			var vm = this
                        vm.cells = Cells.query();
    	            }
                ],
		controllerAs: 'vm'
	})
}])

指令内容

'use strict';

angular
.module('app.directive',['app.service'])
.directive('cellsList', function(){
	return {
		templateUrl:'view/cells.html',
		controller: ['Cells', 
		   function (Cells) {
		      var vm = this
                      vm.cells = Cells.query();
    	           }
                ],
                controllerAs: 'vm'
	}
})

可以选择在路由里面用controller和controllerAs,或者在指令里面使用。

效果都是一样的
controller和controllerAs

AngularJs的provider里面的装饰器decorator

我们经常在使用某些Service的时候,更希望它能具备一些额外的功能,这时我们难道改这个Service吗?如果是系统自带的呢,改吗?这当然不现实吧。所以我们的装饰器decorator就发挥作用了,它能让已有的功能锦上添花。我们在config里面使用装饰器。
使用方法

      第一个参数:需要装饰的Service名
      第二个参数:一个接受$delegate的回调函数,$delegate代表我们的原来的service实例。

需要注意的是constant常量是不可以被装饰的。
示例代码如下:

var app = angular.module("myApp", []);
app.controller('myCtrl', ['$scope','myInfo', function($scope, myInfo){
  console.log(myInfo);
}]);

app.config(['$provide',function($provide) {
  $provide.decorator('myInfo', function($delegate){
    $delegate.lastName = "Prompt";
    return $delegate;
  })
}]);

app.service('myInfo', function(){
  this.fistName = "Shadow";
})

原来的服务myInfo只有firstName一个属性,我们利用decorator后在不修改myInfo代码的情况下里面添加了lastName属性。
decorator

浅析angular.identity

angular.identity这个函数比较简单,官方文档也就简单的交代了下。
使用方法:当你用函数风格书写时,它返回的是函数的一个参数。

function transformer(transformationFn, value) {
       return (transformationFn || angular.identity)(value);
     };

需要额外说明的是:这里面的transformationFn是第一个参数,它将把value作为自己的参数来执行。如果transformation传递的是null或者undefined的话,angular.identity把value作为自己的参数来执行。
示例如下:

var app = angular.module('myApp', []); 
app.controller('myCtrl', ['$scope',function($scope){
  $scope.result = "";
  $scope.input = "";
  $scope.double = function(n){
    return n*2;
  }
  $scope.answer = function(fn, val){
    return (  fn || angular.identity)(val);
  }
  $scope.cal = function(){
    $scope.result = $scope.answer($scope.double, $scope.input);
  }
}]);
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
	<head>
		<meta charset="UTF-8">
		<script src="../js/Angular1.5.5.js" ></script>
		<script src="../js/angular-sanitize.js" ></script>
		<script src="index.js" ></script>
		<title>angular.identity</title>
	</head>
	<body>
		<div ng-controller="myCtrl">
			<input type="text" id="btn" ng-model="input" value="answer" ng-change="cal()"/> * 2  = {{result}}
    </div>
	  </div>
	</body>
</html>

angualr.identity

AngularJS加载方式和angular.bootstrap

我们一般写AngularJS时,会在html或者body、div、form等标签里面加上ng-app=”myApp”之类的,这实际上就是让angular自动加载,Angular会自动找到ng-app指令,并将写有ng-app的HTML元素里面的内容作为自己的管辖范围,也就是以该元素为根。这时系统只会找第一个ng-app,如果你的代码里面有多个ng-app的话,angular只理会第一个,后面的直接无视。
如果你的代码里面没有包含ng-app,你就只能手动加载angular了,就需要用到angular.bootstrap这个函数了。
angular.bootstrap函数有两个参数,第一个参数必须,第二个为可选参数。
使用方法:

  1. element(DOM元素):该DOM元素将作为angular的根
  2. modules(数组,数组里面的元素可以是字符、函数、数组):angular需要加载的各个模块的名字所构成的数组

返回值:为应用新建一个注入器。
示例代码如下:

      var app =angular.module("myApp",[]);
      var app2 =angular.module("myApp2",[]);
      var app3 =angular.module("myApp3",[]);
      console.log(app);
      app.controller('myCtrl', ['$scope',function($scope){
        $scope.test = "This is a test!"
      }]);
      app2.controller('myCtrl2', ['$scope',function($scope){
        $scope.test = "A test too!"
      }]);
      app3.controller('myCtrl3', ['$scope',function($scope){
        $scope.test = "I am from div3!"
      }]);

      angular.element(document).ready(function() {
        angular.bootstrap(document.getElementsByName('boot'), ['myApp', 'myApp2']); 
        angular.bootstrap(document.getElementById("div3"),["myApp3"]);
      });

html代码

<html>
  <body>  
    <div name='boot' ng-controller="myCtrl">
      {{test}}
    </div>
    <div name='boot' ng-controller="myCtrl2">
      {{test}}
    </div>
    <div id='div3' ng-controller="myCtrl3">
      {{test}}
    </div>
  </body>  
</html>

运行结果如下:
angular.bootstrap
也可以利用下面的代码实现加载三个

      angular.element(document).ready(function() {
        angular.bootstrap(document), ['myApp', 'myApp2', 'myApp3']); 
      });

在做项目的实战中,手动加载angular完全没有必要。

浅析angular.bind

简单来说angular.bind函数是用来返回一个自己设定参数的函数。它有三个参数,其中第三个参数是可选的。第二个参数是被绑定的函数fn,第一个参数是第二个参数fn的上下文对象,用this调用。
使用方法:

  1. self(对象):fn的上下文对象,可用this调用
  2. fn(函数):被绑定的函数
  3. arg(*):可以用来绑定给fn的参数

返回值:
被绑定参数的函数
举个例子

      var self = {name:'Jack'};
      //示例1--带参数
      var f = angular.bind(self, //绑定对象,作为函数的上下文
        //被绑定的函数
        function(age){  
          console.log(this.name + ' is ' + age + ' !');
        },
        //绑定的参数,可省略
        '15'
      );        
      //示例2--不带参数
      var m = angular.bind(self, //绑定对象,作为函数的上下文
        //被绑定的函数
        function(age, sex){  
          console.log(this.name + ' is ' + age + ', he is a ' + sex + ' !');
        }
        //省略参数
      );
      f();//调用绑定之后的function
      m(3,'male');//调用传参的函数    

示例1相当于是把参数放进angular.bind()里面的第三个参数,示例2是把参数直接放入m3()里面。
angular.bind

浅析angular.forEach

这个forEach还是比较简单的,angular.forEach有三个参数,前两个参数是必须的,第三个是可选的;
使用方法:

  1. obj(对象或者数组):被迭代对象
  2. iterator(函数):迭代器函数
  3. context(对象):在iterator中被指定为上下文的对象,也就是说在iterator里面的this指的就是此处的context

返回值:对象或者数组,返回的是前面第一个参数obj的引用。
举个例子

var values = {name: 'misko', gender: 'male'};
var log = [];
angular.forEach(values, function(arg1, arg2, arg3) {
   this.push(arg1 + ': ' + arg2);
   console.log(arg3);
}, log);
 console.log(log);

这里forEach传递了三个参数,先说说里面的第三个参数log,将forEach的上下文指定为log,这样在第二个参数function里面的this指的就是log,如果不填写第三个参数的话,里面的this指向的就是window。
还要说明的是这里面的第二个参数function,function里面最多可以填写三个参数,arg1就是我们常见的key,即此处的name/gender,arg2就是我们常见的value,即此处的misko/male,一般不用第三个参数arg3,如果非要第三个参数的话,arg3的值就是此处的对象values。
angular.forEach

浅析AngularJS中的$interpolate

interpolate

$interpolate服务是一个可以接受三个参数的函数,其中第一个参数是必需的。
使用方法:

  • text(字符串):一个包含字符插值标记的字符串。
  • mustHaveExpression(布尔型):如果将这个参数设为true,当传入的字符串中不含有表达式时返回null,而不是我们期望的interpolation function。
  • trustedContext(字符串):AngularJS会对已经进行过字符串插值操作的字符串通过$sec.getTrusted()方法进行严格的上下文转义。

返回值:函数,用来在特定的上下文中运算表达式。
假设我们在群发邮件的时候,邮件内容一样,只是收件人’to’不同,或者想让不同的发件人’from’也不同,这里我们可以将邮件内容’emailBody’写成模板,将to和from写成“变量”:
emailBody = ‘Hello {{ to }}, Best wishes from {{from}}’
要在字符串模板中做插值操作,需要在你的对象中注入$interpolate服务。在下面的例子中,我们将会将它注入到一个控制器中

    var app =angular.module("myApp",[]);
    app.controller('MyController', function($scope, $interpolate) {
      $scope.to = 'ari@fullstack.io';
      $scope.from = '123456@qq.com';
      $scope.emailBody = 'Hello {{ to }},\nBest wishes from {{from}}';

      //Set up a watch
      $scope.$watch('to', function(body) {
        console.log(body);
        if (body) {
          var template = $interpolate($scope.emailBody,true);
          $scope.previewText =  template({to: $scope.to,from:$scope.from});
        }
      });
    });

上面的template就是$interpolate服务返回回来的函数,在这个函数里面只能传递一个参数,里面的{to: $scope.to,from:$scope.from}意思很明显,多个需要替换的上下文就用逗号隔开

<div ng-controller="MyController">
    <input ng-model="to" type="email" >
    <textarea ng-model="emailBody"></textarea>
    <pre>{{previewText}}</pre>
</div>

运行结果如下;
interpolate

如果需要在文本中使用不同于{{ }}的符号来标识表达式的开始和结束,可以在$inter polateProvider中配置。

  • 用startSymbol()方法可以修改标识开始的符号。这个方法接受一个参数。
    value(字符型) :开始符号的值。
  • 用endSymbol()方法可以修改标识结束的符号。这个方法也接受一个参数。
    value(字符型) : 结束符号的值。

如果要修改这两个符号的设置,需要在创建新模块时将$interpolateProvider注入进去。下面我们来创建一个服务:

    app.config(['$interpolateProvider',function($interpolateProvider){
      $interpolateProvider.startSymbol('__') ;
      $interpolateProvider.endSymbol('__') ;
    }]);
    app.factory('EmailParser',['$interpolate',function($interpolate){
      //处理解析的服务
      return {
        parse : function(text,context){
          var template = $interpolate(text) ;
          return template(context) ;
        }
      }
    }]) ;
   app.controller('MyController', ['$scope', 'EmailParser',function($scope, EmailParser) {
      $scope.to = 'ari@fullstack.io';
      $scope.from = '123456@qq.com';
      $scope.emailBody = 'Hello __ to __,\nBest wishes from __from__';
      // 设置监听
      $scope.$watch('emailBody', function(body) {
          if (body) {
              $scope.previewText = EmailParser.parse(body, {to: $scope.to, from:$scope.from});
          }
      });
    }]);

由于我们将表达式开始和结束的符号都设置成了__,因此需要将HTML修改成用这个符号取代{{ }}的版本,效果如图所示。

<div id="emailEditor">
    <input ng-model="to" type="email" />
    <textarea ng-model="emailBody"></textarea>
</div>
<div id="emailPreview">
    <pre>__ previewText __</pre>
</div>