js作用域

作用域

引:Engine/Scope Conversation

Engine/Scope Conversation
from You Don’t Know JS: Scope & Closures
这一章节,生动详细的向我们描述了js解析引擎是如何运行的,这对理解我们的作用域非常有帮助,有兴趣的可以瞅一眼。下面通过几个简单的题来巩固一下。

1.下面代码alert显示的内容是什么?
function a() {
    var foo = 1;
    b();
}

function b() {
    alert(foo);
}

function c() {
    var foo = 1;
    d();
    function d() {
        alert(foo);
    }
}

a();
c();

这道题主要考察的是js中变量的作用域的问题。运行a(),得到的结果是foo is not defined,且程序停止,不会再执行c(),如果把a()注释掉的话,会alert(1)
function a(){}中声明的变量foo是局部变量,无法被外界访问,所以function b(){}会报错。

function d(){}中的foo虽然也是局部变量,但是foo的作用域恰好包括了function d(){},所以这个foo可以找到。

2.下面代码console显示的内容是什么?
var scope="global";  
function t(){  
    console.log(scope);  
    var scope = "local";
    console.log(scope);  
}  
t();

有些童鞋想当然的认为,因为代码还没有执行到var scope = "local",所以第一个显示的内容是global,第二个是local
然而事实却不是这样的,第一个显示的内容是undefined。如果按照js解析引擎去解读这段代码,应该可以这样去理解:

var scope; //只是知道有这个变量,还没有进行赋值
function t() {} //函数声明提前
t(); //执行这个方法
function t() { //开始观察方法的内部
    var scope; //这个是局部变量,变量声明提前
    console.log(scope); //没有赋值,所以为undefined
    scope = "local"; //对局部变量进行赋值
    console.log(scope; //显示为 local
}
3.es5跟es6的作用域区别

在es5中,是没有块级作用域这个概念的,只有函数作用域,下面的这个例子就可以说明:

var name="global";  
if(true){  
    var name="local";  
    console.log(name)  
}  
console.log(name);

这里的两个console显示的内容都是local,因为如果有块级作用域的话,第二个console出来的内容应该是global,但是name这个变量被if中的name给覆盖了。
es6中,多了let这个声明变量的关键字,使用let声明变量的作用域是块级作用域。还是上面es5中的代码,我们将if中的var变为let看一下:

var name="global";  
if(true){  
    let name="local";  
    console.log(name)  
}  
console.log(name);  

这时,console出来的内容就是

local
global

这就证实了,使用let声明变量的作用域是块级作用域。

4.为下面html中每一个li添加点击事件,点击alert出自己为ul中的第几个。
<ul>
    <li>内容</li>
    <li>内容</li>
    <li>内容</li>
    <li>内容</li>
    <li>内容</li>
</ul>

下面代码中是否有问题?如果有应该如何改善?

var liList = document.getElementsByTagName('li');
for(var i = 0, li; li = liList[i]; i++) {
    li.addEventListener('click', function() {
        alert(i);
    }, false);
}

这道题有两个问题,第一个是for循环的写法上存在问题,没有说明i的最大值是多少;第二个问题是根据js中变量的作用域链来看,i在这个匿名函数中没有声明,所以会去外面找,但是这个i因为没有最大值,一直++,所以也不会知道i是多少。
应该使用如下方法去写:
方法一:

var liList = document.getElementsByTagName('li');
for(var i = 0, len = liList.length; i < len; i++) {
    (function (i) {
        liList[i].addEventListener('click', function () {
            alert(i);
        });
    })(i);
}

这种方法使用闭包的方式,将i传入。不会出现每次调用因为匿名函数中找不到i,去外部找,i已经加到最大值的情况。
方法二:

for(let i = 0, len = liList.length; i < len; i++) {
    liList[i].addEventListener('click', function () {
        alert(i);
    })
}

使用es6的块级作用域解决。

浏览器兼容问题【转】

1. ie6.0横向margin加倍

产生因素:块属性、float、有横向margin。
解决方法:display:inline;

2. ie6.0下默认有行高

解决方法:overflow:hidden;或font-size:0;或line-height:xx px;

3. 在各个浏览器下img有空隙(原因是:回车。)


解决方法:让图片浮动。

4. 一个父标签与几个子标签嵌套,父标签不浮动,子标签float,子标签不撑开父的高度。

解决方法:a 在子标签最后清浮动
{
<div style="height:0;clear:both;">
&nbsp;
</div>
}

b 父标签添加{overflow:hidden;}
c 给父标签设置高度

5. Ie6下,不识别最大宽、高度和最小宽高度,意即min-width/height和 Max-width/height在ie6中没效果,

解决方法:
(1):

.abc{border:1px blue solid;width:200px;height:200px;}
html>body .abc{width:auto;height:auto;min-width:200px;min-height:200px;}

(2):

.abc{width:200px;height:200px;_width:200px;_height:200px;}

(因为ie6有一个特征,当定义一个高度时,如果内容超过高度,元素会自动调整高度。)

6. Ie6里面:如li设宽、高,并且li里面的标签浮动,那么li之间会有间距

解决方法:li不设宽、高或者li内的标签不浮动

7. li之间有间距

解决方法:li 设置vertical-align:middle;
8 3像素问题:ie6下,当浮动元素与流动元素并列显示时,他们之间会存在三像素问题。
解决方法:用hack技术, 例如:所有浏览器通用 height:100px;
ie6专用_height:100px;
ie7专用*+height:100px;
ie6/ie7共用*height:100px;

9. 当定义行内元素为包含框时,且包含框包含的绝对定位元素以百分比为单位进行定位时,会出现混乱。

解决方法:在行内元素里加入`{zoom:1;}`

10. 当多个浮动元素中间夹杂着HTML注释语句时,如果浮动元素宽度为100%,则在下一行多显示一个上一行最后一个字符。

解决办法:给浮动元素添加display:inline;

11. opacity 定义元素的不透明度

filter:alpha(opacity=80);/*ie支持该属性*/
opacity:0.8;/*支持css3的浏览器*/

12. 两个块元素,竖向的margin值不增加,会重叠,其间距为最大margin值。

13. 优先级:

被!important 注明的css属性具有最高优先级(.abc{color:red !important;})。但在ie6中!important具有一个bug:在同一组css属性中,!important不起作用。

14. 火狐不识别background-position-y 或background-position-x;

15. ie6 不支持 fixed

/*对于非IE6可以这样写*/
#top{  
    position:fixed;  
    bottom:0;  
    right:20px;  
}  

/*但是IE6是不支持fixed定位的,需要另外重写*/
#top{  
    position:fixed;  
    _position:absolute;  
    top:0;  
    right:20px;  
    _bottom:auto;  
    _top:expression(eval(document.documentElement.scrollTop));
}  

/*使用hack使IE6实现该效果,但这个东东会闪烁,需要以下代码*/
*html{  
    background-image:url(about:blank);  
    background-attachment:fixed;  
}  

/*使固定在顶部*/
#top{  
    _position:absolute;  
    _bottom:auto;  
    _top:expression(eval(document.documentElement.scrollTop));  
}  

/*固定在底部*/
#top{  
    _position:absolute;  
    _bottom:auto;  
    _top:expression(eval(document.documentElement.scrollTop+document.documentElement.clientHeight-this.offsetHeight-(parseInt(this.currentStyle.marginTop)||0)-(parseInt(this.currentStyle.marginBottom)||0)));  
}  
/*垂直居中*/
#top{
    position:fixed;
    top:50%;
    margin-top:-50px;
    _position:absolute;
    _top:expression(eval(document.documentElement.scrollTop+document.documentElement.clientHeight/2));
}

16. 解决 ie6 最大、最小宽高 hack方法

/* 最小宽度 */
.min_width{
    min-width:300px;
    _width:expression(parseInt(this.clientWidth) < 300 ? "300px" : this.clientWidth);
}

/* 最大宽度 */
.max_width{
   max-width:600px;
   _width:expression(parseInt(this.clientWidth) > 600 ? "600px" : this.clientWidth);
}

/* 最小高度 */
.min_height{
   min-height:200px;
   _height:expression(parseInt(this.clientHeight) < 200 ? "200px" : this.clientHeight);
}

/* 最大高度 */
.max_height{
   max-height:400px;
   _height:expression(parseInt(this.clientHeight) > 400 ? "400px" : this.clientHeight);
}

17. z-index不起作用的 bug

1)ie6下 首先讲讲第一种z-index无论设置多高都不起作用情况。这种情况发生的条件有三个:1、父标签position属性为relative;2、问题标签含有浮动(float)属性。
2)所有浏览器:它只认第一个爸爸
层级的高低不仅要看自己,还要看自己的老爸这个后台是否够硬。用术语具体描述为:
父标签position属性为relative或absolute时,子标签的absolute属性是相对于父标签而言的。而在IE6下,层级的表现有时候不是看子标签的z-index多高,而要看它们的父标签的z-index谁高谁低。

18. ie各个版本hack

/*类内部hack:*/
    .header {_width:100px;}            /* IE6专用*/
    .header {*+width:100px;}        /* IE7专用*/
    .header {*width:100px;}            /* IE6、IE7共用*/
    .header {width:100px\0;}        /* IE8、IE9共用*/
    .header {width:100px\9;}        /* IE6、IE7、IE8、IE9共用*/
    .header {width:330px\9\0;}    /* IE9专用*/

/*选择器Hack:*/
    *html .header{}        /*IE6*/
    *+html .header{}    /*IE7*/

chrome插件开发

Chrome浏览器小插件开发

跟对象好了六年多,终于领证了,然而还没有求婚…
之前看知乎上有个网友说自己没有求婚,然后每次一吵架,他老婆就会拿这个没求婚的事儿出来怼他。看来,求婚的事儿真的是迫在眉睫了呀。
但是怎么求婚呢?摆一圈玫瑰、一圈蜡烛、一圈气球?还是…
作为一个程序员,还是搞一个看上去很浪漫,又是自己写的东西出来吧。
最开始想的是做个app,虽然自己是web前端,但是用之前工作中用过的egret或者也有其他的打包工具也可以打包成ios的app。但是这个周期可能稍微长一些,所以就想到了开发一个chrome的小插件,每当她打开新标签页的时候就能够看到一张精美的图片,一段格言锦句,可以让我一直关心她,哈哈:)
下面就说一下这个小插件的做法吧。
项目地址:https://github.com/ZhangShuangV/chrome-plugin

start

项目结构

chrome的插件是由一个名为manifest.json的配置文件定义的,这个跟nodejs的package.json很像,另外还包含了一些html、js、css文件。
目录结构大致如下:

manifest.json

1.基本属性
包括插件名称name、版本version、描述description、图标icon、manifest版本manifest_version等等。
其中manifest_version必须为2,这个看其他的教程也是这么说的,但是自己试了一下,chrome提示这个字段必须set为2。
version这个字段,每次在chrome应用商店提交应用的时候,必须更新。我之前第二次上传应用商店的时候,就没有更新这个字段,结果就报错,提示是增加版本号。
description这个字段是指整个插件的描述,用鼠标指到插件的图标上面显示的内容。
icon这个字段为插件显示的图标

{
    "name": "tabplus",
    "version": "0.1",
    "description": a chrome plug-in for new tab",
    "manifest_version": 2,
    "icon": {
        "128": "./img/tabplus.png"
    }
}

2.chrome工具栏提示 browser_action
browser_action 指定扩展的图标放在 Chrome 工具栏中,它定义了扩展图标文件位置(default_icon)、悬浮提示(default_title)和点击扩展图标所显示的页面位置(default_popup)。

"browser_action": {
    "default_icon": {
        "16": "./resource/img/tabplus.png",
        "48": "./resource/img/tabplus.png",
        "128": "./resource/img/tabplus.png"
    },
    "default_title": "a new chrome tab",
    "default_popup": "./page/popup.html"
},

我写的这个插件,点开后的效果如下:
chrome工具栏效果

3.选项页 options_page
options_page 属性定义了扩展的设置页面,配置后在扩展图标点击右键可以看到 选项,点击即打开指定页面。注意这里面的‘s’。
"options_page":"./page/options.html"

4.权限 permissions
permissions 属性是一个数组,它定义了扩展需要向 Chrome 申请的权限,比如通过 XMLHttpRequest 跨域请求数据、访问浏览器选项卡(tabs)、获取当前活动选项卡(activeTab)、浏览器通知(notifications)、存储(storage)等,可以根据需要添加,如果内容涉及到了跨域等问题,都需要在此字段中添加相应的地址。

"permissions": [
    "tabs",
    "activeTab",
    "notifications",
    "storage",
    "declarativeContent",
    "*://www.zhangshuang.top/*",
    "*://www.baidu.com/*"
],

5.自定义页面替换默认页面 chrome_url_overrides
chrome_url_overrides 属性可以自定义的页面替换 Chrome 相应默认的页面,比如新标签页(newtab)、书签页面(bookmarks)和历史记录(history)。

"chrome_url_overrides": {
    "newtab": "./page/tab.html"
},

6.在指定网站添加文件 content_scripts
content_scripts 属性可以帮助我们实现给特定的站点添加特定的文件。例如去除百度搜索广告,我们就可以这么来实现:

"content_scripts": [{
    "matches": ["*://www.baidu.com/*"],
    "css": ["./resource/css/noad.css"]
}]

其中noad.css是自己写的文件,隐藏百度搜索的广告部分。

#content_left>div:not(.c-container) {
    height:0;
    overflow: hidden;
    margin-bottom: 0;
}
#content_left>div.leftBlock, .hit_top_new {
    height: auto;
}

调试

插件开发完成之后,我们还需要进行调试。
首先打开 Chrome 设置-扩展程序(chrome://extensions/)页面,勾选 开发者模式,点击 加载正在开发的扩展程序 按钮,选择扩展所在的文件夹,就可以在浏览器工具栏中看到自己写的扩展了。
如果该扩展带有图标,右键单击图标,点击 审查弹出内容 即可。

附 manifest.json配置

{
    "manifest_version": 2,
    "name": "TabPlus",
    "description": "a chrome plug-in for new tab",
    "version": "0.1.2",
    "icons": {
        "16": "./resource/img/tabplus.png",
        "48": "./resource/img/tabplus.png",
        "128": "./resource/img/tabplus.png"
    },
    "browser_action": {
        "default_icon": {
            "16": "./resource/img/tabplus.png",
            "48": "./resource/img/tabplus.png",
            "128": "./resource/img/tabplus.png"
        },
        "default_title": "a new chrome tab",
        "default_popup": "./page/popup.html"
    },
    "options_page": "./page/options.html",
    "permissions": [
        "tabs",
        "activeTab",
        "notifications",
        "storage",
        "declarativeContent",
        "*://www.zhangshuang.top/*",
        "*://www.baidu.com/*"
    ],
    "homepage_url": "http://www.zhangshuang.top",
    "chrome_url_overrides": {
        "newtab": "./page/tab.html"
    },
    "background": {
        "scripts": ["./resource/js/background.js"]
    },
    "content_scripts": [{
        "matches": ["*://www.baidu.com/*"],
        "css": ["./resource/css/noad.css"]
    }]
}

egret绘制环形图

之前分享过通过highcarts、echarts绘制饼图(环形图)。前段时间的H5游戏项目中,也通过egret绘制过环形图,下面就分享一下,如何通过egret绘制环形图。

效果图

先看一下效果图
egret绘制环形图

分析

这种绘制方法肯定不是只用一次,所以我们需要将这种方法封装到函数中,然后我们通过传入几个参数就可以绘制出环形图。
function drawCircle(obj, data) {}

先来分析一下这个图都有几部分组成。
首先我们可以看出环形图的颜色是分为深、浅两种颜色,中间是一个icon,下边部分是图例。

环形图主体部分

因为不是使用highcharts、echarts这种插件,所以所有的东西都需要我们自己去写,不能使用一些原有的配置。
在开始绘制之前,我们需要先算出来这个环形图要绘制成几部分,也就是传入的数据的length是多少,然后我们还需要算出绘制的起始和结束角度startDegendDeg

const length: number = data.length;
let startDeg = 0;
let endDeg = 0;
// 还有一些其他的,如要绘制的图形的宽、高等属性,根据需要自行设置即可    

在基本的配置完成后,我们需要先创建一个放置环形图及其图例的区域sprite,下面的objWidthwidth为前一步的自行设置,obj为传入的对象,后面就不在单独说明了。如果有不懂的地方,最后也有所有代码的汇总。

let percentBg: egret.Sprite = new egret.Sprite();
percentBg.width = width;
percentBg.height = height;
percentBg.x = (objWidth - width) / 2;
percentBg.y = 95;
obj.addChild(percentBg);

环形图的区域跟上面一样,也是创建一个sprite,然后放置到percentBg上面。

通过for循环进行外层浅色区域环形图的绘制,这里面同时也会将startDegendDeg分别赋值。

for(let i = 0; i < length; i++) {
    endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
    circleBg.graphics.beginFill(GameConfig.lightColor[i]);
    circleBg.graphics.moveTo(width / 2, width / 2);
    circleBg.graphics.lineTo(width, width / 2);
    circleBg.graphics.drawArc(width / 2, width / 2, width / 2.3, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
    circleBg.graphics.lineTo(width / 2, width / 2);
    circleBg.graphics.endFill();
    startDeg = endDeg;
}

先通过传入数据中的配比情况,算出endDeg结束角度,然后通过egret的绘图API绘制。
内层深色区域绘制方法同上,只需要将半径变小即可。
绘制完成后,展现在我们面前的是下面的样子
只绘制了深色跟浅色区域
这个跟我们的需求还相差很远,这里就需要我们在以同样的圆心,绘制上一个白色的圆形来遮挡住中间的部分。

circleBg.graphics.beginFill(GameConfig.TextColors.white); //环形图中间白色部分
circleBg.graphics.moveTo(width / 2, width / 2);
circleBg.graphics.lineTo(width, width / 2);
circleBg.graphics.drawArc(width / 2, width / 2, width / 3.5, 0, 360 * Math.PI / 180);
circleBg.graphics.lineTo(width / 2, width / 2);
circleBg.graphics.endFill();

并且再以同样的圆心,绘制上一个icon。icon因为是固定的,所以建议使用图片Bitmap,因为绘制会比直接使用图片更加消耗性能。

let centerIcon = ZpyGameUtils.createBitmapByName("card_dark_icon_png"); //中心icon
centerIcon.x = width / 2 - centerIcon.width / 2 * 0.6;
centerIcon.y = width / 2 - centerIcon.height / 2 * 0.6;
centerIcon.scaleX = 0.6;
centerIcon.scaleY = 0.6;
circleBg.addChild(centerIcon);

环形图图例

绘制完主体部分,我们再来绘制一下图例部分。这个相对于主体环形图来说,就很简单了。icon是图例前面的小色块,label是图例文字部分。

let labelBg: egret.Sprite = new egret.Sprite();
labelBg.width = width - 40;
labelBg.x = 20;
labelBg.y = width;
for (let i = 0, len = data.length; i < len; i++) {
    let icon: egret.Shape = new egret.Shape();
    icon.graphics.beginFill(GameConfig.darkColor[i]);
    icon.graphics.drawRect(0, i * lineHeight, 20, 20);
    icon.graphics.endFill();
    labelBg.addChild(icon);

    let label: egret.TextField = ZpyGameUtils.createText(data[i].industryName + '  ' + (data[i].industryPercent).toFixed(2) + '%', GameConfig.TextColors.black, 20);
    label.x = 30;
    label.y = lineHeight * i;
    labelBg.addChild(label);
}

可滑动

因为环形图的数据可能很多,图例一页可能会显示不过来,所以我们还需要一个可以上下滑动的组件来扩展。

let percentTotalBg: egret.Sprite = new egret.Sprite();
percentTotalBg.width = width;

percentTotalBg.addChild(labelBg);
percentTotalBg.height = width + length * lineHeight;

var scrollView: egret.ScrollView = new egret.ScrollView();
scrollView.setContent(percentTotalBg);
scrollView.width = width;
scrollView.height = height;
percentBg.addChild(scrollView);

前面步骤中生成的所元素最终会放置到percentTotalBg上,然后声明一个滑动区域scrollView,通过setContent方法将所有元素的区域设置为可滑动的区域。

最终代码

function drawCircle(obj, data) {
    const objWidth: number = obj.width;
    const objHeight: number = obj.height;
    const width: number = obj.width * 0.8; //新创建的背景的宽度,宽高相同
    const height: number = obj.height * 0.8; //新创建的背景的高度
    const lineHeight: number = 35;
    const length: number = data.length; //数据长度
    let startDeg = 0; //扇形起始角度
    let endDeg = 0; //结束角度

    let percentBg: egret.Sprite = new egret.Sprite();
    percentBg.width = width;
    percentBg.height = height;
    percentBg.x = (objWidth - width) / 2;
    percentBg.y = 95;
    obj.addChild(percentBg);
    let percentTotalBg: egret.Sprite = new egret.Sprite();
    percentTotalBg.width = width;
    let circleBg: egret.Sprite = new egret.Sprite();
    percentTotalBg.addChild(circleBg);
    circleBg.width = width;
    circleBg.height = width;
    circleBg.y = 0;

    for (let i = 0; i < length; i++) { //环形图浅色部分
        endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
        circleBg.graphics.beginFill(GameConfig.lightColor[i]);
        circleBg.graphics.moveTo(width / 2, width / 2);
        circleBg.graphics.lineTo(width, width / 2);
        circleBg.graphics.drawArc(width / 2, width / 2, width / 2.3, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
        circleBg.graphics.lineTo(width / 2, width / 2);
        circleBg.graphics.endFill();
        startDeg = endDeg;
    }
    for (let i = 0; i < length; i++) { //环形图深色部分
        endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
        circleBg.graphics.beginFill(GameConfig.darkColor[i]);
        circleBg.graphics.moveTo(width / 2, width / 2);
        circleBg.graphics.lineTo(width, width / 2);
        circleBg.graphics.drawArc(width / 2, width / 2, width / 2.6, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
        circleBg.graphics.lineTo(width / 2, width / 2);
        circleBg.graphics.endFill();
        startDeg = endDeg;
    }
    circleBg.graphics.beginFill(GameConfig.TextColors.white); //环形图中间白色部分
    circleBg.graphics.moveTo(width / 2, width / 2);
    circleBg.graphics.lineTo(width, width / 2);
    circleBg.graphics.drawArc(width / 2, width / 2, width / 3.5, 0, 360 * Math.PI / 180);
    circleBg.graphics.lineTo(width / 2, width / 2);
    circleBg.graphics.endFill();

    let centerIcon = ZpyGameUtils.createBitmapByName("card_dark_icon_png"); //中心icon
    centerIcon.x = width / 2 - centerIcon.width / 2 * 0.6;
    centerIcon.y = width / 2 - centerIcon.height / 2 * 0.6;
    centerIcon.scaleX = 0.6;
    centerIcon.scaleY = 0.6;
    circleBg.addChild(centerIcon);
    percentTotalBg.addChild(circleBg);

    let labelBg: egret.Sprite = new egret.Sprite();
    labelBg.width = width - 40;
    labelBg.x = 20;
    labelBg.y = width;
    for (let i = 0, len = data.length; i < len; i++) {
        let icon: egret.Shape = new egret.Shape();
        icon.graphics.beginFill(GameConfig.darkColor[i]);
        icon.graphics.drawRect(0, i * lineHeight, 20, 20);
        icon.graphics.endFill();
        labelBg.addChild(icon);

        let label: egret.TextField = ZpyGameUtils.createText(data[i].industryName + '  ' + (data[i].industryPercent).toFixed(2) + '%', GameConfig.TextColors.black, 20);
        label.x = 30;
        label.y = lineHeight * i;
        labelBg.addChild(label);
    }
    percentTotalBg.addChild(labelBg);
    percentTotalBg.height = width + length * lineHeight;

    var scrollView: egret.ScrollView = new egret.ScrollView();
    scrollView.setContent(percentTotalBg);
    scrollView.width = width;
    scrollView.height = height;
    percentBg.addChild(scrollView);
}

如果是在namespace中,请不要忘记通过export方法使其可以在外部访问。

echarts绘制饼图

之前写过一篇highcharts绘制环形配比图,当时boss建议通过highcharts来绘制。但是公司的官网的图表是通过echarts来绘制的,并且这次把这个需求交给了我。所以这次只能再研究一下echarts的绘制饼图的api。

start

echarts因为是百度开发的,所以它的API都是中文的,相对较简单一些。
需求如本图
如上图所示,本次的需求相对比较简单,只需要展示出配比详情及图例即可。左侧较好实现,右侧如果使用图例legend的话非常麻烦,里面包含了很多小项也包含了很多数据。所以决定右侧还是使用html来写。

为echarts准备一个DOM容器

创建一个DOM元素,为其取个id名

<body>
    <div id="echarts_pie" style="width:300px;height:300px;"></div>
</body>

最简单的echarts配置

首先要通过echarts.init方法,初始化一个echarts实例。这个跟highcharts的$("#container_pie").highcharts({})是相似的。这里的echarts_pie就是上一步设置的DOM元素的id名。

var virtualPie = echarts.init($("#echarts_pie"));

然后配置图表的数据

var option = {
    title: { //图表标题
        text: '资金分配构成',
        x: 'center',
        y: 'bottom',
        textStyle: {fontSize: '14px', color: '#999'},
        padding: 15
    },
    tooltip: {

    },
    legend: {

    },
    series: [
        {
            name: '资金构成',
            type: 'pie'
        }
    ]
    ...
}

这里的配置项有很多(点击查看更多)。
其中图表主元素中的东西,如饼图或者折线图本身,有一些类似min,max,scale这种的属性。图表主元素外的,如图例,提示框这种的都会有show这种是否显示的属性。这一点跟highcharts也是一样的。
就比如在这个项目中,legend图例我就用的show: false,然后用DOM元素来替代。
另外说一下决定图表样式的series,这个属性中有一项type,这个地方决定了图表的样式是饼图pie、折现图line还是柱形图bar等。其中,如果在一个图表中展示多组数据。series这个地方就需要用数组来标示,每一组数据都用一个对象来标示。
再说一下显示组件tooltip。这个是鼠标hover上去之后显示的提示框,这里面显示的内容可以用DOM去编写,如:

tooltip : { //显示组件
    show: true,
    trigger: 'item',
    formatter: "{b} : <em style="position:relative;top:1px;">{d}</em>%"
    }

如果不需要,可以在将属性show设为false

最后,需要我们将刚刚的option配置,指定为图表的配置
virtualPie.setOption(option);

下面贴一下我的配置:

var initPie = function(data){
    var chart = echarts.init($('#pie')[0]);
    var option = {
        title : {
            text: '投资资金构成',
            x:'center',
            y:'bottom',
            textStyle: {fontSize: '14px', color:'#999'},
            padding: 15
        },
        tooltip : {
            trigger: 'item',
            formatter: "{b} : <em style='position:relative;top:1px;'>{d}</em>%"
        },
        legend: {
            show: false
        },
        series : [
            {
                name: '资金构成',
                type: 'pie',
                radius : '60%',
                center: ['50%', '50%'],
                data: data,
                label:{
                    normal: {
                        show: false,
                        position: 'inside'
                    }
                },
                itemStyle: {
                    emphasis: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };

    chart.setOption(option);
};

TypeScript初试(四)

其实之前写js的时候,很少使用继承、原型这种东东。但是在开发游戏的时候,发现这个得到了大量的使用。特别是最开始不会的时候,后端童鞋不厌其烦的给我讲private、public、static等等,先感谢一下公司后端童鞋的技术支持,哈哈。
其实es6中,已经有了class,但是平常的项目中还是用的较早版本的js。而在ts的使用中,class作为原生支持的写法,使得我们能够更好的开发。

start

类 class

如果之前接触过java的童鞋肯定会更觉得,下面的代码跟java非常的相像。

class Person {
    private name: string;

    public constructor(name: string) {
        this.name = name;
    }

    public speakName() {
        return 'My name is' + this.name;
    }
}

var zhangshuang: Person = new Person('zhangshuang');

我们声明了一个类’Person’,这个类里面有三个成员分别是私有变量’name’,构造函数’constructor’,方法’speakName’;
先不用管private跟public,当我们发现this的时候,就可以说明这个变量或者方法是属于’Person’这个类的。
最后一行,通过’new’关键字,我们构造了一个’Person’类的实例。

修饰符 private public
private 私有

我们法相上面的例子中,声明变量’name’时,我们使用了private这个关键字。
当我们使用’private’声明变量时,该变量只能在类的内部使用,也就是这个变量是这个类的私有成员,外部或者实例化时不可以调用。

public 公有

在上面的例子中,我们使用’public’关键字写了一个方法。通过’public’修饰的成员是公有的,可以通过外部或者实例化来调用。如果我们不用’public’修饰的话,变量或者方法默认就是’public'(公有)的。

protected 受保护的

‘protected’作为一个修饰关键字,跟private很类似,但是有一点不同。通过’protected’修饰的成员在派生类中依然可以使用。扒一个例子:

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error

我们不能在’Person’类外使用name,但是可以在继承自’Person’类的’Employee’类中使用。

静态属性 static

在使用类或者其内部的方法时,我们一般都会实例化一个,然后再调用方法。但是当我们把一个变量或者方法的属性设置为static时,就可省去实例化的步骤,直接调用。

class Person {
    public static name: string = 'person';
}
单例模式

在游戏开发中,有些类如场景等,我们是不希望出现多个的,所以这里使用单例模式开发会避免重复实例化同一个类出现的问题。

class MainView {

    private static instance: MainView;

    public static getInstance() : MainView {
        if(MainView.instance == null) {
            MainView.instance = new MainView();
        }
        ruturn MainView.instance;
    }
}

这里就是通过判断instance这个属性是否为null,如果是null则实例化一个类,如果不为null则直接ruturn instance

继承 extends
class Person {
    private name: string;

    public constructor(name: string) {
        this.name = name;
    }

    public speakName() {
        return 'My name is' + this.name;
    }
}

class Student extends Person {
    public constructor() {
        super();
    }

    public grade: number;
}

上面的例子中,我们先声明了一个类Person,然后又声明了一个类Student继承自Person类。
构造函数中的super()标示,这个类继承了原型中的所有的方法和属性。

TypeScript初试(三)

之前在js中,为了防止全局变量污染,我们经常会声明一个对象,然后将变量或者函数作为对象的一个属性存在。
但是在ts中,我们可以通过关键字namespace来解决这个问题。
这里的namespace相当于es6中的module。

start

下面通过一个例子来说明一下

namespace gameConfig = {

    export const TextColor = {
        valueGolden: 0xf7b44a, //净值数值颜色
        transparent: 0x00000000, //透明色
        btnGolden: 0xffe8a9 //按钮上的文字的颜色
    }

    export enum errorMsg = {
        "网络连接故障,请耐心等待" = 5000,
        "账号在其他位置登录,如不是本人请更改密码" = 5001
    }

    export let isProto: boolean = false;

    let a;

    export function curHeight(): number {
        //当前游戏宽度
        console.log(a);
        return egret.MainContext.instance.stage.stageHeight;
    }

    export class Card{
        console.log('this is a card');
    }

}

class Example {
    const gold = gameConfig.TextColor.valueGolden;
}

在namespace命名空间中,我们可以声明变量,编写函数,编写类等等。
当我们在变量声明或者函数、类的编写之前使用export关键字后,我们就可以在命名空间之外找到它。当然有一些内部使用的,我们没有必要使用export关键字。

TypeScript初试(二)

在js中,变量的声明一般用var,但是在上一次分享中大家可能也看到了,当时采用的变量声明的关键字为let。那么这个let是个什么东西呢?
let 是es6新增的一个变量声明关键字,用来声明局部变量。同时let也是ts中推荐的声明变量的关键字。let声明的变量是局部变量,它的作用于是块级作用域。

有一个比较好的例子可以让我们清楚的了解

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

上面代码的执行结果为:

10
10
10
10
10
10
10
10
10
10

改为let后:

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}

执行结果:

0
1
2
3
4
5
6
7
8
9

这个才是我们想要的结果,因此推荐在ts中或者es6中使用let来代替。

start

let 声明

通过上面的小例子可以看出var声明存在的问题,这也说明了用let声明变量的好处。除了名字不一样以外,let声明变量的写法与var是一样的。
let i: number = 0;
我们都知道,在js中,通过var声明的变量会有一个变量声明提前,即js解析引擎读取到var之后,会将var声明的变量提升至开始并赋值undefined,当用到的时候,才会再进行重新赋值。

变量声明不提前

而使用let声明的变量,不会出现变量声明提前,所以如果在let声明之前使用let声明的变量会报错。

a++; //变量a的作用域在let声明之后,所以此处找不到,报错。
let a: number = 0;
let的作用域

如果是在函数中或者其他块级作用域如for、if等,用let声明的话,函数外面也是找不到的。

function f(input: boolean) {
    let a = 100;

    if (input) {
        // if语句块在变量a的作用域中,可以找到。
        let b = a + 1;
        return b;
    }

    // 变量b的作用域为if语句块,所以此处找不到,报错。
    return b;
}

在上面的例子中,我们通过let分别声明了两个变量a、b。
a的作用域是f函数体内,所以,在if语句中可以找到。
b的作用域是if语句块内,所以外部找不到就会报错。

关于重复声明的问题

之前的js中,无论用var重复声明一个变量多少次,都不会报错,但是还是只有一个。如果重复声明并给同一个变量赋值的话,也不会报错,只是变量的值是最后一次赋值的值。
但是当我们通过let进行声明变量时,在同一个作用域内声明两个相同的变量就会报错。

var a: string = 'hi';
var a: string = 'hehe'; //不会报错,但是a的值为最后一次赋值的值,即为hehe;


let b: number = 0;
let b; //报错,相同作用域内不能使用let重复声明同一个变量。

const 声明

const是声明常量,这个大家在js中应该也有用过。const的作用域与let相同,但是const声明的变量的值不会发生改变。
const还有一个好处就是,采用const声明的变量如果赋值的是一个表达式或者计算,在声明之初,这个值就已经计算出来了。不会像var一样,每次用到这个变量都会在计算一遍。在项目中遇到了很多绘制环形图或者折线图这种的需求,有很多变量是通过传入的数据或者图形来计算大小的,这个时候使用const,在声明之初就将这个变量的值计算了出来,后续的调用会提高效率。

function drawLine(length: number): void {
    //声明完就会将num的值计算出来。
    const num: number = length * 10 + 1;

    //num已经计算好了,直接调用,不会像var声明的变量一样每次调用都会重新计算。
    console.log(num);
}

const变量的内部状态是可以改变的,如:

const zhangshuang = {
    name: zhangshuang,
    age: 26
}

zhangshuang.name = ZhangShuang; //不会报错,内部状态修改了。

end

let VS const

那么我们什么时候用let,什么时候使用const呢?一般来说,变量的声明最好遵循最小特权原则。也就是说,如果这个变量声明之后不会更改,那么我们就用const来声明;如果以后可能会改变,那我们就使用let来声明。

TypeScript初试(一)

跟egret引擎一样,学习typescript也是因为公司的需求。虽然typescript算是JavaScript的超集,按理说应该跟js很像,但是个人感觉ts真的跟java更像一下。遇到有搞不懂的问题都是像公司后端及android童鞋们求助解决的,在这里先谢啦~
当然有一些像interface接口这种的,我的项目中没有用到,而且我问了几次也不是很理解,感觉不用这个也可以操作。
废话不多说了,下面就说一下我通过这个项目总结的一些经验:

start

ts跟js相比,语法上严禁了一些。js动态语言的特性当然也就没了,书写的时候一般都要跟上类型,如:
let isProto: boolean = false;
后面就不会对这种东东再进行赘述。
这里的 : boolean 就是对变量 isProto 类型的说明,每一种语言都会有相应的基本类型,这些网上都有很多,
例如:TypeScript Handbook等,当时学习的时候就是从网上找的这种的一点一点看的。基本的语法在这里就简单一点带过。

布尔值 boolean

最基本的数据类型就是简单的true/false,这个就是布尔类型(boolean)
let isProto: boolean = false;

数字类型 number

ts中的number跟js一样也是浮点数,这个跟java什么的不太一样,没有int或者float整形或浮点型,只有一个number数字类型。这里有一点需要注意,如果想要通过二进制传输的话,js跟ts的number类型最多只支持15位数字,数字位数变多就会不精确。例如我的项目中,前后端数据传输就采用的protobuf这种二进制格式,在定义userID的时候用的long,并且是18位,结果在传输过来时发现末尾不是0的出现了些问题,变得跟原来不一样了。后来查阅各种资料才发现了这个问题,然后就将这种较长的数字也改为string类型替代了。

let i: number = 0;
let colorRed = 0xff0000;
字符串 string

在ts中,string类型跟js中的一样,就不在多说了
let name: string = 'zhangshuang';
TypeScript Handbook中还介绍了模板字符串的用法,如果有需要也可以参考一下。当然我的项目中没有用到这个。

数组

TypeScript中,数组有两种表示方法:
1.元素类型后跟上[ ]
let numList: number[] = [1,2,3];
2.数组泛型
let numList: Array<number> = [1,2,3];

枚举 enum

最初我理解枚举的时候是将enum分成e+num,本质上还是数字,官方的解释就是将一系列数字赋予友好的名字,它的用法也很强大:

enum fontColor = {
    gameRed = 0xff0099, //游戏中的红色
    gameBlue = 0x0099ff //游戏中的蓝色
}
let red: number = fontColor.gameRed;


enum errorMsg = {
    "网络连接故障,请耐心等待" = 5000,
    "账号在其他位置登录,如不是本人请更改密码" = 5001
}
let eMsg: string = errorMsg[5000];

可以说是结合了对象的.跟数组的[number]的用法于一身,具体怎么使用要看自己。比如第一种的fontColor,颜色数值0xff0099比较难记,如果使用fontColor.gameRed就可以解决这个问题;第二种errorMsg,错误提示较长,就可采用errorMsg[5000]来解决。

任意值 any

ts中的任意值就是为了解决在编程时不清楚变量的类型而定义的一种类型,这个就相当于js的隐式类型转换。

let notSure: any = 4;
notSure = '改变了类型';
空值 void

我理解这个void是通过函数来理解的。有的函数有返回值,有的没有,为了代码清晰严谨,有返回值的函数就写返回值的类型,没有返回值的函数直接写void

//有返回值,类型为string
function getName(name: string): string {
    return name;
}

//没有返回值
function alert(): void {
    alert(1);
}
其他的类型

还有一些类型如元组Tuple,Never等因为在项目中没有用到,所以在这里也没再多说,如果有兴趣可以去TypeScript Handbook中寻找一下。

下次分享一下变量声明var、let、const

egret游戏引擎入门(七)

今天分享几个封装的egret方法。
之前也调研过createjs,感觉egret的绘画API跟createjs的相似,或者说,这一类都是大同小异的。虽然这些方法相较原声的canvas简单一点,但是感觉还是有些繁杂,可以将这些方法封装一下进行调用。
1. 根据资源名称创建图片Bitmap
当我们新建一个egret项目的时候,就会在Main.ts中发现这个方法。这个方法直接传入一个在default.res.json中定义好的图片资源名称,就可以返回一个egret.Bitmap对象。其实这也就是在提示我们将文字、绘图等方法进行封装,使用起来会更加高效。

function createBitmapByName(name: string): egret.Bitmap {
    const resource: egret.Bitmap = new egret.Bitmap();
    const texture: egret.Texture = RES.getRes(name);
    resource.texture = texture;
    return resource;
}

  1. 创建文本类TextFiled
/**
 * 创建文本类TextFiled
 *
 * @param {string} text 要显示的文字
 * @param {number} color 文字的颜色
 * @param {number} [size=20] 文字的大小
 * @param {number} [x=0] 文字的x坐标
 * @param {number} [y=0] 文字的y坐标
 */
function createText(text: string, color: number, size: number = 20, x: number = 0, y: number = 0): egret.TextFiled {
    const resource: egret.TextFiled = new egret.TextFiled();
    resource.text = text;
    resource.color = textColor;
    resource.size = size;
    resource.x = x;
    resource.y = y;
}
  1. 创建带有矢量绘制功能的显示容器sprite
    个人感觉这个方法使用的相当频繁,因为我是将sprite作为一个组件去使用的。打个比方来说,我要创建一张卡牌,那么我先创建一个sprite并将其添加到场景上,然后再创建一些卡牌的图案或者文字,添加到sprite上。每次要移动卡牌的时候,只需要操作sprite就好了,其内的文本类后者矢量绘图类都会随之移动。
    值得一提的是,如果是创建一个类继承自egret.Sprite,并且这个类有点击等监听事件,如果无法触发,请设置其touchEnable属性变为true;如果有些地方触发有效果有些地方无效果,就要创建一个同等大小的shape,并且将其的alpha设置为0,放置在sprite上即可。
/**
 * 创建带有矢量绘制功能的显示容器sprite
 *
 * @param {number} width 显示容器的宽
 * @param {number} height 显示容器的高
 * @param {number} [x=0] 显示容器的x坐标
 * @param {number} [y=0] 显示容器的y坐标
 * @returns {egret.Sprite} 返回显示容器对象
 */
function createSprite(width: number, height: number, x: number = 0, y: number = 0): egret.Sprite {
    const resource: egret.Sprite = new egret.Sprite();
    resource.width = width;
    resource.height = height;
    resource.x = x;
    resource.y = y;
    return resource;
}
  1. 创建矢量图Shape
    我在最开始使用的时候,将x跟y直接用在drawRect中,但是通过egret wing自带的调试工具调试后发现,他的实际位置显示的有问题。所以现在在使用drawRect的时候,将x跟y都设置为0,再通过刚刚传入的x跟y坐标设置这个矢量图对象的位置。
    这个只是创建矩形、还有很多如圆弧、圆形等可以参照这个自行封装。
/**
 * 创建矢量图Shape
 *
 * @param {number} width 矢量图的宽
 * @param {number} height 矢量图的高
 * @param {number} color 矢量图的颜色
 * @param {number} [x=0] 矢量图的x坐标
 * @param {number} [y=0] 矢量图的y坐标
 * @param {number} [alpha=1] 矢量图的透明度
 * @returns {egret.Shape} 返回矢量图对象
 */
function createShape(width: number, height: number, color: number, x: number = 0, y: number = 0, alpha: number = 1): egret.Shape {
    const resource: egret.Shape = new egret.Shape();
    resource.graphics.beginFill(color, alpha);
    resource.graphics.drawRect(0, 0, width, height);
    resource.graphics.endFill();
    resource.x = x;
    resource.y = y;
    return resource;
}

另附一个自己封装的环形图代码:
这个实现了传入一个sprite对象以及固定格式的数据,将环形图及图例添加到传入对象的特定方法。

/**
     * 画饼图
     * obj,在哪个对象上绘制
     * data,饼图数据,格式固定
     *
     * @export
     * @param {any} obj
     * @param {any} data
     */
    export function drawCircle(obj, data) {
        const objWidth: number = obj.width;
        const objHeight: number = obj.height;
        const width: number = obj.width * 0.8; //新创建的背景的宽度,宽高相同
        const height: number = obj.height * 0.8; //新创建的背景的高度
        const lineHeight: number = 35;
        const length: number = data.length; //数据长度
        let startDeg = 0; //扇形起始角度
        let endDeg = 0; //结束角度

        let percentBg: egret.Sprite = new egret.Sprite();
        percentBg.width = width;
        percentBg.height = height;
        percentBg.x = (objWidth - width) / 2;
        percentBg.y = 95;
        obj.addChild(percentBg);
        let percentTotalBg: egret.Sprite = new egret.Sprite();
        percentTotalBg.width = width;
        let circleBg: egret.Sprite = new egret.Sprite();
        percentTotalBg.addChild(circleBg);
        circleBg.width = width;
        circleBg.height = width;
        circleBg.y = 0;

        for (let i = 0; i < length; i++) { //环形图浅色部分
            endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
            circleBg.graphics.beginFill(GameConfig.lightColor[i]);
            circleBg.graphics.moveTo(width / 2, width / 2);
            circleBg.graphics.lineTo(width, width / 2);
            circleBg.graphics.drawArc(width / 2, width / 2, width / 2.3, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
            circleBg.graphics.lineTo(width / 2, width / 2);
            circleBg.graphics.endFill();
            startDeg = endDeg;
        }
        for (let i = 0; i < length; i++) { //环形图深色部分
            endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
            circleBg.graphics.beginFill(GameConfig.darkColor[i]);
            circleBg.graphics.moveTo(width / 2, width / 2);
            circleBg.graphics.lineTo(width, width / 2);
            circleBg.graphics.drawArc(width / 2, width / 2, width / 2.6, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
            circleBg.graphics.lineTo(width / 2, width / 2);
            circleBg.graphics.endFill();
            startDeg = endDeg;
        }
        circleBg.graphics.beginFill(GameConfig.TextColors.white); //环形图中间白色部分
        circleBg.graphics.moveTo(width / 2, width / 2);
        circleBg.graphics.lineTo(width, width / 2);
        circleBg.graphics.drawArc(width / 2, width / 2, width / 3.5, 0, 360 * Math.PI / 180);
        circleBg.graphics.lineTo(width / 2, width / 2);
        circleBg.graphics.endFill();

        let centerIcon = ZpyGameUtils.createBitmapByName("card_dark_icon_png"); //中心icon
        centerIcon.x = width / 2 - centerIcon.width / 2 * 0.6;
        centerIcon.y = width / 2 - centerIcon.height / 2 * 0.6;
        centerIcon.scaleX = 0.6;
        centerIcon.scaleY = 0.6;
        circleBg.addChild(centerIcon);
        percentTotalBg.addChild(circleBg);

        let labelBg: egret.Sprite = new egret.Sprite();
        labelBg.width = width - 40;
        labelBg.x = 20;
        labelBg.y = width;
        for (let i = 0, len = data.length; i < len; i++) {
            let icon: egret.Shape = new egret.Shape();
            icon.graphics.beginFill(GameConfig.darkColor[i]);
            icon.graphics.drawRect(0, i * lineHeight, 20, 20);
            icon.graphics.endFill();
            labelBg.addChild(icon);

            let label: egret.TextField = ZpyGameUtils.createText(data[i].industryName + '  ' + (data[i].industryPercent).toFixed(2) + '%', GameConfig.TextColors.black, 20);
            label.x = 30;
            label.y = lineHeight * i;
            labelBg.addChild(label);
        }
        percentTotalBg.addChild(labelBg);
        percentTotalBg.height = width + length * lineHeight;

        var scrollView: egret.ScrollView = new egret.ScrollView();
        scrollView.setContent(percentTotalBg);
        scrollView.width = width;
        scrollView.height = height;
        percentBg.addChild(scrollView);
    }