得益于前辈的分享,做了一个h5录音的demo。效果图如下:

点击开始录音会先弹出确认框:

首次确认允许后,再次录音不需要再确认,但如果用户点击禁止,则无法录音:

点击发送 将录音内容发送到对话框中。点击即可播放。点击获取录音即可下载最后一次的音频:

播放下载都是围绕blob文件。播放就是让隐藏的audio标签的地址指向内存中的blob:

        this.play = function (audio,blob) {
            blob=blob||this.getBlob().blob;
            audio.src = URL.createObjectURL(blob);
        };  
createObjectURL 我们在用base64显示图片的时候也可以用到。
 img.src = URL.createObjectURL(blob);

这样比一长串的字符串好看很多。同理如果你想销毁该地址对应的数据而节省内存可以这样:

 URL.revokeObjectURL(img.src);

扯远了点。下载就是模拟a标签的点击。

   function downloadRecord(record){
              var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
                save_link.href = URL.createObjectURL(record);
                var now=new Date;
                save_link.download = now.Format("yyyyMMddhhmmss");
                fake_click(save_link);
            }

            function fake_click(obj) {
            var ev = document.createEvent('MouseEvents');
            ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
            obj.dispatchEvent(ev);
            }

每次发送 ,其实是讲音频数据缓存下来,标记下id。下次点击的时候根据id获取缓存的数据,然后叫给audio元素播放:

var msg={};
            //发送音频片段
            var msgId=1;
            function send(){
                if(!recorder){
                    showError("请先录音");
                    return;
                }

               var data=recorder.getBlob();
               if(data.duration==0){
                     showError("请先录音");
                    return;
               }
                msg[msgId]=data;
                recorder.clear();
                console.log(data);
                var dur=data.duration/10;
                 var str="<div class='warper'><div id="+msgId+" class='voiceItem'>"+dur+"s</div></div>"
                $(".messages").append(str);
                msgId++;
            }

            $(document).on("click",".voiceItem",function(){
                var id=$(this)[0].id;
                var data=msg[id];
                playRecord(data.blob);
            })

内部是基于AudioContext实现:兼容性如下,基本上只能在谷歌和火狐浏览器里面玩。很可惜微信和ios目前不支持的。如果电脑没有音频驱动或者没有麦都会报错提示。

有兴趣的朋友可以玩玩。未来移动端支持就更好了。

源码:http://files.cnblogs.com/files/stoneniqiu/Voice.zip

参考博客:

 

自动生成proto Js语句

2017-07-28 20:02 by stoneniqiu, 789 阅读, 0 评论, 收藏编辑

在与后端的WebSocket通信时,前端要带一个proto文件是一个累赘的事情。首先是明显的曝光了协议实体对象,再一个浏览器客户端很容易会缓存该文件,新的协议更新可能导致客户端不能使用,另外在cdn服务器上还需要配置.proto类型客户端才能下载过去。真是遗毒不浅,自己使用的时候会注意这些,但给别人使用的时候就很不乐观了,所以这次全部将proto文件转成JavaScript对象,省去协议文件和加载的步骤。

先看代码:

 function createProto(name) {
        var args = [].slice.call(arguments, 1);
        var obj = new protobuf.Type(name);
        for (var i = 0; i < args.length; i++) {
            var p = args[i];
            var key = i + 1;
            obj.add(new protobuf.Field(p[0], key, p[1] || "string"));
        }
        return obj;
    }

    function createEnum(name,list) {
        var obj = new protobuf.Enum(name);
        for (var i = 0; i < list.length; i++) {
            obj.add(list[i],i);
        }
        return obj;
    }

    function loadProto(callback) {
         if (typeof protobuf == "undefined") return;//说明浏览器加载失败            root = new protobuf.Root().define("IMEntity");
            root.add(createProto("Token", ["UserID"], ["Token"], ["Device"], ["Version", "int32"], ["Appkey"]));
            root.add(createProto("Feedback", ["ResultCode", "int32"], ["ResultData"], ["Seq", "int32"], ["MsgID"]));
            root.add(createEnum("ReceiptType", ["Receive", "Read"]));       //...            util.triggerCallback(callback);
    };

proto 主要有两种类型,Type和Enum。Type对应协议中的message,相当于是类。Enum就是枚举类型

var Root  = protobuf.Root,
    Type  = protobuf.Type,
    Field = protobuf.Field;

var AwesomeMessage = new Type("AwesomeMessage").add(new Field("awesomeField", 1, "string"));

var root = new Root().define("awesomepackage").add(AwesomeMessage);

枚举的创建不要需要Field。只需要add 字段名即可。那么接下来的问题是,手写root.add 也很烦,因为要一个一个对照属性,不断的复制粘贴,很容易出错。所以又做了个自动生成代码的页面:

 <textarea id="content">
        //登陆Token
        message Token{
        string UserID = 1;    //登陆接口返回的IMUserID
        string Token = 2;    //登陆接口返回的Token
        string Device = 3;    //客户端设备号
        int32  Version = 4;    //版本号,发布前与服务端约定值
        string Appkey = 5;    //客户端Appkey
        }

        //收到私信
        message ReceivePrivateMessage{
        string MsgID = 1;        //消息id
        string SenderID = 2;    //发送者id
        string ReceiverID = 3;    //接收者id
        string Content  = 4;    //消息内容。客户端转换成业务相关的实体后,再做后续处理(客户端使用,服务器不做任何处理,原样下发)
        bool Ack = 5;              //是否需要已读回执
        int32 SendDateTime = 6;    //消息发送时间
        int32 ContentType = 7;    //内容类型(客户端使用,服务器不做任何处理,原样下发)
        }
        //回执类型
        enum ReceiptType{
        Receive = 0;                      //已收回执(收到消息后立即发送给服务器的回执)
        Read = 1;                        //已读回执(用户进入消息阅读界面后发送给服务器的回执)
        }
    </textarea>
    <div id="result"></div>
    <script>
        function start() {
            $("#result").html("");
            $("#result").append('root = new protobuf.Root().define("IMProtoEntity")<br>');

            var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)/g,// 过滤注释
          str = $('#content').val(); // 欲处理的文本
            // console.log(str.match(reg));// 打印出:匹配子串
            var news = str.replace(reg, "");
            // console.log(news); // 打印出:原文本
            var reg1 = /[message|enum].*?{/mg;
            var regobj = /{[^}{]*?}/g;//新地址
            var names = news.match(reg1);
            var protos = news.match(regobj);
            // console.log(names, protos);
            var root = {};
            for (var i = 0; i < names.length; i++) {
                var rawname = names[i];
                var rawObj = protos[i];
                //if (~rawname.indexOf("message"))
                if (!rawObj) continue;

                var name = rawname.replace("{", '').replace("message ", '').replace("enum ", '');
                var obj = { name: name };
                if (~rawname.indexOf("enum")) {
                    obj["type"] = "enum";
                }

                rawObj = rawObj.replace("{", '').replace("}", '');
                var protolist = rawObj.split(';');
                //  console.log("protolist", protolist);
                var plist = [];
                for (var j = 0; j < protolist.length; j++) {
                    var p = $.trim(protolist[j]);
                    if (p) {
                        var args = [];
                        var list = p.split(' ');
                        //  console.log("list", list);
                        list.forEach(function (n) {
                            n && args.push(n);
                        }),
                        //  console.log("args", args);
                        plist.push(args);
                    }
                }
                obj.list = plist;
                console.log(obj);
                toProto(obj);
            }

        }

        start();

        function toProto(obj) {
            var root = "root";
            var fun = "createProto";
            var enumfun = "createEnum";

            var str = root + '.add(';
            var args;
            if (!obj.type) {//message
                args = '';
                for (var i = 0; i < obj.list.length; i++) {
                    var item = obj.list[i];

                    //老协议2.0
                    if (item[0] == "required" || item[0] == "optional") {
                        item.shift();
                    }
                    //新协议3.0
                    if (item[0] != "string") {
                        args += '["' + item[1] + '","' + item[0] + '"]';
                    } else {
                        args += '["' + item[1] + '"]';
                    }
                    if (i < obj.list.length - 1) args += ",";
                }
            } else {//enum
                args = '[';
                for (var i = 0; i < obj.list.length; i++) {
                    var item = obj.list[i];
                    args += '"' + item[0] + '"';
                    if (i < obj.list.length - 1) args += ",";
                }
                args += ']';
            }

            var all = str + (obj.type ? enumfun : fun) + '("' + obj.name + '",' + args + '));';
            //  console.log(all);
            $("#result").append(all + "<br>");
        }
    </script>

然后页面上会得到:

红色部分复制到工程里面就可以用了。当然要带上createProto和createEnum两个方法。proto的格式要规范,毕竟start里面是以空格split的。相对于protobuf.load("xx.proto",callback)的方式要好很多。load对位置要求比较死板,一定要在根目录。而且有类型不存在就会报错,终止程序。add方法不存在找不到类型的错误。另外速度也快了很多。

UglifyJS-- 对你的js做了什么

2017-07-27 08:38 by stoneniqiu, 1118 阅读, 4 评论, 收藏编辑

也不是闲着没事去看压缩代码,但今天调试自己代码的时候发现有点意思。因为是自己写的,虽然压缩了,格式化之后还是很好辨认。当然作为min的首要准则不是可读性,而是精简。那么它会尽量的缩短代码,尽量的保持一行,最大化的减少的空白。我们常用的分号都会被替换成了逗号,短句变成了连贯的长句。

1.立即执行函数

我本是第二种写法,uglify给我替换成了第一种(当然更短一点)。其实括号和!号的作用都是将funtion的部分转成一个表达式,而不再是申明。这样就能立即执行,同理~   +都可以做到。

2.变量名替换

这个是自然的,函数名、参数名、变量名都替换成了单个字母。甚至是‘_’

3.函数置顶

function foo (){} 这种形式的代码都会被放到模块的最顶端。当然这是一种规范,后来发现还有另外一个作用就是方便后面的代码合并。 比如 我们这样定义:
var self=this;
function a(){}
self.a=a;
function b(){}
self.b=b;
return self;

会替换成:

function a(){}
function b(){}
var s={}
return  s.a={},s.b={},s

注意到最后的s 不能漏了,return会以最后一个表达式的结果为准。

  function rt(n) {
    return n;
   }
  function xx() {
      return rt(1), rt(2);
  }

执行xx()得到的是2,如果 rt(2)后面还有个不返回值的函数执行,那么xx()会得到undefined。

4.bool值替换

false-->!1  true-->!0

5.if 

if语句是压缩最多的地方。

1) return 前置:
 function load() {
            if (t) {
                x = false;
                log("error");
                return;
            }
            console.log("22")
        }

比如我的原函数大概是这样。压缩后成了这样:

  if (t) return x =!1,void log("error")
return提前了,末尾多了一个void。 这是为什么呢。 没有大括号,if的四段代码变成了一句话。void的在这里的作用是抹掉函数的返回值。因为本来的这个if 是没有返回值的 。如果这个时候log方法带有返回值。那么调用load就会拿到这个返回值。这会产生干扰,违背了原函数的本意。所以用void抹掉了。 
2) 短路
     function foo() {
            if (!x) {
                return;
            }
            console.log("doA");
            console.log("doB");
  }

压缩后:

    function f() {
            x || console.log("doA"), console.log("doB");
      }

这样蛮不错的。同理:

if(x&&y){ doa(); dob(); } doc();   -->    x&&y&&(doa(),dob()),doc()

原本四行变成了一行代码。

3).为了合并一行,这也行:

    console.log("doA");
    console.log("doB");
     if (x>0) {
         console.log("true");
      }

合并成这样:

 if (console.log("doA"), console.log("doB"), x > 0) console.log("true");

平时这么写可能不太友好,重点是在if语句中,最后一句才是判断句。结合之前的return。想必对逗号语句有了深刻的认识。

4)throw也不放过

 if (errMsg) {
       util.triggerCallback(fail, "模型验证错误");
       throw Error(errMsg);
  }

压缩后:

 if (a)  throw x.triggerCallback(o, "模型验证错误"), Error(a)

调换了语句的顺序,把throw看成return 就明白了。

5) if else

这个会替换成三元表达式  a?b:c 。

6.数字处理

整百整千的会处理成科学计数 1000 -->1e3 。

7. while

var offset = 0;
            while (true) {
                if (offset >= bufferLength) {
                    break;
                }
}

会替换成这样:

  for (var n = 0; ; ) {
                if (n >= K)
                    break
 }

确实不错,节省了一行代码。

以上只是独自对比自己的代码发现的一些东西,有的可以在平时的编码中用起来,当然不是追求所有代码都写成一行,这样可读性比较差,另外可能你下次看压缩代码就不那么费劲了。欢迎补充。

protobuf.js的结构和webpack的加载之后的结构很相似。这样的模块化组合是个不错的结构方式。1个是适应了不同的加载方式,2个模块直接很独立。webpack的功能更全一点。但如果自己封装js库这样够用了。而且模块对外统一接口 module.exports。这和node很像。

(function(global, undefined) {
    "use strict";
    (function prelude(modules, cache, entries) {
        function $require(name) {
            var $module = cache[name];
            //没有就去加载
            if (!$module)
                modules[name][0].call($module = cache[name] = { exports: {} }, $require, $module, $module.exports);

            return $module.exports;
        }

        //曝光成全局
        var proto = global.proto = $require(entries[0]);

        // AMD
        if (typeof define === "function" && define.amd) {
            define(["long"], function(Long) {
                if (Long && Long.isLong) {
                    proto.util.Long = Long;
                    proto.configure();
                }
            });
            return proto;
        }

        //CommonJS
        if (typeof module === "object" && module && module.exports)
            module.exports = proto;

    })
    //传参
    ({
        1: [function (require, module, exports) {

            function first() {
                console.log("first");
            }

            module.exports = first;

        }, {}],
        2: [function(require, module, exports) {
            function second() {
                console.log("second");
            }
            module.exports = second;
        }],
        3: [function (require, module, exports) {

            var proto = {};
            proto.first = require(1);
            proto.second = require(2);
            proto.build = "full";

            module.exports = proto;
        }]
      }, {}, [3]);

})(typeof window==="object"&&window||typeof self==="object"&&self||this)

在处理超过16位的整形就得使用Long.js了。 主要是fromString和toString。

  function fromString(str, unsigned, radix) {
        if (str.length === 0)
            throw Error('empty string');
        if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
            return ZERO;
        if (typeof unsigned === 'number') {
            // For goog.math.long compatibility
            radix = unsigned,
            unsigned = false;
        } else {
            unsigned = !!unsigned;
        }
        radix = radix || 10;
        if (radix < 2 || 36 < radix)
            throw RangeError('radix');

        var p;
        if ((p = str.indexOf('-')) > 0)
            throw Error('interior hyphen');
        else if (p === 0) {
            return fromString(str.substring(1), unsigned, radix).neg();
        }

        // Do several (8) digits each time through the loop, so as to
        // minimize the calls to the very expensive emulated div.
        var radixToPower = fromNumber(pow_dbl(radix, 8));

        var result = ZERO;
        for (var i = 0; i < str.length; i += 8) {
            var size = Math.min(8, str.length - i),
                value = parseInt(str.substring(i, i + size), radix);
            if (size < 8) {
                var power = fromNumber(pow_dbl(radix, size));
                result = result.mul(power).add(fromNumber(value));
            } else {
                result = result.mul(radixToPower);
                result = result.add(fromNumber(value));
            }
        }
        result.unsigned = unsigned;
        return result;
    }

fromstring的思路是把字符串8位一个截取。然后转成Long型(高位,地位,符号位) 加起来。最后是一个Long型。 4294967296 是2的32次方。每次操作之前都会有一个基数的操作 mul(radixToPower)或者mul(power)这两者都是保证result的位数是正确的。

比如{low:123} 和{low:1} 相加之前,先要让{low:123}乘以10,得到{low:1230}再与{low:1}进行位操作。因为第一个是高位,不能直接相加。

function fromBits(lowBits, highBits, unsigned) {
        return new Long(lowBits, highBits, unsigned);
    }

fromBits 即转为Long对象。value%4294967296 得到低位。/得到高位。结果通过位移合并起来。mul是bit的乘法,add是bit的加法。 原理是讲一个64位的拆成四段。分别16位。this.low左移16位 就得到 low的32-17位是啥。 然后和addend对象的同位相加

最后的合并是通过|运算。位移之后再还原确实很巧妙。一时看上去都不大理解。

 LongPrototype.add = function add(addend) {
        if (!isLong(addend))
            addend = fromValue(addend);

        // Divide each number into 4 chunks of 16 bits, and then sum the chunks.

        var a48 = this.high >>> 16;
        var a32 = this.high & 0xFFFF;
        var a16 = this.low >>> 16;
        var a00 = this.low & 0xFFFF;

        var b48 = addend.high >>> 16;
        var b32 = addend.high & 0xFFFF;
        var b16 = addend.low >>> 16;
        var b00 = addend.low & 0xFFFF;

        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
        c00 += a00 + b00;
        c16 += c00 >>> 16;
        c00 &= 0xFFFF;
        c16 += a16 + b16;
        c32 += c16 >>> 16;
        c16 &= 0xFFFF;
        c32 += a32 + b32;
        c48 += c32 >>> 16;
        c32 &= 0xFFFF;
        c48 += a48 + b48;
        c48 &= 0xFFFF;
        return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
    };

>>>和>>有什么区别??。

toString

LongPrototype.toString = function toString(radix) {
        radix = radix || 10;
        if (radix < 2 || 36 < radix)
            throw RangeError('radix');
        if (this.isZero())
            return '0';
        if (this.isNegative()) { // Unsigned Longs are never negative
            if (this.eq(MIN_VALUE)) {
                // We need to change the Long value before it can be negated, so we remove
                // the bottom-most digit in this base and then recurse to do the rest.
                var radixLong = fromNumber(radix),
                    div = this.div(radixLong),
                    rem1 = div.mul(radixLong).sub(this);
                return div.toString(radix) + rem1.toInt().toString(radix);
            } else
                return '-' + this.neg().toString(radix);
        }

        // Do several (6) digits each time through the loop, so as to
        // minimize the calls to the very expensive emulated div.
        var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
            rem = this;
        var result = '';
        while (true) {
            var remDiv = rem.div(radixToPower),
                intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
                digits = intval.toString(radix);
            rem = remDiv;
            if (rem.isZero())
                return digits + result;
            else {
                while (digits.length < 6)
                    digits = '0' + digits;
                result = '' + digits + result;
            }
        }
    };

也是sub之后拼出来的。也就是fromstring的反向操作。

【微信开发】-- 发送模板消息

2017-06-28 20:25 by stoneniqiu, 8795 阅读, 7 评论, 收藏编辑

我们需要将一些行为的进展消息推送给用户。除了短信,发送微信模板消息也是不错的选择。模板消息免费、精准到达、而且可以引导用户回到网站上来。但它有两个前提条件。1个是认证的服务号,你才能选择模板。2个是被推送的用户必须关注了你的公众号,而且你也拿到了他的openid。

先在模板库中找到自己的想要的模板,添加到“我的模板”中。

展开详情,我们可以看到示例。接下来用C#代码发送一次:

官方文档的示例中我们可以看到除了推送人的openid,还可以设置每个字段的颜色及跳转地址。先可以定义以个TempModel对象:

  public class TemplateModel
    {
        public string touser { get; set; }
        public string template_id { get; set; }
        public string url { get; set; }

        public string topcolor { get; set; }

        public TemplateData data { get; set; } 

        public TemplateModel(string hello,string state,string reason,string last)
        {
            data=new TemplateData()
            {
                first = new TempItem(hello),
                keyword1 = new TempItem(state),
                keyword2 = new TempItem(reason),
                remark = new TempItem(last)
            };

        }
    }

    public class TemplateData
    {
        public TempItem first { get; set; }
        public TempItem keyword1 { get; set; }
        public TempItem keyword2 { get; set; }
        public TempItem remark { get; set; }
    }
    public class TempItem
    {
        public TempItem(string v,string c = "#173177")
        {
            value = v;
            color = c;
        }
        public string value { get; set; }
        public string color { get; set; }
    }

还有一个返回结果对象:

 public class OpenApiResult
    {
        public int error_code { get; set; }
        public string error_msg { get; set; }

        public string msg_id { get; set; }
    }

然后定义一个发送方法:

using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend;

public OpenApiResult SendTemplateMessage(string token,TemplateModel model)
        {
            var url = string.Format("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={0}", token);
            try
            {
                var res = SendHelp.Send<OpenApiResult>(token, url, model);
                return res;
            }
            catch (Exception e)
            {

                return new OpenApiResult(){error_code = -1,error_msg = e.Message};
            }

        } 

SendHelp是基于Senparac.Weixin 最后就可以调用了:

        public ActionResult SendMessage()
        {
            var token = getToken();
            var toUserId = "oBSBmwQjqwjfzQlKsFNjxFLSiIQM";
            var data = new TemplateModel("你好,stoneniqiu","审核通过","资料完整","祝你生活幸福!");
            data.touser = toUserId;
            data.template_id = "gXmkeL7Kc-KUy2EQzKqjPKY-kzFyatTztiYFKCaUPO4";
            data.url = "http://www.xxx.com/xx/xx";
            data.topcolor = "#FF0000";
            var res=wxDeviceService.SendTemplateMessage(token, data);
            return View(res);
        }

token即通过AppID和APPSECRET获取。发送之后,手机上马上收到消息。这里的url就是下图详情的跳转地址。 只能是注册域名下面的地址,不能跳到别的域名去。

但如果你只是拿到了用户的openid,但该用户没有关注公众号,发送时会抛出下面的错误:

其他信息: 微信Post请求发生错误!错误代码:43004,说明:require subscribe hint: [Q2OfvA0092ge21]

相关部分代码:http://files.cnblogs.com/files/stoneniqiu/wx-template.zip

官方文档:https://mp.weixin.qq.com/advanced/tmplmsg?action=faq&token=1798469214&lang=zh_CN

全部错误类型:http://www.szdyhd.com/news/view/webdesign/2016/0614/519.html

几个月前因为一个事情被diao了。起因是临近上线的时候项目后端统一了消息协议(.proto),然后要我前端也支持。我研究了一天,没走通,要么依赖项太多,要么一直报错,而且需要使用的对象兼容性有问题。当时心里有些急,也有几份抵触这种方案,于是在会上说出了我的想法:能不能友好的发发json,兼容性好也不需要什么第三方解析。结果自然是被否决了,理由是大厂出品的,怎么可能不能用呢,用屁股想想就知道?你为啥遇到问题就想着退缩呢。我无语凝噎。重要是给我强调了能编程与会编程是不一样的

开完会情绪有点低落,回到座位上打开github 继续找方案,不一会儿居然找到了!啪啪打脸的感觉好酸爽。然后开始思索...

是否抵触

一件事带有抵触情绪之后,那基本是做不好的。在找方案的时候,眼中只看到“NO”,而自动忽略了“YES”。解决方法在你眼前你可能都看不到。就像一个人不想做事就找借口,这些借口在他自己看来都是很合理的。要带有这种情绪,那不如先别开始,要么自我消化,要么拿更好的方案去说服别人。

只要有一种解释是对自己有利的,我们便不想去推敲和反驳,再漏洞百出的事情看上去也不无可能,而且只要一种解释是可能的,我们就认定是一定的,强大的情绪大脑会阻止理性大脑往深入的想。而对于自己不利的解释,我们或忽略,或者异常仔细的去推敲,抓住一个漏洞则相信自己推翻了所有的解释。----《暗时间》

所以一件事情在自己着手做的时候,先整理好类似的情绪。很多时候摆在我们面前的方案很多,如果是你熟悉的领域当然好选择,利弊清晰,能快速判断。但不熟悉的时候,就要注意到每个方案所适用的环境和条件,要归类和总结,自己所遇到的问题本身已经提供了一些前提条件。用这些条件先过滤到一部分。而不是瞎猫抓死耗子一样一个个去碰。

另外一点,我们在尝试新的事物的时候,总是会遇到各种困难,不同的人在不同的碰壁次数之后会退出。用程序员的话来说,我们都在for循环,区别在于你是什么情况下break;的。有的人退出阈值高,这是能坚持的一类人,有的人退出阈值低。过早退出的原因在于对未来的不确定性,对投资时间最终无法收到回报的恐惧。其实在坚持坚持,再想一想,答案马上就出来了。

不要等待

编程工作中我们经常遇到需求不完整,接口文档没有,接口返回值错,误诸如此类 别人的因素,然后导致项目进度缓慢或者停滞。其实我想说,编程简直太轻松愉快了,因为还有人给你提供需求,提供设计稿,你实现就行了。实际生活中很多事情根本没有人会告诉你需求,比如自己装修,你需要定风格,然后选材料,按照流程喊施工队伍,考虑风格和成本,考虑材料合不合适,喜不喜欢,甲醛高不高等等。建材市场鱼龙混杂,装修师傅可能阳奉阴违。即使你监工他都敢用料不够,总有一款让你交点学费。 可能很忙的时候,父母又生病了..... 生活的事情更考验的人的协作和沟通能力、承压能力。没有谁告诉你截止日期和责任,但是你没做好,就是没有做好,不要说什么装修的人坑了你。工作的事情责任范围清晰,不属于你的锅你都可以甩的远远的。但如果团队内部都是这样的氛围,那又能成什么事。雪崩的时候,没有一片雪花觉得自己有责任,应该积极主动起来,因为等待耗费的也是自己的时间。

做完了还是做好了

按照需求和效果图,你可能很快就实现了所有功能和效果。但是自己测试了吗?可用性高吗?稳定吗?兼容性如何?功能做完往往只到了一个阶段,后面还需要自己做检验性工作。确保交出去的东西是ok的,达到预期的。有信心对别人说我做好了,让别人用的放心。而不是说一句我做完了,暗藏的坑让别人先踩。有时间还应该思考一下,代码是否可以更简洁?接口设计是否可以更简明?多个类似方案是否可以统一成产品?思考的价值总会为你省下未来的时间,特别是重复劳动的时间、遇到问题沟通的时间。

知识体系完善

你选择的方案,都是你所知道的方案。也就是说,你的知识范围,决定了你的处理能力范围。甚至于,有的知识你会,但关键的时候你未必想的出来。这个过程就像解题,自己苦思冥想不得,看到答案恍然大悟。为什么呢?你的大脑就像一个图书馆,知识的碎片就如书架上的书,你想用的时候,发现找不到地址了。

知识分两种,一种是我们通常所谓的知识,即领域知识。二是关于我们大脑吸收知识的机制的知识,后者不妨成为元知识。虽说这也是领域知识,但跟其他的领域知识不同的是,它指导我们学习其它所有的领域知识。

除了完善我们的领域知识,也许需要补充一下其他领域的,譬如心理学和思维方面的,当然最好是到生活中去解决问题。

小结:会编程是基于现有经验办事的能力,而能编程是对整个事情的解决能力。在学习一门技术的成本差不多的情况下,差别就来自于编程之外的能力。

学习了Vue全家桶和一些UI基本够用了,但是用元素的方式使用组件还是不够灵活,比如我们需要通过js代码直接调用组件,而不是每次在页面上通过属性去控制组件的表现。下面讲一下如何定义动态组件。

Vue.extend

思路就是拿到组件的构造函数,这样我们就可以new了。而Vue.extend可以做到:https://cn.vuejs.org/v2/api/#Vue-extend

// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')

官方提供了这个示例,我们进行一下改造,做一个简单的消息提示框。

动态组件实现

创建一个vue文件。widgets/alert/src/main.vue

<template>
 <transition name="el-message-fade">
<div  v-show="visible" class="my-msg">{{message}}</div>
  </transition>
</template>

<script  >
    export default{
        data(){
           return{
               message:'',
               visible:true
           }
        },
        methods:{
            close(){
                setTimeout(()=>{
                     this.visible = false;
                },2000)
            },
        },
        mounted() {
        this.close();
      }
    }
</script>

这是我们组件的构成。如果是第一节中,我们可以把他放到components对象中就可以用了,但是这儿我们要通过构造函数去创建它。再创建一个widgets/alert/src/main.js

import Vue from 'vue';
let MyMsgConstructor = Vue.extend(require('./main.vue'));

let instance;

var MyMsg=function(msg){
 instance= new MyMsgConstructor({
     data:{
         message:msg
}})

//如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。
instance.$mount();

 document.body.appendChild(instance.$el)
 return instance;
}

export default MyMsg;

require('./main.vue')返回的是一个组件初始对象,对应Vue.extend( options )中的options,这个地方等价于下面的代码:

import alert from './main.vue'
let MyMsgConstructor = Vue.extend(alert);

而MyMsgConstructor如下。

参考源码中的this._init,会对参数进行合并,再按照生命周期运行:

  Vue.prototype._init = function (options) {
    ...// merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
// expose real self
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');

    ...

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };

而调用$mount()是为了获得一个挂载实例。这个示例就是instance.$el。

可以在构造方法中传入el对象(注意上面源码中的mark部分,也是进行了挂载vm.$mount(vm.$options.el),但是如果你没有传入el,new之后不会有$el对象的,就需要手动调用$mount()。这个方法可以直接传入元素id。

 instance= new MessageConstructor({
     el:".leftlist",
     data:{
         message:msg
}})

这个el不能直接写在vue文件中,会报错。接下来我们可以简单粗暴的将其设置为Vue对象。

调用

在main.js引入我们的组件:

//..
import VueResource from 'vue-resource'
import MyMsg from './widgets/alert/src/main.js';
//..
//Vue.component("MyMsg", MyMsg);
Vue.prototype.$mymsg = MyMsg;

然后在页面上测试一下:

<el-button type="primary" @click='test'>主要按钮</el-button>
//..

 methods:{
  test(){
  this.$mymsg("hello vue");
  }
 }

这样就实现了基本的传参。最好是在close方法中移除元素:

 close(){
    setTimeout(()=>{
       this.visible = false;
       this.$el.parentNode.removeChild(this.$el);
      },2000)
   },

回调处理

回调和传参大同小异,可以直接在构造函数中传入。先修改下main.vue中的close方法:

export default{
        data(){
           return{
               message:'',
               visible:true
           }
        },
        methods:{
            close(){
                setTimeout(()=>{
                     this.visible = false;
                      this.$el.parentNode.removeChild(this.$el);

                if (typeof this.onClose === 'function') {
                 this.onClose(this);
                }
                },2000)
            },
        },
        mounted() {
        this.close();
      }
    }

如果存在onClose方法就执行这个回调。而在初始状态并没有这个方法。然后在main.js中可以传入

var MyMsg=function(msg,callback){

 instance= new MyMsgConstructor({
     data:{
         message:msg
    },
    methods:{
        onClose:callback
    }
})

这里的参数和原始参数是合并的关系,而不是覆盖。这个时候再调用的地方修改下,就可以执行回调了。

   test(){
      this.$mymsg("hello vue",()=>{
        console.log("closed..")
      });
    },

你可以直接重写close方法,但这样不推荐,因为可能搞乱之前的逻辑且可能存在重复的编码。现在就灵活多了。

统一管理

如果随着自定义动态组件的增加,在main.js中逐个添加就显得很繁琐。所以这里我们可以让widgets提供一个统一的出口,日后也方便复用。在widgets下新建一个index.js

import MyMsg from './alert/src/main.js';

const components = [MyMsg];

let install =function(Vue){
  components.map(component => {
    Vue.component(component.name, component);
  });

 Vue.prototype.$mymsg = MyMsg;

}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
};

export default {
    install
}

在这里将所有自定义的组件通过Vue.component注册。最后export一个install方法就可以了。因为接下来要使用Vue.use

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法将被作为 Vue 的参数调用。

也就是把所有的组件当插件提供:在main.js中加入下面的代码即可。

...
import VueResource from 'vue-resource'
import Widgets from './Widgets/index.js'

...
Vue.use(Widgets)

这样就很简洁了。

小结: 通过Vue.extend和Vue.use的使用,我们自定义的组件更具有灵活性,而且结构很简明,基于此我们可以构建自己的UI库。以上来自于对Element源码的学习。

widgets部分源码:http://files.cnblogs.com/files/stoneniqiu/widgets.zip

vue2入坑随记(一)-- 初始全家桶

低版本的安卓上传图片是个问题,能出现选择图片,但点击图片后没有反应,转成base64也无解。于是改为用微信的接口上传。和之前的微信分享功能都是基于微信的jssdk。

步骤比我们平时上传到服务器多一步,他是先调用chooseeImage方法获得用户要上传的图片id。然后上传到微信的服务器,微信的服务器默认只保存三天,所以还要让后台下载到自己的服务器上,然后返回地址,前端显示,这样才算完成。

     var time = '@ViewBag.Share.timestamp';
      wx.config({
        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '@ViewBag.Share.appId', // 必填,公众号的唯一标识
        timestamp: parseInt(time), // 必填,生成签名的时间戳
        nonceStr: '@ViewBag.Share.nonceStr', // 必填,生成签名的随机串
        signature: '@ViewBag.Share.signature',// 必填,签名,见附录1
          jsApiList: ["chooseImage", "previewImage", "uploadImage", "downloadImage"] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
      });
        wx.ready(function() {
            $(document).on("click", ".ctcon", function() {
                wx.chooseImage({
                    count: 1, // 默认9
                    sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
                    sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
                    success: function (res) {
                        var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
                        uploadimg(localIds[0].toString());

                    }
                });
            });

                
            function uploadimg(lid) {
                wx.uploadImage({
                    localId: lid, // 需要上传的图片的本地ID,由chooseImage接口获得
                    isShowProgressTips: 1, // 默认为1,显示进度提示
                    success: function (res) {
                        var serverId = res.serverId; // 返回图片的服务器端ID
                        //alert(serverId);
                        $.post("/Question/DownWxImage", { serverId: serverId }, function(res) {
                            //alert(res.SaveName);
                            if (res.Success === true) {
                                 // 显示图片
                            }

                        });
                });
            }

        });

count表示让用户选择图片的张数,然后这里的localIds要tostring。不然上传不了。uploadImage执行完了就可以通知让后台上传:

  public ActionResult DownWxImage(string serverId)
        {
            var token = getToken();
             var url = string.Format("http://file.api.weixin.qq.com/cgi-bin/media/get?access_token={0}&media_id={1}",
                 token, serverId);//图片下载地址

             HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url);

             req.Method = "GET";
             using (WebResponse wr = req.GetResponse())
             {
                 HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();

                var strpath = myResponse.ResponseUri.ToString();
                 WebClient mywebclient = new WebClient();

                 var path = "/Content/UploadFiles/mobile/";
                 var uploadpath = Server.MapPath(path);
                 if (!Directory.Exists(uploadpath))
                 {
                     Directory.CreateDirectory(uploadpath);
                 }
                 string saveName = Encrypt.GenerateOrderNumber() + ".jpg";
                 var savePath = uploadpath + saveName;

                 try
                 {
                     mywebclient.DownloadFile(strpath, savePath);
                     return Json(new { Success = true, SaveName = path + saveName });
                 }
                 catch (Exception ex)
                 {
                     savePath = ex.ToString();
                 }

             }
             return Json(new {Success = false, Message = "上传失败!"});
        }

这样安卓是能上传了,但是也没有了进度条。

h5 录音 自动生成proto Js语句 UglifyJS-- 对你的js做了什么 【原码笔记】-- protobuf.js 与 Long.js 【微信开发】-- 发送模板消息 能编程与会编程 vue2入坑随记(二) -- 自定义动态组件 微信上传图片的更多相关文章

  1. vue2入坑随记(二) -- 自定义动态组件

    学习了Vue全家桶和一些UI基本够用了,但是用元素的方式使用组件还是不够灵活,比如我们需要通过js代码直接调用组件,而不是每次在页面上通过属性去控制组件的表现.下面讲一下如何定义动态组件. Vue.e ...

  2. vue2入坑随记(一)

    都说Vue2简单,上手容易,但小马过河,自己试了才晓得,除了ES6语法和webpack的配置让你感到陌生,重要的是思路的变换,以前随便拿全局变量和修改dom的锤子不能用了,变换到关注数据本身.vue的 ...

  3. vue2入坑随记(一)-- 初始全家桶

    都说Vue2简单,上手容易,但小马过河,自己试了才晓得,除了ES6语法和webpack的配置让你感到陌生,重要的是思路的变换,以前随便拿全局变量和修改dom的锤子不能用了,变换到关注数据本身.vue的 ...

  4. Entity Framewrok 7beta7中不同版本sql server自动生成分页sql语句的问题

    在EF中,使用linq进行分页是很方便的,假如我们有一个EMP表,结构如下: public class Emp { [Key] public Guid No { get; set; } public ...

  5. Docker入坑系列(二)

    Docker入坑系列(二) 上一篇我们为Docker创造了一个良好的生活环境,这一篇我们就开始让Docker活起来. 安装Docker ok,原文地址在这里. 当然,我只是自己翻译了一下而已- -跟着 ...

  6. 微信公众平台开发,模板消息,网页授权,微信JS-SDK,二维码生成(4)

    微信公众平台开发,模板消息,什么是模板消息,模板消息接口指的是向用户发送重要的服务通知,只能用于符合场景的要求中去,如信用卡刷卡通知,购物成功通知等等.不支持广告营销,打扰用户的消息,模板消息类有固定 ...

  7. 自动生成proto Js语句

    在与后端的WebSocket通信时,前端要带一个proto文件是一个累赘的事情.首先是明显的曝光了协议实体对象,再一个浏览器客户端很容易会缓存该文件,新的协议更新可能导致客户端不能使用,另外在cdn服 ...

  8. Vue入坑教程(二)——项目结构详情介绍

    之前已经介绍了关于Vue的脚手架vue-cli的安装,以及一些文件目录介绍.具体可以查看<vue 入坑教程(一)--搭建vue-cli脚手架> 下面简单说一下具体的文件介绍 (一) pac ...

  9. sqlite3 根据实体自动生成建表语句

      public class BuildSqlTool { public static string GetCreateTableSql(object t) { //CREATE TABLE &quo ...

随机推荐

  1. BFS:UVa220 ACM/ICPC 1992-Othello(黑白棋)

    Othello Othello is a game played by two people on an 8 x 8 board, using disks that are white on one ...

  2. ACM-ICPC 2018 南京赛区网络预赛 L. Magical Girl Haze

    262144K   There are NN cities in the country, and MM directional roads from uu to v(1\le u, v\le n)v ...

  3. PAT Basic 1023

    1023 组个最小数 给定数字0-9各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意0不能做首位).例如:给定两个0,两个1,三个5,一个8,我们得到的最小 ...

  4. redis 内存管理与数据淘汰机制(转载)

    原文地址:http://www.jianshu.com/p/2f14bc570563?from=jiantop.com 最大内存设置 默认情况下,在32位OS中,Redis最大使用3GB的内存,在64 ...

  5. 模板<最小生成树>

    转载 最小生成树浅谈 这里介绍最小生成树的两种方法:Prim和Kruskal. 两者区别:Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣.Prim是以更新过的节点的连边找最小值,K ...

  6. Linux下安装Oracle客户端

    1.创建用户名和组名 /usr/sbin/groupadd oinstall /usr/sbin/groupadd dba /usr/sbin/useradd -m -g oinstall -G db ...

  7. Oracle排错总结

    一.Oracle常规恢复之不安全恢复 http://www.cnblogs.com/jyzhao/p/4723994.html#2.11

  8. 【javascript面试题】5个经典的面试题

    问题1: 作用域 看一下下面的代码: (function(){ var a = b =5; })(); console.log(b); 结果会输出什么? 答案: 5 这个问题考查的要点是两个不同的作用 ...

  9. 信息安全试验-DES加密!

    信息安全试验二--DES加密算法 本渣表示没有理解原理,照着书上敲了一发,运行无误! 吐槽:手动S盒简直丧心病狂,扩展置换表全是手动输入,加密原理还是很好理解,两次异或,先混淆. 此代码数据由老师给出 ...

  10. POJ——3159Candies(差分约束SPFA+前向星+各种优化)

    Candies Time Limit: 1500MS   Memory Limit: 131072K Total Submissions: 28071   Accepted: 7751 Descrip ...