高性能JavaScript--JS加载和执行

随着前端技术的不断发展,JS在web应用中的作用越来越来大,同时也意味着JS越来越成为前端的瓶颈。写出高性能的JS代码,也成为了前端开发者必备的技能。这里分享一下《高性能JavaScript》的读书心得,以便大家更好的理解JS,写出更高效的JS代码。

JS加载和执行

JS的加载

  1. 地球人都知道,为了不阻塞页面的加载和渲染,JS文件一般放在html的底部加载。
  2. 减少加载的JS脚本数量,一般采用合并文件的方式
  3. 减小JS文件大小,压缩JS文件,开启GZIP压缩

无阻塞的脚本

避免一次加载一个很大的JS文件,避免阻塞浏览器。主要采用延迟加载技术,具体实现方法包括:

  1. 利用script标签的延迟加载属性defer(需注意兼容性,可以在上查看)
    1
    <script type="text/javascript" src="file1.js" defer></script>

带有defer属性的script标签,可以放在文档的任何位置。对应的JS文件将在script标签解析时下载,但代码不会被执行,直到DOM加载完成(onload事件触发之前)才会执行。JS文件下载时不会阻塞页面的其他处理过程。

  1. 动态创建script元素,通过src加载脚本
    1
    2
    3
    4
    var script= document.createElement ("script");
    script.type= "text/javascript";
    script.src= "file1.js";
    document.getElementsByTagName("head")[0].appendChild(script)

特点:无论在何处启动下载,文件的下载和运行都不会阻塞页面的其他处理过程。如需控制狂态和加载顺序,需要自己添加监听事件处理.

1
2
3
4
5
6
7
//Internet Explorer
script.onreadystatechange =function(){...}
//Firefox,Opera, Chrome,Safari3+
script.onload = function(){...}
```

3. XMLHttpRequest脚本注入,通过XHR获取JS字符串,填入script元素。

var xhr= newXMLHttpRequest();
xhr.open(“get”,”file1.js”,true);
xhr.onreadystatechange= function(){
if (xhr.readyState==4){
if (xhr.status>=200&&xhr.status<300|| xhr.status="=" 304){="" varscript="document.createElement("script");" script.type="text/javascript" ;="" script.text="xhr.responseText;" document.body.appendchild(script);="" }="" };="" xhr.send(null)=""

1
2
3
特点:下载后可以不用立即添加script元素,从而控制执行时间。
### 推荐的无阻塞模式
先加载必要的JS文件,然后再执行load函数,再加载其他功能所需的脚本。


```
目前各主流的延迟加载类库,也是采用这些技术。比如,图片懒加载,动态给img元素的src赋值。requireJS是使用动态创建script元素实现的。

基于karma+jasmine的web前端自动化测试

本文介绍了基于karma+jasmine的web前端自动化测试的方案和详细操作指导。

名词解释

  1. Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm,是全球最大的开源库生态系统。
  2. Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuous integration)工具,也可和其他代码编辑器一起使用。这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。
  3. Jasmine 是一个简易的JS单元测试框架。Jasmine 不依赖于任何浏览器、DOM、或者是任何 JavaScript 而存在。它适用于所有网站、Node.js 项目,或者是任何能够在 JavaScript 上面运行的程序。

环境安装

  1. 首先必须安装执行环境nodejs
  2. 安装浏览器,推荐chrome(用于运行监听程序,监听js文件变化,自动触发测试执行)
  3. 安装karma+jasmine
    npm install karma -g
    npm install karma-jasmine -g
    npm install karma-chrome-launcher -g 
    npm install karma-cli -g 
    npm install karma-coverage -g 
    npm install karma-html-reporter -g 
    
  4. 安装完成后执行karma -v,检查安装是否正常

工程配置

  1. 可以使用karma init,自动生成配置文件,完成部分参数的设置,然后再手动修改。
  2. 当然最快的配置方法,复制下面的配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    		// Karma configuration
    // Generated on Tue Nov 01 2016 14:17:00 GMT+0800 (中国标准时间)

    module.exports = function(config) {
    config.set({
    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    //需要加载入浏览器的js文件,包括基础的类库,被测试js文件和测试用例js文件
    //如果需要测试angular代码,比如引入angular-mock.js,给angular代码进行mock。
    //注意angular-mock的版本一定要和angular版本一致。可在cdn网站对应的angular版本列表中寻找
    files: [
    '../webapp/vender/jquery/jquery-1.10.2.min.js',
    '../webapp/vender/angular/angular.min.js',
    '../webapp/vender/angular/angular-ui-router.min.js',
    'lib/angular-mocks.js',
    '../webapp/common/*.js',
    '../webapp/commont/template/*.html',
    'tc/ut/**/*.js'
    ],

    // list of files to exclude
    exclude: [
    //'../webapp/vender/**/*.js'
    ],

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    //这里定义输出的报告
    //html对应karma-html-reporter组件,输出测试用例执行报告
    //coverage对应karma-coverage组件,输出测试用例执行报告
    reporters: ['progress', 'html', 'junit', 'coverage'],
    junitReporter: {
    // will be resolved to basePath (in the same way as files/exclude patterns)
    outputFile: 'report/ut/test-results.xml',
    suite: 'UT',
    useBrowserName: false
    },
    htmlReporter: {
    outputDir: 'report/ut',
    reportName: 'result' //outputDir+reportName组成完整的输出报告格式,如没有定义,会自动生成浏览器+OS信息的文件夹,不方便读取报告
    },
    //定义需要统计覆盖率的文件
    preprocessors: {
    '../webapp/common/*.js':'coverage',
    '../webapp/common/template/*.html': 'ng-html2js'
    },
    coverageReporter: {
    type: 'html', //将覆盖率报告类型type设置为cobertura 或者 html
    subdir:'coverage', //dir+subdir组成完整的输出报告格式,如没有定义,会自动生成浏览器+OS信息的文件夹,不方便读取报告
    dir: 'report/ut/' //代码覆盖率报告生成地址
    },

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    //karma自动自动监视被测试文件和测试用用例文件,如有修改,自动重新执行测试
    autoWatch: true,
    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    //上一个参数为true,本参数为false,,则自动监视才生效。否则执行完测试用例后自动退出
    singleRun: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    //用来执行自动监听的浏览器,推荐chrome
    browsers: ['Chrome'],

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
    //自动将模板文件路径转换页面引入路径,以便注入用例中
    ngHtml2JsPreprocessor: {
    cacheIdFromPath: function(filepath) {
    var cacheId = filepath.substr(filepath.lastIndexOf('/webapp/')+7);
    // console.log(cacheId);
    return cacheId;
    },
    moduleName: 'template'
    }
    })
    }
    ```
    3. 保存配置文件到测试目录

    ### 测试用例编写
    #### 1、用例怎么写
    ```javascript
    describe("A suite of Common/common.js", function() {

    beforeAll(function(){
    console.log('beforeAll');
    });
    describe("extends of String", function() {
    var expected;
    beforeEach(function(){
    expected = 'abcd';
    });
    it("trim",function(){
    expect(expected).toEqual((" abcd ").trim());
    });
    it("ltrim",function(){
    expect(expected).toEqual((" abcd").ltrim());
    });
    it("rtrim",function(){
    expect(expected).toEqual(("abcd ").rtrim());
    });
    });
    });

上述例子中,
a. describe相当于一个测试套,可以嵌套。
b. it(‘tc name’,function(){})是一个测试用例。
c. beforeAll和beforeEach是预置条件,前者一个测试套执行一次,后者每个测试用例执行一次。
d. 当然还会有afterAll和afterEach
e. expect是断言

2、 断言都有那些比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Matcher实现了断言的比较操作,将Expectation传入的实际值和Matcher传入的期望值比较。任何Matcher都能通过在expect调用Matcher前加上not来实现一个否定的断言(expect(a).not().toBe(false);)。
常用的Matchers有:
toBe():相当于= =比较。
toNotBe():相当于! =比较。
toBeDefined():检查变量或属性是否已声明且赋值。
toBeUndefined()
toBeNull():是否是null
toBeTruthy():如果转换为布尔值,是否为true
toBeFalsy()
toBeLessThan():数值比较,小于。
toBeGreaterThan():数值比较,大于。
toEqual():相当于==,注意与toBe()的区别。一个新建的Object不是(not to be)另一个新建的Object,但是它们是相等(to equal)的。
expect({}).not().toBe({});
expect({}).toEqual({});
toNotEqual()
toContain():数组中是否包含元素(值)。只能用于数组,不能用于对象。
toBeCloseTo():数值比较时定义精度,先四舍五入后再比较。
toHaveBeenCalled()
toHaveBeenCalledWith()
toMatch():按正则表达式匹配。
toNotMatch()
toThrow():检验一个函数是否会抛出一个错误

3、 angular代码怎么写

先看例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
describe('Apply MainCtrl', function() {
var $scope,
$controller,
$httpBackend;
var MainCtrl;

beforeEach(module('applyApp'));

beforeEach(inject(function(_$controller_, $rootScope, _$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$controller = _$controller_;
$httpBackend.when('POST', /\/api\/wxcop\/common\/record.*/).respond({});
}));

it('Check $scope assignments.', function() {
MainCtrl = $controller('MainController', {
$scope: $scope
});
$httpBackend.flush();
$scope.gotoApplyHome();
$scope.judgeLogin();
expect($scope.typeSelect).toEqual(["单行文本","多行文本","单选","多选"]);
expect($scope.getItemItems("1,2,3")).toEqual(["1","2","3"]);
});
});

注意,要测试angular必须引入angular-mock。
说明

  1. beforeEach(module(‘applyApp’)); 引入module ‘applyApp’
  2. beforeEach(inject(function($controller, $rootScope, $httpBackend) 依赖注入和http测试打桩
  3. $controller(‘MainController’, )初始化controller
  4. 直接调用scope,然后执行断言

5、 angular的相关特性如何测试

1、测试函数

a. 被测试代码

1
2
3
4
$scope.functionTriger = false;
$scope.doTest = function(){
$scope.functionTriger = true;
}

b. 测试用例

1
2
3
4
5
it('function', function() {    
expect($scope.functionTriger).toBeFalsy();
$scope.doTest();
expect($scope.functionTriger).toBeTruthy();
});

2、测试监听

a. 被测试代码

1
2
3
4
5
$scope.watchVar = false;
$scope.watchedTrigeIndex = 0;
$scope.$watch('watchVar', function() {
$scope.watchedTrigeIndex++;
});

b. 测试用例

1
2
3
4
5
6
7
8
9
it('watch', function() {    
expect($scope.watchedTrigeIndex).toBe(0);
$scope.watchVar = true;
$scope.$digest();
expect($scope.watchedTrigeIndex).toBe(1);
$scope.watchVar = false;
$scope.$digest();
expect($scope.watchedTrigeIndex).toBe(2);
});

3、测试广播

a. 被测试代码

1
2
3
4
$scope.isHaveTriger = false;
$scope.$on('ngRepeatFinished', function(){
$scope.isHaveTriger = true;
});

b. 测试用例

1
2
3
4
5
it('broadcast', function() {    
expect($scope.isHaveTriger).toBeFalsy();
$scope.$broadcast('ngRepeatFinished');
expect($scope.isHaveTriger).toBeTruthy();
});

4、测试路由切换

a. 被测试代码

1
2
3
4
5
6
7
8
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise("/detail");
$stateProvider.state('detail', {
url:'/detail',
template:'<p></p>',
controller:'MainCtrl'
});
}])

b. 测试用例

1
2
3
4
5
6
7
8
9
10
it('route', function() {  
inject(function (_$injector_) {
$state = _$injector_.get('$state');
});
var curState = $state.get('detail');
expect(curState.name).toEqual('detail');
expect(curState.url).toEqual('/detail');
expect(curState.controller).toEqual('MainCtrl');
expect(curState.template).toEqual('<p></p>');
});

5、测试过滤器

a. 被测试代码

1
2
3
4
5
.filter('myFilter', function(){
return function(data) {
return data + 'lzw';
}
})

b. 测试用例

1
2
3
4
it('filter', function() {
inject(function (_$injector_) {
$filter = _$injector_.get('$filter');
});

6、测试service

a. 被测试代码

1
2
3
4
5
6
7
8
9
10
.service('foo', function() {
var thisIsPrivate = "Private";
function getPrivate() {
return thisIsPrivate;
}
return {
variable: "This is public",
getPrivate: getPrivate
};
})

b. 测试用例

1
2
3
4
5
6
7
8
it('service',function(){
var foo;
inject(function(_foo_) {
foo = _foo_;
});
expect(foo.variable).toBe('This is public');
expect(foo.getPrivate()).toBe('Private');
});

7、测试指令

a. 被测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.directive('myDirective', function() {
return {
restrict: 'A',
replace: true,
template: '<p>11</p>',
link: function(scope) {}
};
})
.directive('dirButton', function() {
return {
template: '<button>Increment value!</button>',
link: function (scope, elem) {
elem.find('button').on('click', function(){
scope.value++;
});
}
};
})
.directive('dirScope', function() {
return {
scope:{
config: '=',
notify: '@',
onChange:'&'
},
link: function(scope){
}
};
})

b. 测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
it('directive', function(){    
var link = $compile('<p my-directive></p>');
var element = link($scope);
expect($(element).html()).toBe('11');
});

it('button directive', function () {
var directiveElem = $compile('<button dir-button></button>')($scope);
$scope.value=10;
var button = directiveElem.find('button');
button.triggerHandler('click');
$scope.$digest();
expect($scope.value).toEqual(11);
});

it('scope directive',function(){
$scope.config = {
prop: 'value'
};
$scope.notify = true;
$scope.onChange = jasmine.createSpy('onChange');
var directiveElem = $compile(angular.element('<p dir-scope config="config" notify="notify" on-change="onChange()"></p>'))($scope);
$scope.$digest();
var isolatedScope = directiveElem.isolateScope();

//test =
isolatedScope.config.prop = "value2";
expect($scope.config.prop).toEqual('value2');

//test @
isolatedScope.notify = false;
expect($scope.notify).toEqual(true);

//test &
expect(typeof(isolatedScope.onChange)).toEqual('function');
isolatedScope.onChange();
expect($scope.onChange).toHaveBeenCalled();

//调用指令的父controller。
directiveElem.scope().doFunction();
});

关于mock

$httpBackend

$httpBackend对于代码中的http请求进行mock。常用方法:

1
2
$httpBackend.when(method, url, [data], [headers]);
$httpBackend.expect(method, url, [data], [headers]);

when和expect都有对应的快捷方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
whenGET(url, [headers]);
whenHEAD(url, [headers]);
whenDELETE(url, [headers]);
whenPOST(url, [data], [headers]);
whenPUT(url, [data], [headers]);
whenJSONP(url);
expectGET(url, [headers]);
expectHEAD(url, [headers]);
expectDELETE(url, [headers]);
expectPOST(url, [data], [headers]);
expectPUT(url, [data], [headers]);
expectPATCH(url, [data], [headers]);
expectJSONP(url);

url支持正则,比如:

1
$httpBackend.when('POST', /\/api\/wxcop\/common\/record.*/).respond({});

注意:
$httpBackend.when与$httpBackend.expect的区别在于:$httpBackend.expect的伪后台只能被调用一次(调用一次后会被清除),第二次调用就会报错,而且$httpBackend.resetExpectations可以移除所有的expect而对when没有影响。

常见异常处理

Argument ‘MainCtrl’ is not a function, got undefined

无法找到MainCtrl。可能原因:controller定义错误,app注入失败。

Disconnected, because no message in 10000 ms.

ajax请求超时。原因:$httpBackend.flush();要放在ajax发起请求后执行。

指令采用templateUrl方式加载模板失败

可采用karma-ng-html2js-preprocessor插件自动注入。也可以采用$templateCache注入。注意,这两种方式都不支持模糊匹配

参考资料

《AngularJS Testing Tips: Testing Directives》
《Unit Testing in AngularJS: Services, Controllers & Providers》
《Unit Testing Services in AngularJS for Fun and for Profit》

使用Protractor进行web功能测试

本文介绍了使用Protractor对AngularJS开发的web应用进行自动化功能测试的方案和详细操作指导。

名词解释

Protractor是AngularJS团队发布的一款开源的端到端web测试运行工具。它可以模拟用户的实际交互,帮助验证Angular应用的实际运行状况。Protractor使用Jasmine测试框架来定义测试用例。Protractor为不同的页面交互提供一套健壮的API。相对于其他的端到端的工具,Protractor有着自己的优势,它知道怎么和AngularJS的代码一起运行,特别是应对$digest循环。

环境安装

  1. 首先必须安装执行环境nodejs
  2. 安装浏览器,推荐chrome
  3. 安装protractor+webdriver
    npm install protractor -g
    webdriver-manager update 
    
  4. 安装完成后执行protractor –version,检查安装是否正常
  5. 在命令行控制台启动Selenium测试服务器。
    webdriver-manager start
    //默认情况下,Selenium测试服务器接入地址为:http://localhost:4444/wd/hub
    
  6. 输出测试报告需要安装相关插件
    //输出html报告
    npm install protractor-jasmine2-html-reporter -g
    //输出junit格式的xml报告
    npm install jasmine-reporters -g
    //由于需要在config文件中加载。一般把这两个插件放在根目录的node_modules 目录下
    

工程配置

配置文件举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var Jasmine2HtmlReporter = require('./node_modules/protractor-jasmine2-html-reporter');
var report = require('./node_modules/jasmine-reporters');
exports.config = {
// Selenium server 测试服务器接入地址
SeleniumAddress: 'http://localhost:4444/wd/hub',
// 测试服务器的配置信息
multiCapabilities: [{
browserName: 'firefox'
},{
browserName: 'chrome',
'chromeOptions': {
'args': ['incognito', 'disable-extensions', 'start-maximized']
}
}],
// 需要运行的测试程序代码文件列表
suites: {
scan: 'tc/e2e/scan.js',
app: 'tc/e2e/app.js',
hppd: 'tc/e2e/hppd.js'
},
// 选择使用 jasmine 作为JavaScript语言的测试框架
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
isVerbose: true,
includeStackTrace: false
},
//输出测试报告
onPrepare: function(){
jasmine.getEnv().addReporter(
new Jasmine2HtmlReporter({
savePath: 'report/e2e/',
takeScreenshots: true, //是否截屏
takeScreenshotsOnlyOnFailures: true //测试用例执行失败时才截屏
})
);
jasmine.getEnv().addReporter(
new report.JUnitXmlReporter({
savePath: 'report/tc/e2e/',
consolidateAll: false,
filePrefix:'',
package:'E2E'
})
);
}
};

测试用例怎么写

测试用例是基于jasmine框架,关于用例描述和断言比较,请参考上一篇帖子《基于karma+jasmine的web前端自动化测试》,这里不再赘述了
这里举一个简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var DOMAIN = '127.0.0.1';
var TARGET_ROOT = 'http://'+DOMAIN;
describe('scan all app', function() {
beforeAll(function() {
// 设置cookie
var cookieObj = {
"real_name":"放松一下",
"dept_name":"开发部",
"avatar":"/images/u15.png",
"message":"22"
};
browser.get(TARGET_ROOT + '/index/about.html');
browser.manage().deleteAllCookies().then(function () {
browser.manage().addCookie("login_user",JSON.stringify(JSON.stringify(cookieObj)), '/', DOMAIN);
});
});

it('app.code', function() {
browser.get(TARGET_ROOT + '/app/code/');
expect(element.all(by.binding("project['name']")).count()).toBeGreaterThan(1);
});

it('app.ci', function() {
browser.get(TARGET_ROOT + '/app/ci/');
expect(element(by.binding('userInfo.realName')).getText()).toEqual('李忠伟10183089');
});
});

上述事例主要使用了如下接口:

  1. browser.get(url) 访问url指定的web页面
  2. browser.manage().deleteAllCookies() 删除所有cookie
  3. browser.manage().addCookie(key,value) 添加cookie
  4. element.all(locator) 查找locator描述的所有元素
  5. element(locator) 查找locator描述的单个元素

#浏览器的相关操作

这里举例常用的几个操作,详细的请见参考资料。

1
2
3
4
browser.get(url)   //访问url指定的web页面
browser.close() //关闭当前窗口
browser.sleep(ms) //等待,单位毫秒
browser.pause() //暂停执行,停止在当前页面,主要用于调试

#如何定位元素

上述示例中提到的by.binding,用于定位元素,被称为定位器locator。Protractor中常用的定位器有如下几种:

1
2
3
4
5
by.id('myElement')   //id为myElement的元素
by.css('[class="element"]') //根据元素的属性定位元素,此例为样式class为element的元素。同时也支持jquery的selector语法来定位元素,例如(by.css('.element'))。
by.binding('list.title') //绑定了ng-bind="list.title"的元素
by.repeater('modules.content') //绑定了ng-repeat="module in modules.content"的元素
by.model('person.name') //绑定了ng-model="person.nam"的元素

更多定位器请见参考资料。

定位的元素如何操作

Protractor中使用element(locator)和element.all(locator)来定位元素,前者是定位单个元素,后者是定位所有符合条件的元素。定位到元素后能做哪些操作?常用操作举例如下:

1
2
3
4
5
6
7
8
element.all(by.binding('list.title')).count()   //返回查找到的元素的个数
element.all(by.css('.element')).get(1) //返回定查找到的元素中的第二个元素
element(by.css('.myname')).getText() //返回查找到的元素的text
element(by.id('user_name').sendKeys('user1') //向查找到的元素输入'user1'
element(by.id('user_desc').sendKeys(protractor.Key.ENTER); //向查找到的元素输入回车键
element(by.id('user_desc').sendKeys(protractor.Key.TAB); //向查找到的元素输入TAB键
element(by.id('user_name')).clear(); //清空查找到的元素的内容
element(by.id('submit')).clear(); //点击查找到的元素

参考资料

Protractor官网,不过被墙了,你懂的。
《Protractor入门》推荐!有可能被墙
示例比较多的指导书
《浏览器r相关接口文档》
《元素操作的相关接口文档》

编码新利器--Visual Studio Code

VSC简介

Microsoft在2015年4月30日Build 开发者大会上正式宣布了 Visual Studio Code项目:一个运行于 Mac OS X、Windows和 Linux 之上的,针对于编写现代 Web 和云应用的跨平台源代码编辑器。
Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件扩展,并针对网页开发和云端应用开发做了优化。软件跨平台支持 Win、Mac 以及 Linux,运行流畅,可谓是微软的良心之作……

下面简单介绍下个人认为比较酸爽的特性:

集成Git

VSC默认集成了git。打开git项目根目录后,VSC自动会扫描git相关设置,并且默认定时更新代码,扫描状态。如下图,左侧菜单选择git,就能看到变更的文件信息(包括新建,修改,删除)。在信息输入框里输入注释信息,执行Ctrl+Enter,即可完成 git add . 和git commit操作。
git1png.jpg

然后在右上角打开菜单,执行push操作,或者撤销本次提交,以及其他操作。
双击修改的文件,会自动进行差异对比。如下图

git3.jpg

集成ESLint

ESLint需要自己安装,安装过程如下:

  • 安装ESLint环境。首先安装node.js,然后执行npm install -g eslint安装eslint。
  • 安装vsc的eslint插件。点击左侧菜单最后一项,进入插件安装界面。选择 ESLint点击安装,安装后变成设置按钮。

plugin.jpg

安装完成后,按照提示重启VSC。再次打开后在底部信息框的“问题”标签页里就能看到ESLint检查的结果了。同时在有问题的代码,以及右侧滚动条上也会有错误位置提示。如下图。

eslint.jpg

注意,代理设置方法如下:
文件——>首选项——>设置。在右侧配置里增加如下设置
“http.proxy”: “http://proxy.my.com.cn:80“,
“https.proxy”: “https://proxy.my.com.cn:80“,
“http.proxyStrictSSL”: false,

支持Node.js调试

打开Node.js项目,左侧菜单选择调试功能。点击顶部的启动调试按钮。第一次调试时,会弹出配置页面。对配置文件launch.json里的参数进行配置。主要是配置下路径和启动文件名,配置完成后再次点击启动即可。如下图

debug1.jpg

调试的界面与Chrome dev tool类似,这里就不多介绍了,如下图。

debug2.jpg

内嵌终端控制器

在底部的信息框里选择“终端”选项卡,就能看到命令行界面了,调入命令执行即可,免去了频繁的切换窗口。如下图:

cmd.jpg

代码格式化

选中代码,右键,选择格式化

代码跳转

右键,选择跳转

html、css自动填充

同sublime功能类似,输入标签后按下tab自动补齐。输入字母后,自动关联可能的属性等

支持Markdown预览

Jenkins脚本调用firefox失败的解决办法

问题描述

jenkins执行的shell脚本里,调用karma进行UT测试,日志显示Cannot start Firefox。karma设置的浏览器是firefox,由于firefox无法启动导致用例无法执行。

定位过程

1、开始怀疑是jenkins没有权限,无法调用firefox,给jenkins配置为root权限。可以在脚本中增加 whoami 命令来打印当前用户名称。结果错误依然存在。
2、在在脚本里直接调用firefox,显示错误信息:Error: GDK_BACKEND does not match available displays。经查询,发现是调用firefox时没有传递display ID,导致firefox无法显示界面。
3、原因分析:由于CI服务器上调用jenkins使用的命令是:(java -jar /home/data/jenkins/jenkins.war&) 。此种方式调用jenkins,直接后台进行运行,没有分配显示资源,而且关闭命令窗口,程序依然可以正常执行。但是由于没有display ID,导致firefox无法运行。

解决办法

直接在命令窗口中运行jenkins,保持命令窗口。命令为:java -jar /home/data/jenkins/jenkins.war

关于我

2004年南京航空航天大学本科毕业后来到上海。先后在华为,中兴以及物联网公司就职。做过测试,自动化测试工具,物联网应用软件系统,web系统开发。参与敏捷推广。web全栈均有所涉猎,ASP.net,Android,Spring cloud,AngularJS,Vue,React Native……
目前就职于中兴通讯,在一款企业内部知识共享,组织协作平台项目中担任Tech Leader,系统架构师。

出生于白山黑水之间,
求学于虎踞龙蟠之地,
奋斗于东方明珠之畔,
钟情于coding之中。

曾经写的一些博客

https://my.oschina.net/belllee/blog
https://www.jianshu.com/u/563753cefe4d
http://blog.csdn.net/li_lzw

高性能JavaScript--DOM操作

用JS进行DOM操作的代价是昂贵的,它是富web应用中最常见的性能瓶颈。

DOM

文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。通常在浏览器中DOM和JS都是独立的,因为彼此独立,所以JS操作DOM,性能开销就很大。

提升性能最佳实践

  • 减少操作
    典型场景,循环操作DOM,改成循环拼接,最后操作DOM。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //循环操作DOM15000次
    function innerHTMLLoop(){
    for(var count = 0; count < 15000; count++){
    document.getElementById('here').innerHTML += 'a';
    }
    }
    //只操作1次DOM,在IE8中性能提升273倍
    function innerHTMLLoop(){
    var content = '';
    for(var count = 0; count < 15000; count++){
    content += 'a';
    }
    document.getElementById('here').innerHTML = content;
    }
  • 增加新元素时innerHTML性能高于DOM方法

    1
    2
    3
    4
    5
    var newDiv = "<div></div>";
    document.getElementById('here').innerHTML = newDiv;
    //
    var newElement = document.createElement('div');
    document.getElementById('here').appendChild(newElement);
  • 使用节点clone

    1
    2
    var newElement1 = document.createElement('div');
    var newElementN = newElement1.cloneNode(true);
  • HTML集合使用array代替
    DOM查询方法(getElementByName,getElementByClassName,getElementByTagName),以及部分属性(images,links,forms,elements)返回值是HTML集合(类似数组,有length和下标访问,无push,slice)。这些集合要避免重复访问,因为每次访问都会重新执行查询。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //由于每次添加div后,长度增加,此循环是死循环
    var divList= document.getElementByName('div');
    for(var count = 0; count < divList.length; count++){
    document.body.appendChild(document.createElement('div'));
    }
    //集合保存到变量,避免重复查询
    for(var count = 0; count < document.getElementByName('div').length; count++){
    //do something
    }
    var divList= document.getElementByName('div');
    var len = divList.length
    var arr = toArray(divList); //toArray是自定义的函数,将集合转成array
    for(var count = 0; count < len; count++){
    //do something
    }

注意:此方法会额外增加一次遍历操作,长度小的集合可能不会提升反而下降

  • 局部变量替代

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for(var i = 0; i < document.getElementsByTagName("a").length; i++){
    document.getElementsByTagName("a")[i].class = 'active'
    }
    //改进后
    var list = document.getElementsByTagName("a");
    var len = list.length;
    for(var i = 0; i < len; i++){
    list[i].class = 'active'
    }
  • IE6、7中遍历DOM,nextSibling性能高于childNode

  • 遍历元素节点优选元素节点属性
    childNodes,firstChild和nextSibling这些属性并不区分元素节点和其他类型节点(比如注释和文本节点)。如果只需要查询元素节点,优先使用如下方法代替。(注意:IE6-8只支持children属性)
元素节点属性名 被替代的属性名
children childNodes
childElementCount childNodes.length
firstElementChild firstChild
lastElementChild lastChild
nextElementSibling nextSibling
previousElementSibling previousSibling
  • 利用CSS选择器提高查找效率
    querySelectorAll()方法使用CSS选择器作为参数,并且返回匹配节点的类数组对象。不会返回HTML集合,不会对应实时的文档结构,避免了之前讨论的HTML集合引起的性能和逻辑问题。代码示例如下:
    1
    2
    3
    4
    //改进前
    var els = document.getElementsById("menu").getElementsByTagName("a");
    //改进后
    var els = document.querySelectorAll("#menu a");

如果是组合查询,querySelectorAll()方法更具优势。对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
//改进前
var els = [];
var divs = document.getElementsByTagName("div");
var className = "";
for(var i = 0,len = divs.length; i<len;i++){
className = divs[i].className;
if(className === 'warning' || className === 'notice'){
els.push(divs[i]);
}
}
//改进后
var els = document.querySelectorAll("div.warning, div.notice");

推荐使用querySelector()方法,查询第一个匹配的节点。

重绘与重排

浏览器下载完所有的组件文件(html、js、css、图片)之后,会解析这些组件,并生成两个数据结构:

  1. DOM树:表示页面结构
  2. 渲染树:表示DOM节点如何显示
    DOM树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“帧”或者“盒”,具有内边距padding,外边距margin,边框border和位置position(IE盒模型的高度和宽度包括边框和内边距,W3C只是内容部分。W3C盒模型可使用box-sizing:border-box改成IE盒模型)。一旦DOM树和渲染树构建完成,浏览器就开始显示(绘制)页面元素。
    当DOM的变化影响了元素的几何属性(宽和高),浏览器需要重新计算元素的集合属性,同事其他元素的集合属性和位置也会受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程被称为“重排reflow”。完成重排后,浏览器会重新绘制受影响的部分到平路中,该过程被称为“重绘repaint”。
    并不是所有的DOM变化都会影响几何属性,比如改变背景色,此时只会执行重绘而不会触发重排。重绘和重排都是代价昂贵的操作,需要尽量避免。

触发重排的操作

  1. 添加和删除可见的DOM元素
  2. 元素位置改变
  3. 元素尺寸变化
  4. 内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口尺寸改变
  7. 滚动条的出现和消失会触发整个页面的重排

渲染树变化的排队与刷新

由于重排消耗大,大多数浏览器都会通过队列化修改并批量执行来优化重排过程。获取布局的如下操作会导致队列刷新:

  • offsetTop,offsetLeft,offsetWidth,offsetHeight
  • scrollTop,scrollLeft,scrollWidth,scrollHeight
  • clientTop,clientLeft,clientWidth,clientHeight
  • getCumputedStyle()(currentStyle in IE)
    执行这些属性和方法需要返回最新的布局信息,因此浏览器会执行渲染队列中的操作,已获得最新的布局信息。因此不需要避免频繁执行这些属性和方法。

    最小化重排和重绘

    为减少重排或者重绘,应该合并多次对DOM和样式的修改,然后一次性处理。

    修改样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //优化前,执行了三次重排。大部分现代浏览器进行了优化,可能只执行一次
    var el = document.getElementById('mydiv');
    el.style.borderLeft='1px';
    el.style.borderRight='2px';
    el.style.padding='5px';
    //优化后,只执行一次
    var el = document.getElementById('mydiv');
    el.style.ccsText='border-left:1px;border-right:2px;padding:5px;';
    //第二种优化方法
    var el = document.getElementById('mydiv');
    el.className='active';

批量修改DOM

可以通过如下步骤减少重绘和重排次数:

  1. 使元素脱离文档流
  2. 对其应用多重改变
  3. 把元素待会文档中
    这样操作后只会在1和3补执行两次重排,忽略了步骤2中可能的N次重排。
    使元素脱离文档流的方法有如下三种:
  4. 隐藏元素,应用修改,重新显示
  5. 使用文档片段在当前DOM之外构建一个子树,执行完修改后再把它拷贝回文档
  6. 讲原始元素拷贝到一个脱离文档的节点中,修改这个副本,完成后再替换原始元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function appendDataToElement(appendToElement, data) {
    var a, li;
    for (var i = 0, max = data.length; i < max; i++) {
    a = document.createElement('a');
    a.href = data[i].url;
    a.appendChild(document.createTextNode(data[i].name));
    li = document.createElement('li');
    li.appendChild(a);
    appendToElement.appendChild(li);
    }
    };
    //优化前,循环内N次重排
    var ul = document.getElementById('mylist');
    appendDataToElement(ul, data);
    //第一种,异常和显示
    var ul = document.getElementById('mylist');
    ul.style.display = 'none';
    appendDataToElement(ul, data);
    ul.style.display = 'block';
    //第二种,文档片段
    var fragment = document.createDocumentFragment();
    appendDataToElement(fragment, data);
    document.getElementById('mylist').appendChild(fragment);
    //第三种,元素替换
    var old = document.getElementById('mylist');
    var clone = old.cloneNode(true);
    appendDataToElement(clone, data);
    old.parentNode.replaceChild(clone, old);

推荐第二种方案,其产生的DOM遍历和重排次数最少。

缓存布局信息

当查询布局信息(例如offsets,scroll,client等),浏览器为返回最新值,会刷新队列并应用所有变更。所哟尽量减少布局信息的获取次数,获取后赋值给局部变量,然后再操作局部变量。
例如移动元素的例子,timeout循环部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//改进前
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if (myElement.offsetLeft >= 500) {
stopAnimation();
}
//改进后,先一次性获取初始位置
var current = myElement.offsetLeft;
//然后循环执行操作
current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current >= 500) {
stopAnimation();
}

让元素脱离动画流

采用绝对位置定位,可以减少元素尺寸变化时,对其他元素造成的重排影响。
例如折叠/展开这种交互方式,每次变化都会导致下方所有元素的移动。如果把这部分元素使用绝对位置定位,覆盖其他部分。这样就能避免下方元素的重排和重绘,减少开销。

IE和:hover

从IE7开始,IE允许任何元素上使用:hover这个CSS伪选择器。如果大量使用:hover,响应速度下降明显。特别是IE8。

事件委托

如果进行大量的DOM元素事件绑定,会引入性能问题。一个简单的解决方案是事件委托。只需给最外层的元素绑定事件,利用事件逐层冒泡并能被父级元素捕获,就可以处理所有子元素上触发的事件。

centos安装mysql过程记录

软件版本:centos7,mysql5.6

具体安装过程:

1、通过ssh上传rpm安装文件

2、由于centos默认安装了mariadb,需要先卸载。

查询是否已安装:

 $ rpm -qa |grep mariadb 

如果已经安装,则卸载:

$ rpm -e --nodeps mariadb-libs-5.5.47-1.el7_2.x86_64

3、分别安装mysql server/client/devel。

切换到安装包目录:    

$ cd /usr/local/sw/MySql5.6

 执行安装命令:

$ rpm -ivh MySQL-server-5.6.13-1.el6.x86_64.rpm

 $ rpm -ivh MySQL-client-5.6.13-1.el6.x86_64.rpm

 $ rpm -ivh MySQL-devel-5.6.13-1.el6.x86_64.rpm

4、复制服务启动文件

$ cp /usr/share/mysql/mysql.server /etc/init.d/mysqld

5、启动mysql服务

$ /etc/init.d/mysqld start

检查mysql 是否已经启动:$ netstat -atln | grep 3306

6、把mysql加入开机自启动:

$ chkconfig  --add mysqld  

7、设置账号密码

关闭mysql:$ /etc/init.d/mysql stop

进入mysql安全模式: $ mysqld_safe --user=mysql --skip-grant-tables --skip-networking &

设置root账号密码,允许root远程连接,开发所有权限:

mysql -u root

mysql> use mysql;

mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';

mysql> INSERT INTO mysql.user (Host,User,Password,ssl_cipher,x509_issuer,x509_subject) VALUES ('%','root',PASSWORD('newpassword'),"","",""); 

mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'newpassword' WITH GRANT OPTION;

mysql> FLUSH PRIVILEGES;

mysql> quit

WEB前端介绍

1、WEB前端是神马

Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征。在互联网的演化进程中,网页制作是Web1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行为也以浏览为主。2005年以后,互联网进入Web2.0时代,各种类似桌面软件的Web应用大量涌现,网站的前端由此发生了翻天覆地的变化。网页不再只是承载单一的文字和图片,各种富媒体让网页的内容更加生动,网页上软件化的交互形式为用户提供了更好的使用体验,这些都是基于前端技术实现的。 以前会Photoshop和Dreamweaver就可以制作网页,现在只掌握这些已经远远不够了。无论是开发难度上,还是开发方式上,现在的网页制作都更接近传统的网站后台开发,所以现在不再叫网页制作,而是叫Web前端开发。Web前端开发在产品开发环节中的作用变得越来越重要,而且需要专业的前端工程师才能做好。Web前端开发是一项很特殊的工作,涵盖的知识面非常广,既有具体的技术,又有抽象的理念。简单地说,它的主要职能就是把网站(Web App网页应用和Hybrid App混合应用)的界面更好地呈现给用户。

2、前端技术

W3C标准

W3C标准不是某一个标准,而是一系列标准的集合。网页主要由三部分组成:结构(Structure)、表现(Presentation)和行为(Behavior)。对应的标准也分三方面:结构化标准语言主要包括HTML,表现标准语言主要包括CSS,行为标准主要包括对象模型(如W3C DOM)、ECMAScript等。

简单来说就是: html(结构) + css(表现) + js(行为)

学习资源:http://www.w3school.com.cn/
名词解释:W3C——万维网联盟(World Wide Web Consortium)

HTML——WEB应用的基石

HyperText Markup Language 超文本标记语言。超文本是用超链接的方法,将各种不同空间的信息组织在一起的网状文本。信息可以包含图片、链接,甚至音乐、程序等非文字元素。

html文件示例如下:

标签的基本格式如下:

示例中的标签是个闭合标签,有部分标签不是闭合标签。例如:

一个标准的html文件主要包括如下常用的标签:

  • !DOCTYPE:文档类型声明,它的目的是要告诉浏览器,它应该使用什么样的文档类型定义来解析文档。示例中所示的文档类型是html5。文档类型一般是向下兼容的,html5基本兼容html4.01.
  • html:文档的根节点。 与 标签限定了文档的开始点和结束点,在它们之间是文档的头部和主体。
  • head: 标签用于定义文档的头部,它是所有头部元素的容器。 中的元素可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等等。
  • meta: 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎的关键词,文件编码类型,浏览器版本等。
  • title:文档标题,浏览器的标签页显示的内容。
  • link:标签定义文档与外部资源的关系,最常见的用途是链接样式表(css文件),ICON。
  • script: