小麦

小麦 查看完整档案

广州编辑广州广播电视大学  |  计算机应用 编辑Makshi  |  前端开发 编辑 makshi.gitee.io/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

小麦 发布了文章 · 5月14日

⾼级前端开发⼯程师 笔试题(请在 45 分钟内闭卷完成)

某公司面试题,自己思考再看答案

⼀、HTML 与 CSS。

  1. 实现两列布局:左列宽度为 200px,右列占据剩余宽度。
  2. 把尺⼨未知的元素置于浏览器可⽤区域的正中央,且位置不随⻚⾯滚动⽽变化(⽆须兼容旧浏览器)。

⼆、JavaScript。

  1. 使⽤正则表达式把字符串 str 中的占位符替换成 obj 的对应属性值,要求只能调⽤⼀次 replace ⽅法。
    var str = 'My name is ${name}. I like ${hobby}.';
    var obj = { name: 'Tom', hobby: 'coding' };
    // 结果应为 'My name is Tom. I like coding.'
  1. 从数组中随机选出⼀个元素,要求:priority 越⼤的元素,被选中的概率越⼤。
const arr = [
    { id: 1, priority: 16 }, { id: 2, priority: 20},
    { id: 3, priority: 3 }, { id: 4, priority: 7 }
];

3.a、b、c 都是执⾏异步操作的函数,请通过 Promise、async、await 实现:a、b 并⾏执⾏完毕后再执⾏ c。

    function a(callback) { setTimeout(callback, 10); }
    function b(callback) { setTimeout(callback, 20); }
    function c(callback) { setTimeout(callback, 30); }
    

答案如下

  1. 实现两列布局:左列宽度为 200px,右列占据剩余宽度。
    <style>
    .box{
        width:100vw;
        height:100vh;
        display: flex;
    }
    .left{
        width: 200px;
    }
    .right{
        flex: 1;
    }
    </style>
    <div class="box">
        <div class="left"></div>
        <div class="right"></div>
    </div>
  1. 把尺⼨未知的元素置于浏览器可⽤区域的正中央,且位置不随⻚⾯滚动⽽变化(⽆须兼容旧浏览器)。
    <style>
        .box{
            width: 200px;
            height: 200px;
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate3d(-50%,-50%,0);
            z-index: 99;
        }
    </style>
    
    <div class="box"></div>

⼆、JavaScript。

使⽤正则表达式把字符串 str 中的占位符替换成 obj 的对应属性值,要求只能调⽤⼀次 replace ⽅法。


    var str = 'My name is ${name}. I like ${hobby}.';
    var obj = { name: 'Tom', hobby: 'coding' };
    // 结果应为 'My name is Tom. I like coding.'
    
    var newStr = str.replace(/\$\{(.+?)\}/g,(str, k) => {
        return obj[k]
    })
    console.log('newStr = ',newStr)

从数组中随机选出⼀个元素,要求:priority 越⼤的元素,被选中的概率越⼤。


const arr = [
    { id: 1, priority: 16 }, { id: 2, priority: 20},
    { id: 3, priority: 3 }, { id: 4, priority: 7 }
];

function randomNum(Min, Max) {
  var Range = Max - Min
  var Rand = Math.random()
  var num = Min + Math.round(Rand * Range)
  return num
}
const countVal = arr.reduce((count, item) => count += item.priority, 0)
arr.map(item => {
    item.probability = parseInt(item.priority / countVal * 100)
    item.text = `当前item中奖的概率为 ${item.probability} %`
    for (let i = 0; i <= 100; i++) {
        if (item.probability >= i) {
            const num = randomNum(0, 100)
            if (num === 100) {
                item.checked = true
                return
            }
        }
    }

})

console.log(arr)

或者
var arr=[
    {id:1,p:16},
    {id:2,p:20},
    {id:3,p:3},
    {id:4,p:7}
];
var sum =arr.reduce(function(t,v){
    return v.m=t+=v.p;
},0);
var rd = randomNum(0,sum);
var item=arr.find(function(v){
    return rd<v.m;
});
console.info('随机数:'+rd);
console.info('随机元素:'+JSON.stringify(item));
console.info('当前元素概率为:'+(item.p/sum));

a、b、c 都是执⾏异步操作的函数,请通过 Promise、async、await 实现:a、b 并⾏执⾏完毕后再执⾏ c。


    function a(callback) { setTimeout(callback, 10); }
    function b(callback) { setTimeout(callback, 20); }
    function c(callback) { setTimeout(callback, 30); }
    
promiseHandler()
async function promiseHandler() {

    function a(callback) { setTimeout(callback, 3000); }
    function b(callback) { setTimeout(callback, 20); }
    function c(callback) { setTimeout(callback, 100); }

    const p1 = function() {
        return new Promise(resolve => {
            a(() => resolve('a') )
        })
    }
    const p2 = function() {
        return new Promise(resolve => {
            b(() => resolve('b') )
        })
    }
    const p3 = function() {
        return new Promise(resolve => {
            c(() => resolve('c') )
        })
    }

    const arr = await Promise.all([p1(), p2()])
    console.log(arr[0])
    console.log(arr[1])
    console.log(await p3())
   
}
查看原文

赞 2 收藏 1 评论 0

小麦 发布了文章 · 5月9日

遮罩层镂空效果的多种实现方法

先看看效果

微信图片_20200509094005.jpg

【 方法一:截图模拟实现 】

原理:先截一张相同位置的图片,创建一个遮罩层,然后把图片定位在相应的位置上。

优点:原理简单;兼容性好,可以兼容到IE6、IE7;可以同时实现镂空多个。

缺点:此方法只适合静止页面,不适合可以滚动的页面。也不适合页面内容会发生变换的页面。

代码如下:
<div class="class1">
    <img data-original="images/000.jpg" alt=""/>
</div>

.class1{
    position: absolute;
    width:100%;
    height:100%;
    top: 0;
    left: 0;
    background-color: #000;
    opacity: 0.6;
    filter:alpha(opacity=60);
}
.class1 img{
    position: absolute;
    top:260px;
    left: 208px;
}

【 方法二:CSS3阴影属性实现 】

原理:利用CSS3的阴影属性。

优点:实现方便;适合任何页面,不会受页面的限制。

缺点:兼容不太好,只能兼容到IE9。

代码如下:
<div class="class2"></div>

.class2{
    position: absolute;
    width:170px;
    height:190px;
    top: 260px;
    left: 208px;
    box-shadow: rgba(0,0,0,.6) 0  0  0  100vh;
}

【 方法三:CSS边框属性实现 】

原理:利用边框属性。先将一个空盒子定位在目标区域,然后在其四周用边框填充。

优点:实现方便,兼容性好,可以兼容到IE6、IE7;适合任何页面,不会受页面的限制。

缺点:要做兼容实现过程则相对复杂。

     代码如下:

<div class="class3"></div>
.class3{
      position: absolute;
      width:170px;
      height:190px;
      top: 0;
      left: 0;
      border-left-width:208px;
      border-left-style: solid;
      border-left-color:rgba(0,0,0,.6);
      border-right-width:970px;
      border-right-style: solid;
      border-right-color:rgba(0,0,0,.6);
      border-top-width:260px;
      border-top-style: solid;
      border-top-color:rgba(0,0,0,.6);
      border-bottom-width:253px;
      border-bottom-style: solid;
      border-bottom-color:rgba(0,0,0,.6);
}

【 方法四:SVG或者canvas 】

原理:利用SVG或者canvas的绘图功能。

优点:可以同时镂空多个。

缺点:兼容性不好,实现过程相对复杂。

我以SVG为例,代码如下:
<svg style="position: absolute;" width="1366" height="700">
    <defs>
        <mask id="myMask">
            <rect x="0" y="0" width="100%" height="100%" style="stroke:none; fill: #ccc"></rect>
            <rect id="circle1" width="170" height="190" x='208' y="260" style="fill: #000" />
        </mask>
    </defs>
    <rect x="0" y="0" width="100%" height="100%" style="stroke: none; fill: rgba(0, 0, 0, 0.6); mask: url(#myMask)"></rect>
</svg>
查看原文

赞 4 收藏 3 评论 0

小麦 回答了问题 · 4月24日

解决html2canvas 生成图片报错

此问题已经解决,是插件版本号问题

关注 2 回答 1

小麦 提出了问题 · 2月11日

解决html2canvas 生成图片报错

在vue中使用html2canvas 生成图片报typeError: Cannot assign to read only property 'className' of object '#<SVGSVGElement>'
image.png
image.png

在一个全新的项目中下载html2canvas没有问题
在我的项目中,最近就生成时报错
你们有没有遇到过???

关注 2 回答 1

小麦 回答了问题 · 2019-10-16

window. Weixinjsbridge already exists

以前导致这个问题是因为我整个vue只调一次jsdk,现在每个页面都调一次就解决了
clipboard.png

关注 1 回答 1

小麦 赞了文章 · 2019-10-11

民工哥的十年故事:杭漂十年,今撤霸都!

clipboard.png

之前发过一篇文章 民工哥的这十年,有伙伴说是鸡汤文,没有故事。还真不是鸡汤文,其一,民工哥不是厨师真的不擅长生产鸡汤,其二,我也没有必要写这些鸡汤文。说真的,也就是回首十年来自我的一点点对生活、对人生的感悟,仅此而已。

文章发出去后,有好多小伙伴后台找我,说写写一些人生经历吧,对自己也算是个回顾,对他人可能也有一些可以借鉴的经验。思考很久,迟迟无法下笔,本身民工哥的文笔太烂,实在写不出来什么好文,以至于怕到时候文不成文,段不成段,句不成句,变成了一篇的通篇流水账,这可就不好了。慢慢的,自己静下心来想想过去的十年,还是有点故事情节的(哈哈),这不,终将提笔,写下此文,或许是自己回首过去,或许是展望未来,然而只想用这段文字来纪念下曾经苦逼的那段岁月。

那一天

我不得已上路

为不安的心

为自尊的生存

为自我的证明

-----致一直奔跑在技术路上的伙伴们

clipboard.png

十年故事时间轴

2017年4月15号,早上8:00,电话铃声响起,打破了原本的沉静,我接起电话,

电话那头:喂,师傅,我到你们小区门口了,马上进来。
我:好的,你进来,左拐再进来,我们在XX栋。
电话那头:好的,好的。

就这样挂断了电话,真的要走了,这次是真的离开了,下次再来,那就是以游客的身份来了(杭漂这十年,其实已把自己当成“假杭州人”了),我在心里对自己这样说。电话那头是我从X8平台上找的拉货师傅,从杭州搬家到合肥。由于有了孩子,家里大部分的东西都是孩子的玩具,丢了也是可惜。索性,叫了辆货车全部拉走。

我放下电话说:妈,车子来了,我们把东西搬下去吧,等会装车就直接走了。杭州离合肥应该有400+公里,需要6-8小时才能到。这次搬家,没有叫任何朋友或同事。十年了,在这里留下的是十年的青春岁月,有欢笑、有泪水、有…..,十年挥手一瞬间!

时间回到2007年3月8日,这一天我踏上杭城这片土地,开始了十年的杭漂生活。从大山里走出来的我,来到这样繁华的都市中,没有亲人,没有朋友。到现在我也没有明白,当初,是何原因选择去了杭州,也许是冥冥之中注定要与这座城结缘,在杭城的十年,亲眼见证了杭州城的变迁、发展与繁荣。我翻了翻之前的老照片,好不容易找出来几张,记得当时使用的NOKIA 3230的手机(那时日子苦啊)。

clipboard.png

clipboard.png

来杭城之后就是租房子,记得当时房租在180-220块钱一间,有独立的小厨房、卫生间、加上房间约15、16平方左右。离开之时,已涨到了1500左右了吧,十年,翻了七倍(当时的杭州的最低工资水平720块一个月,目前应该是从2017年12月1日起,将全省最低月工资标准调整为2010元、1800元、1660元、1500元四档),长期租房的伙伴们应该都了解其中的痛苦所在(几乎每年的三分之一的收入全部贡献给了“本地土豪”的房东)。

房子解决后,那么就是找工作,这是头等大事,很快入职了一家企业,生产型企业,网络工程师的职位,其实就是企业的网管,什么都有涉及,当时有企业邮箱、ERP软件、内部AD服务器、一些简单的网络设备等维护工作。

clipboard.png

由于刚刚接触实际环境,也发生过至今仍然记忆犹新的一件事。当时有一个同事电脑用的比较久(那时还是win XP系统),感觉不太好用,说让我给重装下系统。尼码当时,二话不讲,上手就是重装(第一次实操),三下五除二搞定了,归还给同事。可是不久后,同事拿着电脑跑过来讲:“我电脑桌面的这个位置(用手给我指了下)少了一个图标”。说实话,当时我就懵逼了,吓的我..........。后来才知道,由于每个系统不一样,装完系统后默认的桌面图标也是不相同的,为了这事,这个“好同事”纠结我好久好久。也是因为如些,吃一见长一智,到现在还我保留着那个因“祸”得来的好习惯,无论什么情况下,备份第一,备份第一,备份第一。现在将这个踩坑得来的经验无偿传授给大家。

很快我也适应了这样的工作环境,上手也还算比较快,也是由于第一份工作,毕竟课堂上的知识与现实环境还是有所差别。因此,也算是在不断的折腾中,慢慢的一点点的进步,民工哥呢,本人比较笨,所以对于有些事喜欢记笔记,所以在工作,我会每天下班后回家,将当天工作遇到的问题及怎么解决的,用笔记下来(当年记的小本本一直我都有留着,由于搬家的次数太多太多,后面不知道丢到哪了,有点可惜了)。

时间转眼也就走到了2008年,这一年,国家举办了北京奥运会,同时也发生了“汶川地震”这么大的灾难,记得当时我捐了50块钱(那时穷小子一个,如今还是这么穷,哎….)。其实也是应了那句话,在灾难面前显现了“众志成城”。

其实对于漂一族来说,最大的问题的就是解决房子,2008年杭州的房价在我印象当中,主城区市中心应该在15000左右吧,市郊(像现如今的城东新城)应该在7000-8000左右。

clipboard.png

可能对于现在来说是不高,可是对于当时的经济形式,也是不低的,对于杭漂一族来说无疑也是一座难于逾越的大山。

这里穿插个小事情,我在杭城一同事,记得应该是08年底的时候,他向朋友借款30+W在杭州城东买了套90+平方的房子(当时房价9000+)。时过一年多点,他已近20000一平的价格出手,一年多点的时间增值一倍多。这能说明什么,你的圈子,你的资源,你的思维方式,这些才是决定你财富增值指数的涨与跌。

这么简单的生活、工作,时间它是真的很快,斗转星移,岁月如梭。来杭城工作三年多了,好像除了上班、下班,好像也没有发生过什么有趣的事,说白了,简单而又枯燥的生活了三年之久。

大山农村出来的孩子,的确,有些方面是受限的,比如你的信息来源的面、你的交际圈子,这些无疑也是阻碍自己发展的一部分因素(绝对不是找理由与借口,相信有同感的小伙伴)。

为什么这说呢,是因为民工哥自己发现,自己的一些消息来源、自己对一些事物的判断,对自己未来的规划等等。你永远是要比有些同龄人要慢一拍(也许是民工哥自己笨的原故吧)。也是因为如此,浑浑噩噩的渡过了5年的时光,这5年对于我来说,不知道是怎么过的,好像一点收获没有。不是有那么一句话么“限制你发展的不是你出生的贫穷,而是你那从未改变过的贫穷的思维”。

2012年3月我与我现在的爱人相识,其实是通过朋友介绍的(我是她同学的客户)。在这之前,家里人一直担心我找不到女朋友,要打光棍(哈哈哈)。或许,也会有好多小伙伴,无论男女,目前可能也是所谓的“单身dog”,请不要着急,不要害怕,像民工哥这样的都娶到老婆,你们这第优秀还有理由害怕吗?其实,真的就是一种缘份,不早不晚,正是那个时间到来,就在那一个瞬间,你们彼此就会默默对上眼。

其实,民工哥还是要求进步的,想想,这样下去可不行,终究还是改变下自己的思维方式。不然5年再5年,可能仍然如此。在这之前,对网络设备这块还是比较感兴趣,09年的时候是想去考CCIE的,可是那时穷啊,说多了都是泪!!所以这次下定决心,去考一考,其一,也算是为了考验下自己的学习能力,其二,也算是为了圆了自己当初的一个小小的愿望。从此,踏上了长达一年之久的CCIE RS的学习与考试之路,每天除了上班、吃饭、睡觉的时间,其它全都用在了看书、找资料、看视频、做实验上面。下面给大家看下当年总结的一些资料截图。

clipboard.png

clipboard.png

光笔记就长达20W+字

clipboard.png

也是从那时候起,养成爱总结,爱记录的好习惯。当你的年龄增长,你会发现这个好习惯会比较受益的。那是因为,人年纪的增长,记忆力是衰退(无人能够否认这一点),其次,你有了家,有了孩子的,你思绪不在能专注到某一件事上,所以,小伙伴们,我很认真负责的告诉你,从现在起,养成爱记录的好习惯不会错的,好的习惯你将会受益终身。

长达一年的学习,也让我结识了很多原来不认识的一些朋友,当时我们将自己称之为“野战军(也就是自学)”,现在想想,那时的自己还是有点冲劲的,当然现在也不会减少,经常和这些“战友”位讨论问题到深夜,记得有一次,为了一个OSPF MPLS的问题和一个战友搞到3点才解决,现在想想不禁感叹啊,年轻真好。

虽然这次的考试以失败告终,但我仍将铭记那段努力、辛苦付出的日子。翻出了当时回来写的总结,我们那时将战报,是每个群里人考试完后都会写的,也算是给后面的人一些提示。

其实,这一年多的时间,几乎是没有休息时间,基本全花在这个上面。这里给大家提下,不是说大家一定要去考这个证书,当然了,你说它没有价值,那是错误的。任何事物它都有存在的价值。

然而,考这个证书,不仅是需要时间,更是需要一定花费,报名费、笔试费、机试费(国内固定北京、香港才有考场)一共1850美刀,那时候折合RMB在11500-12000之间。所以它也是不笔不小的支出。

clipboard.png

香港维多得来港与星光大道

clipboard.png

在此也要感谢下我现在的爱人,当时的女朋友,家务什么她全包了。所以啊,朋友们,伙伴们,找对象,找人生伴侣,真的还是要找与自己有共同目标的人,虽然说起来好像有点虚,有点不太实际。肯定很多人都会讲,没钱没势,活该你单身。其实,这是错误的,两个人走到一起,组建家庭是真的需要两个人的人生观、价值观相同才可以。否则你们终将会形同陌路,分道扬镳。

其实,无论是什么样的经历,对于一个人的人生来说,它都是财富,是别人无法给予你的财富。要相信自己,相信自己所做的都有用的,只不过是现在没有用上而已。

转眼之间,我也成家了,虽然没有立业,虽然即将踏过而立之年,走向奔四的道路。这一年(2014年),我组建了家庭,然而在这一年,我也成了有房一族。现在回头想想,当初不知是怎么想的,突然间买房了,只能说当初决定是正确的(在这给老婆大人一个大赞,当时是她坚决要买,当时房价5200+,现如今12000+左右,不能说是赚钱了,但是也是一种财富升值的表现)。可是你们都不知道,买房付完首付后,我们两个人全部家当只有3000块(N张银行卡+微信+支付宝)。

clipboard.png

通过买房这件事,再次给予我一个提示。那就是限制你与他人距离的真正原因,真的不是你不努力,不积极向上,而是你与他人思维模式之间的差距。就像很多人总是“很执着(其实是死脑子)”的认为房价终会降下去,可是N年,甚至N++年之后,它降了吗?有可能它真的降了一点(从10000到30000,然后降到25000,的确是降了)。

其实,还是那句话,当你想做某件事的时候,千万不要犹豫放手去做,办法总比困难多,这里也给那些刚需伙伴们,决定好自己今后的发展地点,在自己经济还能允许的情况下,抓紧时间买房,千万不要幻想房价会降,可能也许会降,但那是某一个阶段性的,仅个人经验,不喜勿喷。肯定会有很多人会说,现在买不起啊,那么,将来你可能更加的买不起,一句话,漂终将不是办法,定才是王道。

不过,在这几年间,我们和其它杭漂的伙伴们一样,搬了无数次的家,经历过无数次房租的上涨,同样也经历一些霸道的、拿着房子押金不还的房东(其实是二房东,就是中介),真人真事,后来我跑到他们的中介公司直接找他们才拿回来。这就是所有身处他乡,到处漂泊的“漂一族”痛苦之所在。

clipboard.png

思则通、通则顺、顺则人生处处是美丽风景

可能很多人会问,最后怎么选择了做运维这个行业。我只能这么,就是自己不断折腾,不断尝试,不断的摸索出来的一条道路。在这之前,我做过很多岗位,网管、网工、企业信息化建设、类似PM的岗位、信息技术经理、信息安全、最终落定在目前的运维岗位上。

对于运维老鸟来说,我也是菜鸟,也是在不断的探索中前行,也是一个不断试错的过程,同样是一个经验积累的过程,工作亦如此,人生亦如此。
对于系统运维这块,我依然是自学。也不是说现在的培训行业很黑(当然不排除有),也不是说从培训中出来的就学不到东西。只是因为,我比较喜欢自己折腾,我的人生格言:“生命不息,折腾不止”。其实,不光是运维行业,其它IT技术类的行业,终将是以企业实际需求、解决问题为出发点的。

对于我这样一个比较笨的人来说,最好的学习方法那就是不断的练习,不断的总结,可以说是一遍又一遍的记笔记,没有别的方法。刚刚开始入门的时候,我和大家一样,也是无从下手,不知道从哪里看起。网络上的资料的确很多,但是都是要么太深、要么太浅。对于好多问题也是一时半会无法理解,或者说有些问题压根自己是无法解决的。但请教谁呢??大牛们都很忙,怎么办呢?只好自己慢慢折腾吧。

慢慢的,我也总结出一些自己的经验,基础是第一位的,基础抓的牢靠,后面的学习顺风顺水。我也将自己的总结的经验和大家分享一下,不一定适用于所有人、所有的技术方向,仅供大家参考。
大概有以下几点:

1、注重基础,别以为都是很简单的命令或理论知识。

2、要勤于练习,不要认为命令都已经很熟悉了,直接复制粘贴即可。

3、千万不要照搬别人的博客、网站等等上面的操作过程,没有理解,那些都是无用功。

4、别纠结哪些毫无意义的事情(为一个压根不用命令参数纠结一周时间,真有其其事,有些小伙伴应该听我说起过)。

5、学会自我查找问题原因,不要动不动就去问人,这是个不好的习惯,无论是学习哪个技术方向。

6、学会看日志,学会看日志中的关键错误点信息,然后才能有征对的去解决问题。

7、总结、总结、再总结,这些都是你以后工作中的财富(千万不要忽视)。

8、学会如何去请教别人(别无厘头的扔给人一堆截图,没有人会回答你),至少你在向他人请教的时候,你要说出你的操作环境,你的操作步骤、需求是什么?目前的出错点是什么?

9、学习是持续性的动作,所以坚持是很重要,半途而废的多了去了,所以,至于你学的怎么样,就看你能不能比别人可以坚持下去。这里说个事,搞IT的好多都是烟民,民工哥同样也是,有着十年烟龄的老烟民,但是2015年3月27号开始我戒掉,在当时,不为别的,其一为了即将出生的孩子的健康(身为人父的责任),其二为了证明自己还是能将一件事做好。别无它意,仅些而已。

10、做自己认为正确的事,不受他人左右,很多人在学习某个技术方向时,听他人讲那个方向不行拉,找不到工作拉,是真的吗?

11、找一个让自己能够坚持走下去的理由(像民工哥,我能不坚持学习吗?要面临房贷、养娃,时常摸摸自己的口袋,那将是你不得不努力与坚持的理由)。
以上的一些个人总结的经验,希望对小伙伴们所有帮助。

转眼即是2017年,细数时间,来杭城整整十年了。十年间,杭城的变化太大太大,有时候都不敢相信,自己竟然亲眼见证了这些变化。十年间,我们在这里挥洒过自己的青葱岁月,十年间,在这里有过我们曾经一起奋斗的时光。

也许,我们终将不属于这里,不属于这片繁华都市中的一员。所以,有时候,退一步海阔天空。不一定非得在大都市里,你才能过上自己想要的生活,才能拥有那些自己想要的。

也是因为这些原因,才使得我们决定回撤霸都,毕竟多年前购买的房子在这空着也是浪费了,同时也是为了提前让孩子回来适应另一个生活环境,再加上一些种种因素,才有文章开头的那一幕情景的发生。随着一声轻脆的刹车声,就在这声中结束了我十年的杭漂生活,而又是开启了下一个十年的新的生活。

clipboard.png

不管你才踏出社会,还是已久经沙场,请相信,这个世界上,岁月对每个人都是公平的,1天都是24个小时,一分钟都是60秒。也许你要花久一点的时间才能找到你真正想做的事情,也许你要花长一点的时间才能改变现在的状况,但是不管早还是晚,请你一定要出发,不管是早还是晚,请记得一定要努力去做、去改变!!!

如果,此文对于你来说,有一定的共鸣。或者说也有你打拼、奋斗路上的某个面的缩影。再者说,你也有一定的感触,请动动手指转发下此文到朋友圈支持一下。也欢迎各位小伙伴们留言,说说你的那些故事、对生活的向往、感触,或者那些令你难忘的曾经。

本文参与了 SegmentFault思否征文「一起分享你的故事」,欢迎正在阅读的你也加入,分享你的故事。

查看原文

赞 16 收藏 2 评论 12

小麦 提出了问题 · 2019-07-01

window. Weixinjsbridge already exists

微信授权回来,点击上传图片报window. Weixinjsbridge already exists

很难重新,刷新一下就好了

报错截图

clipboard.png

有谁遇到过?

关注 1 回答 1

小麦 回答了问题 · 2019-06-24

Vue页面跳转后有按钮,但是必须刷新一下按钮才可点击,否则不可点,是什么问题呢?

我觉得我现在的问题和你遇到的一样,我是用this.$router.push跳转的,也跳不动

关注 2 回答 2

小麦 回答了问题 · 2019-05-15

小程序中的qcloud.login( )和wx.login( )有什么区别?

我也想问,你知道了?

关注 2 回答 1

小麦 赞了文章 · 2019-04-23

基于socket.io快速实现一个实时通讯应用

随着web技术的发展,使用场景和需求也越来越复杂,客户端不再满足于简单的请求得到状态的需求。实时通讯越来越多应用于各个领域。

HTTP是最常用的客户端与服务端的通信技术,但是HTTP通信只能由客户端发起,无法及时获取服务端的数据改变。只能依靠定期轮询来获取最新的状态。时效性无法保证,同时更多的请求也会增加服务器的负担。

WebSocket技术应运而生。

WebSocket概念

不同于HTTP半双工协议,WebSocket是基于TCP 连接的全双工协议,支持客户端服务端双向通信。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

HTTP与websocket对比

实现

原生实现

WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror。

建立连接

通过javascript可以快速的建立一个WebSocket连接:

    var Socket = new WebSocket(url, [protocol] );

以上代码中的第一个参数url, 指定连接的URL。第二个参数 protocol是可选的,指定了可接受的子协议。

同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头,另外安全的WebSocket协议使用wss://开头。

  1. 当Browser和WebSocketServer连接成功后,会触发onopen消息。
    Socket.onopen = function(evt) {};
  1. 如果连接失败,发送、接收数据失败或者处理数据出现错误,browser会触发onerror消息。
    Socket.onerror = function(evt) { };
  1. 当Browser接收到WebSocketServer端发送的关闭连接请求时,就会触发onclose消息。
    Socket.onclose = function(evt) { };

收发消息

  1. 当Browser接收到WebSocketServer发送过来的数据时,就会触发onmessage消息,参数evt中包含server传输过来的数据。
    Socket.onmessage = function(evt) { };
  1. send用于向服务端发送消息。
    Socket.send();

socket

WebSocket是跟随HTML5一同提出的,所以在兼容性上存在问题,这时一个非常好用的库就登场了——Socket.io

socket.io封装了websocket,同时包含了其它的连接方式,你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。

socket.io是基于 Websocket 的Client-Server 实时通信库。

socket.io底层是基于engine.io这个库。engine.io为 socket.io 提供跨浏览器/跨设备的双向通信的底层库。engine.io使用了 Websocket 和 XHR 方式封装了一套 socket 协议。在低版本的浏览器中,不支持Websocket,为了兼容使用长轮询(polling)替代。

engine.io

API文档

Socket.io允许你触发或响应自定义的事件,除了connect,message,disconnect这些事件的名字不能使用之外,你可以触发任何自定义的事件名称。

建立连接

    const socket = io("ws://0.0.0.0:port"); // port为自己定义的端口号
    let io = require("socket.io")(http);
    io.on("connection", function(socket) {})

消息收发

一、发送数据

    socket.emit(自定义发送的字段, data);

二、接收数据

    socket.on(自定义发送的字段, function(data) {
        console.log(data);
    })

断开连接

一、全部断开连接

    let io = require("socket.io")(http);
    io.close();

二、某个客户端断开与服务端的链接

    // 客户端
    socket.emit("close", {});
    // 服务端
    socket.on("close", data => {
        socket.disconnect(true);
    });

room和namespace

有时候websocket有如下的使用场景:1.服务端发送的消息有分类,不同的客户端需要接收的分类不同;2.服务端并不需要对所有的客户端都发送消息,只需要针对某个特定群体发送消息;

针对这种使用场景,socket中非常实用的namespace和room就上场了。

先来一张图看看namespace与room之间的关系:

namespace与room的关系

namespace

服务端

    io.of("/post").on("connection", function(socket) {
        socket.emit("new message", { mess: `这是post的命名空间` });
    });
    
    io.of("/get").on("connection", function(socket) {
        socket.emit("new message", { mess: `这是get的命名空间` });
    });

客户端

    // index.js
    const socket = io("ws://0.0.0.0:****/post");
    socket.on("new message", function(data) {
        console.log('index',data);
    }
    
    //message.js
    const socket = io("ws://0.0.0.0:****/get");
    socket.on("new message", function(data) {
        console.log('message',data);
    }

room

客户端

    //可用于客户端进入房间;
    socket.join('room one');
    //用于离开房间;
    socket.leave('room one');

服务端

    io.sockets.on('connection',function(socket){
        //提交者会被排除在外(即不会收到消息)
        socket.broadcast.to('room one').emit('new messages', data);
        // 向所有用户发送消息
        io.sockets.to(data).emit("recive message", "hello,房间中的用户");      
    }

用socket.io实现一个实时接收信息的例子

终于来到应用的阶段啦,服务端用node.js模拟了服务端接口。以下的例子都在本地服务器中实现。

服务端

先来看看服务端,先来开启一个服务,安装expresssocket.io

安装依赖

    npm install --Dev express
    npm install --Dev socket.io

构建node服务器

    let app = require("express")();
    let http = require("http").createServer(handler);
    let io = require("socket.io")(http);
    let fs = require("fs");
    
    http.listen(port); //port:输入需要的端口号
    
    function handler(req, res) {
      fs.readFile(__dirname + "/index.html", function(err, data) {
        if (err) {
          res.writeHead(500);
          return res.end("Error loading index.html");
        }
    
        res.writeHead(200);
        res.end(data);
      });
    }
    
    io.on("connection", function(socket) {
        console.log('连接成功');
        //连接成功之后发送消息
        socket.emit("new message", { mess: `初始消息` });
        
    });

客户端

核心代码——index.html(向服务端发送数据)

    <div>发送信息</div>
    <input placeholder="请输入要发送的信息" />
    <button onclick="postMessage()">发送</button>
    // 接收到服务端传来的name匹配的消息
    socket.on("new message", function(data) {
      console.log(data);
    });
    
    function postMessage() {
      socket.emit("recive message", {
        message: content,
        time: new Date()
      });
      messList.push({
        message: content,
        time: new Date()
      });
    }

核心代码——message.html(从服务端接收数据)

    socket.on("new message", function(data) {
      console.log(data);
    });

效果

实时通讯效果
实时通讯效果

客户端全部断开连接
全部断开

某客户端断开连接
某客户端断开连接

namespace应用
namespace

加入房间
加入房间

离开房间
离开房间

框架中的应用

npm install socket.io-client

    const socket = require('socket.io-client')('http://localhost:port');

    componentDidMount() {
        socket.on('login', (data) => {
            console.log(data)
        });
        socket.on('add user', (data) => {
            console.log(data)
        });
        socket.on('new message', (data) => {
            console.log(data)
        });
    }

分析webSocket协议

Headers

Headers

请求包

    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    Cache-Control: no-cache
    Connection: Upgrade
    Cookie: MEIQIA_VISIT_ID=1IcBRlE1mZhdVi1dEFNtGNAfjyG; token=0b81ffd758ea4a33e7724d9c67efbb26; io=ouI5Vqe7_WnIHlKnAAAG
    Host: 0.0.0.0:2699
    Origin: http://127.0.0.1:5500
    Pragma: no-cache
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    Sec-WebSocket-Key: PJS0iPLxrL0ueNPoAFUSiA==
    Sec-WebSocket-Version: 13
    Upgrade: websocket
    User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1

请求包说明:

  • 必须是有效的http request 格式;
  • HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1;
  • 必须包括Upgrade头域,并且其值为“websocket”,用于告诉服务器此连接需要升级到websocket;
  • 必须包括”Connection” 头域,并且其值为“Upgrade”;
  • 必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列;
  • 如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接;
  • 必须包括“Sec-webSocket-Version”头域,是当前使用协议的版本号,当前值必须是13;
  • 可能包括“Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之;
  • 可能包括“Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强;
  • 可能包括任意其他域,如cookie.

应答包

应答包说明:

    Connection: Upgrade
    Sec-WebSocket-Accept: I4jyFwm0r1J8lrnD3yN+EvxTABQ=
    Sec-WebSocket-Extensions: permessage-deflate
    Upgrade: websocket
  • 必须包括Upgrade头域,并且其值为“websocket”;
  • 必须包括Connection头域,并且其值为“Upgrade”;
  • 必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是“Sec-WebSocket-Accept”的值;
  • 应答包中冒号后面有一个空格;
  • 最后需要两个空行作为应答包结束。

请求数据

    EIO: 3
    transport: websocket
    sid: 8Uehk2UumXoHVJRzAAAA
  • EIO:3 表示使用的是engine.io协议版本3
  • transport 表示传输采用的类型
  • sid: session id (String)

Frames

WebSocket协议使用帧(Frame)收发数据,在控制台->Frames中可以查看发送的帧数据。

其中帧数据前的数字代表什么意思呢?

这是 Engine.io协议,其中的数字是数据包编码:

<Packet type id> [<data>]

  • 0 open——在打开新传输时从服务器发送(重新检查)
  • 1 close——请求关闭此传输,但不关闭连接本身。
  • 2 ping——由客户端发送。服务器应该用包含相同数据的乓包应答

    客户端发送:2probe探测帧
  • 3 pong——由服务器发送以响应ping数据包。

    服务器发送:3probe,响应客户端
  • 4 message——实际消息,客户端和服务器应该使用数据调用它们的回调。
  • 5 upgrade——在engine.io切换传输之前,它测试,如果服务器和客户端可以通过这个传输进行通信。如果此测试成功,客户端发送升级数据包,请求服务器刷新其在旧传输上的缓存并切换到新传输。
  • 6 noop——noop数据包。主要用于在接收到传入WebSocket连接时强制轮询周期。

实例

发送数据

接收数据

以上的截图是上述例子中数据传输的实例,分析一下大概过程就是:

  1. connect握手成功
  2. 客户端会发送2 probe探测帧
  3. 服务端发送响应帧3probe
  4. 客户端会发送内容为5的Upgrade帧
  5. 服务端回应内容为6的noop帧
  6. 探测帧检查通过后,客户端停止轮询请求,将传输通道转到websocket连接,转到websocket后,接下来就开始定期(默认是25秒)的 ping/pong
  7. 客户端、服务端收发数据,4表示的是engine.io的message消息,后面跟随收发的消息内容

为了知道Client和Server链接是否正常,项目中使用的ClientSocket和ServerSocket都有一个心跳的线程,这个线程主要是为了检测Client和Server是否正常链接,Client和Server是否正常链接主要是用ping pong流程来保证的。

该心跳定期发送的间隔是socket.io默认设定的25m,在上图中也可观察发现。该间隔可通过配置修改。

socket通信流程

参考engine.io-protocol

参考文章

Web 实时推送技术的总结
engine.io 原理详解

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

查看原文

赞 137 收藏 110 评论 6

认证与成就

  • 获得 67 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-10-13
个人主页被 406 人浏览