Justin

Justin 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织 segmentfault.com/u/jinwq 编辑
编辑

山高总有顶,天大却无边;坐井观天小,登高望天阔。谋胜虑败终不败,取长补短终不短;知错改错终不错,知耻雪耻终不耻。

个人动态

Justin 赞了文章 · 3月10日

互联网人吐槽互撕系列漫画要来啦~

大家新年好,我是南哥,你们也可以叫我南南、苏南,但 不能叫我渣南,虽然画 画的有点渣,但人并不渣哦

这周没有画,跟大家聊点其他的.

前段时间讲过,经常有小伙伴在后台问我:"南哥,你每天工作不忙吗?怎么有这么多时间画画?"、"南哥,你到底是干设计的,还是前端啊?"等之类的问题。

今天就跟大家简单聊下吧,同时也说说自己今年的一些想法.

问题1:哪来的时间画画?

工作其实很忙,现在互联网风气特别不好,资本家越来越卷,不停的内卷,虽说没有996,但也已经接近995了,晚上基本8点半~9点半左右走,有时候项目上线会更晚,那么时间哪里来的呢?

大部分时间在周末,如果没有什么特别的事,两天我基本都是在画画(是不是挺无聊的?),周一到周五,每天早上会抽出1小时左右的时间来画一下(以前有晨跑的习惯,后来检查关节不好,医生建议少跑,慢慢就落下了,现在就改用来画画了),其余空闲时间会进行脑洞、构思策划,一般画一篇下来大概需要1.5~2周的时间,所以有些催更的小伙伴也理解下,一周一篇已经是及限,时间较少是事实、功底不好画的慢也是事实,但我会坚持努力.

问题2:主职内容是什么?

有些朋友可能已经知道了,本人是一枚90后,目前在深圳一非著名互联网公司(小作坊)从事前端开发工作,也是野前端半路出家,
刚毕业那会,是在北京一家公司做网页设计(你可以理解为美工),为什么说要叫网页设计呢,因为那时候这个职位是设计跟网页重构、Jquery交互都要这个职位干的,经常做完设计就要写页面,时间久了,发现自己实在是没有那个设计天赋,发现反而更喜欢代码,后来随着前端慢慢掘起,就转了做前端,这一做就是好多年,然后发现程序员其实是一个非常有趣的群体,只是有些特别,一般人理解不了.

为什么会画画?

话说虽然不做设计好多年,但心里还是一直有一个画家的梦的,非常崇拜会画画的大师,羡慕他们信手拈来,拿起笔就画的风范,而且一直的梦想是有朝一日能成为一个能执笔画尽世间沧桑的大师.

可能是受到内心深处梦想的召唤与渴望,又或是而立之年的迷茫,2020年年后我又试着开始学画画,最开始是在B站上找一些基础教程、买了些画画相关的书、关注公众号等来练习,学了一段时间,可发现自己连根线都画不直,曾经几度决定放弃,还好、还好最后坚持下来了(当然现在线同样画不直),就这样学着画着差不多有近一年时间吧,总算有点感觉了.

画什么?

国庆后,就开始在思考,我可以画些什么呢?思索许久,觉得还是从程序员入手比较好,自己是不是可以把程序员相关的日常、生活、职场、工作、技术等有关的小故事通过漫画表达出来呢??让更多的人了解程序员神(有)秘(趣)的一面,他们并不是话少、钱多、挂的……,他们同样有烦恼、也浪漫风趣、有追求,只是不容易发现而已.

好了,方向有了,但如何找到一个切入点,从哪里开始画呢?很遗憾,想了很久,也没有找到一个好的切入点,然后又决定改变策略,自己是干前端的,也最了解前端,于是又思考从前端领域入手,这一次终于……,是的,你没有猜错,这一次终于憋出来了一篇前端发展史简史,从找资料到策划小剧本,到开始画、画完又改、改完又画,历时差不多两个月,一直到11月30号,我的处女作《漫画 | 前端发展史的江湖恩怨情仇》终于面世了.

同时也得到了不错的反馈与肯定,因为这篇漫画有被近30多个公众号转载,其中不乏有很多大号,如:CSDN程序人生、慕课网、开源中国社区、脚本之家、前端早读课、程序员最幽默等,当然最、最、最要感谢的还是每一位默默关注我们的小伙伴们,你们关注支持是对我最大的肯定,你们才是我最爱的人.

再后来,你们也都知道了,每周我都会输出一篇漫画,都还不错,大部分都得到了大家的肯定与赞誉,但都是没有任何关联性的,毫无目的画着,是的,你没有看错,我又陷入了迷茫的状态,同时又一直在迷茫的边缘垂死掙扎,也经常会在群里跟大家交流,找身边的大佬朋友们请教,收集了很多的资料/信息意见,希望找到那盏能照亮我前行的路的明灯,指引我前路在何方、能拯救我这只迷途羔羊.

这里插一句,如果你们有好的致富捷径,记得带我,比如:富婆、阿姨、推荐一支涨停股啊,等等这些,我都是可以接受的…,哈哈

你们这群最可爱的人

没错,上天是眷顾我的,2021年它为我打开了一扇窗,有你们这群最可爱的人,在背后给于我很多建议、启发思路,我终于摸索到了前行的门槛,也是我未来一直要坚持努力的方向.

这个方向,其实也很简单,却也很难(对于我),就是尝试画一些系列类有连续性的内容.

为什么说简单呢?因为我们身边有很多这样的例子,掘金、思否等平台,有很多系列类的文章,大家每天都能看到.

为什么又说却很难呢??我比较菜……(哈哈,开个玩笑)。首先技术类系列文章有我们熟悉的技术基础知识支持,我们只要肯深入钻研学习,写起来并不难,
但连续系列性漫画,却不一样:<br/>
1、需要把程序员相关信息或技术相关的话题,转换表达的方式,<br/>
2、如何编写出系列性的剧本话题,感觉策划比写代码还难,<br/>
3、还有画画,我是半吊子上路,功底不足,很多场景能想到却不一定会画<br/>
4、等等…

尝试

俗话说:万事开头难、然后中间难、…,虽然困难很多,不过我还是愿意去尝试,在断断续续几个月时间里收集了一些意见与想法后,开始了策划我的第一个系列 —— 《互联网人吐槽大会系列👈》,初步预计在5~6篇吧

目录:

1、前端把后端告上县衙,还列了 5 宗罪<br/>
2、程序员把产品经理告上县衙,还列了 8大罪状<br/>
3、前端把测试告上县衙,被判了6年<br/>
4、设计师把前端告上县衙,终于锒铛入狱<br/>
5、一怒之下把领导告上县衙,结局万万没想到<br/>
6、……

写在最后

内容初步规划就是上面那些,整体的剧情大概是围绕互联网职场人来展开的,比如:前端对后端开发人员有很多意见、程序员烦透了产品经理天天改需求、……等等,有点类似吐槽大会的感觉,用风趣幽默的漫画形式讲述互联网职场人的无奈与现实、虚构中又带着那么点真实.

部分剧情还在策划中,在这里也跟大家征个稿,如果你有好的想法,或者有兴趣且擅长策划类似的小剧情,欢迎随时来找交流.

预计从下周开始(03.01号),争取每周更新一篇,第一次尝试,如果觉得有不足的地方,请一定要帮指出来,让我及时改正,90度鞠躬感谢!!

同时也希望大家能多多支持、给予包容,每周分享的漫画,觉得不错,请点个「赞」、「分享」,让更多人看到,万分感谢。

往期回顾

漫画程序员 前端布道师-苏南著作

更多精彩,欢迎关注我们

本文首发于公众号:前端布道师

链接:https://mp.weixin.qq.com/s/zh5xYgeI7KgHParpILC8jg

转载请联系微信:su-south 授权

用漫画解读前端技术,执笔演绎程序人生,愿吾手中笔,能博君(卿)一笑
Github地址:更多有趣漫画https://github.com/meibin08/comics-program-life欢迎 Star、watch

查看原文

赞 3 收藏 1 评论 2

Justin 关注了用户 · 1月27日

张京 @fengerzh

现任北京联云天下科技有限公司技术副总裁。1994年本科毕业于清华大学计算机科学与技术专业;20多年软件开发及项目管理经验;历任亚洲生活网络公司CTO,摩托罗拉软件中心QSE工具经理,融信恒通技术总监,安必信软件公司技术副总裁,微能创投加速器创始人CTO等职。

关注 1371

Justin 赞了回答 · 1月27日

关于 API 的一个问题,不知道是我的想法错了,还是服务端的想法错了?

我做前端的,刚开始那会,我也会抱怨,后端那些人什么玩意,数据都不处理下。但是现在不会了,因为数据在后端处理,会给服务器造成压力,这个压力随着数据量和用户量的增加是不断增加的,而数据在前台处理,相当于实现了分布式,每个客户端都是一个小服务器,大大减轻了服务器的压力。

而在后台处理,你以为他们就不麻烦吗,有的比在前台还麻烦,这看语言,毕竟js是动态语言,数据处理相对是简单的。

再说一下你的烦恼,无非就是数据处理麻烦,这关键点还是自身实力问题,这就需要你去提升了,比如基础、算法等,这也是目前前段需要会算法的一个原因。

关注 8 回答 8

Justin 提出了问题 · 2020-12-23

视频能实现内凹圆角吗?

image
如上图所示:有办法将视频右下角弄成内凹圆角吗?视频是盖在一个可拖动地图上的,所以不能采用传统在视频右下角盖圆角图片的方式。

关注 1 回答 1

Justin 回答了问题 · 2020-12-04

antd 的Input 组件如何取值 弄了半天没成功

componentDidMount() {
    console.log(this.input.value);
}
<input ref={ref => { this.input = ref }} value="12345" />

关注 4 回答 3

Justin 回答了问题 · 2020-12-03

请问, state是什么新语法

关注 3 回答 4

Justin 收藏了文章 · 2020-12-01

DOM 高级工程师不完全指南

本文干货部分翻译自: Use the DOM like a Pro
译者:kyrieliu(劉凯里)

“前端框架真的太香了,香到我都不敢徒手撕 DOM 了!”

虽然绝大多数前端er都有这样的困扰,但本着基础为大的原则,手撕 DOM 应当是一个前端攻城狮的必备技能,这正是本文诞生的初衷 —— DOM 并没有那么难搞,如果能去充分利用它,那么你离爱上它就不远了。

三年前我初入前端坑的时候,发现了一个叫做 jQuery 的宝贝,她有一个神奇的 $ 函数,可以让我快速选中某一个或一组 DOM 元素,并提供链式调用以减少代码的冗余。虽然现在提到 jQuery 这个名词,你会觉得老土,“都 9102 年了你跟我说 Nokia?”。土归土,但也是真的香。尽管这几年风生水起的 Vue、React 加剧了 jQuery 的没落,但全世界仍有超过 6600 万个网站在使用 jQuery,占全球所有网站数量的 74%。

jQuery 也给业界留下了产生深远影响的“遗产”,W3C 就仿照其 $ 函数实现了 querySelector 和 querySelectorAll。而讽刺的是,也正是这两个原生方法的出现,大大加快了 jQuery 的没落,因为它们取代了前者最常用的功能 —— 快捷的选择 DOM 元素。

虽然这两个新方法写起来有点长(问题不大,封装一哈),但是它们是真的贼好用。

来,冲!

获取 DOM 元素

获取单个元素

向 document.querySelector 中传入任何有效的 css 选择器,即可选中单个 DOM 元素:

document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')

如果页面上没有指定的元素时,返回 null

获取元素集合

使用 document.querySelectorAll 可以获取一个元素集合,它的传参和 document.querySelector 一毛一样。它会返回一个静态的 NodeList ,如果没有元素被查找到,则会返回一个空的 NodeList 。

NodeList 是一个可遍历的对象(aka:伪数组),虽然和数组很像,但它确实不是数组,虽然可以利用 forEach 遍历它,但它并不具备数组的一些方法,比如 map、reduce、find。

那么问题来了,如何将一个伪数组转化为数组呢?ES6 为开发者提供了两个便利的选择:

const arr = [...document.querySelectorAll('div')]
// or
const alsoArr = Array.from(document.querySelectorAll('div'))

远古时代,开发者们常用 getElementsByTagName 和 getElementsByClassName 去获取元素集合,但不同于 querySelectorAll,它们获取的是一个动态的 HTMLCollection,这就意味着,它的结果会一直随着 DOM 的改变而改变。

元素的局部搜索

当需要查找元素时,不一定每次都基于 document 去查找。开发者可以在任何 HTMLElement 上进行 DOM 元素的局部搜索:

const container = document.querySelector('#container')
container.querySelector('#target')

打得字太多了啊喂!

事实证明,每个优秀的开发者都是很懒的。为了减少对宝贝键盘的损耗,我一般会这么干:

const $ = document.querySelector.bind(document)

保护机械键盘,从我做起。

少年,爬上这棵 DOM 树

上述内容的主题是查找 DOM 元素,这是一个自上而下的过程:从父元素向其包含的子元素发起查询。

但没有一个 API 可以帮助开发者借由子元素向父元素发起查询。

迷惑之际,MDN 给我提供了一个宝藏方法:closest 。

Starting with the Element itself, the closest() method traverses parents (heading toward the document root) of the Element until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returns null.

也就是说,closest 方法可以从特定的 HTMLElement 向上发起查询,找到第一个符合指定 css 表达式的父元素(也可以是元素自身),如果找到了文档根节点还没有找到目标时,就会返回 null 。

添加 DOM 元素

如果用原生 JavaScript 向 DOM 中添加一个或多个元素,一般开发者的内心都是抗拒的,为啥呢?假设向页面添加一个 a 标签:

<a href="/home" class="active">首页</a>

正常情况下,需要写出如下的代码:

const link = document.createElement('a')
link.setAttribute('href', '/home')
link.className = 'active'
link.textContent = '首页'

// finally
document.body.appendChild(link)

真的麻烦。

而老大哥 jQuery 可以简化为:

$('body').append('<a href="/home" class="active">首页</a>')

但,各位观众,如今原生 JavaScript 也可以实现这一操作了:

document.body.insertAdjacentHTML(
    'beforeend',
  '<a href="/home" class="active">首页</a>'
)

这个方法允许你将任何有效的 HTML 字符串插入到一个 DOM 元素的四个位置,这四个位置由方法的第一个参数指定,分别是:

  • 'beforebegin': 元素之前
  • 'afterbegin': 元素内,位于现存的第一个子元素之前
  • 'beforeend': 元素内,位于现存的最后一个子元素之后
  • 'afterend': 元素之后
<!-- beforebegin -->
<div>
    <!-- afterbegin -->
  <span></span>
     <!-- beforeend -->
</div>
<!-- afterend -->

舒服了呀。

更舒服的是,它还有两个好兄弟,让开发者可以快速地插入 HTML 元素和字符串:

// 插入 HTML 元素
document.body.insertAdjacentElement(
    'beforeend',
    document.createElement('a')
)

// 插入文本
document.body.insertAdjacentText('afterbegin', 'cool!')

移动 DOM 元素

上面提到的兄弟方法 insertAdjacentElement 也可以用来对已存在的元素进行移动,换句话说:当传入该方法的是已存在于文档中的元素时,该元素仅仅只会被移动(而不是复制并移动)。

如果你有以下 HTML:

<div class="first">
  <h1>Title</h1>
</div>

<div class="second">
  <h2>Subtitle</h2>
</div>

然后操作一下,把 <h2> 搞到 <h1> 的后面去:

const h1 = document.querySelector('h1')
const h2 = document.querySelector('h2')

h1.insertAdjacentElement('afterend', h2)

于是我们就得到了这样的结果:

<div class="first">
  <h1>Title</h1>
  <h2>Subtitle</h2>
</div>

<div class="second">
    
</div>

替换 DOM 元素

replaceChild? 这是几年前的做法了,每当开发者需要替换两个 DOM 元素,除了需要拿到这必须的两个元素之外,还需要获取他们的直接父元素:

parentNode.replaceChild(newNode, oldNode)

而如今,开发者们可以使用 replaceWith 就可以完成两个元素之间的替换了:

oldElement.replaceWith(newElement)

从用法上来说,要比前者清爽一些。

需要注意的是:

  1. 如果传入的 newElement 已经存在于文档中,那么方法的执行结果将是 newElement 被移动并替换掉 oldElement
  2. 如果传入的 newElement 是一个字符串,那么它将作为一个 TextNode 替换掉原有的元素

移除 DOM 元素

和替换元素的老方法相同,移除元素的老方法同样需要获取到目标元素的直接父元素:

const target = document.querySelector('#target')
target.parentNode.removeChild(target)

现在只需要在目标元素上执行一次 remove 方法就 ok 了:

const target = document.querySelector('#target')
target.remove()

用 HTML 字符串创建 DOM 元素

细心的你一定发现了,上文提到的 insertAdjacent 方法允许开发者直接将一段 HTML 插入到文档当中,如果我们此刻只想生成一个 DOM 元素以备将来使用呢?

DOMParser 对象的 parseFromString 方法即可满足这样的需求。该方法可以实现将一串 HTML 或 XML 字符串转化为一个完整的 DOM 文档,也就是说,当我们需要获得预期的 DOM 元素时,需要从方法返回的 DOM 文档中获取这个元素:

const createSingleElement = (domString) => {
    const parser = new DOMParser()
  return parser.parseFromString(domString, 'text/html').body.firstChild
}

// usage
const element = createSingleElement('<a href="./home">Home</a>')

做一个检查 DOM 的小能手

标准的 DOM API 为开发者们提供了很多便利的方法去检查 DOM 。比如,matches 方法可以判断出一个元素是否匹配一个确定的选择器:

// <div class="say-hi">Hello DOM!</div>

const div = document.querySelector('div')

div.matches('div')      // true
div.matches('.say-hi')  // true
div.matches('#hi')      // false

contains 方法可以检测出一个元素是否包含另一个元素(或者:一个元素是否是另一个元素的子元素):

// <div><h1>Title</h1></div>
// <h2>Subtitle</h2>

const $ = document.querySelector.bind(document)
const div = $('div')
const h1 = $('h1')
const h2 = $('h2')

div.contains(h1)   // true
div.contains(h2)   // false

一招鲜:compareDocumentPosition

compareDocumentPosition 是一个强大的 API ,它可以快速判断出两个 DOM 元素的位置关系,诸如:先于、跟随、是否包含。它返回一个整数,代表了两个元素之间的关系。

// 还是用上面的例子哈
container.compareDocumentPosition(h1)   // 20
h1.compareDocumentPosition(container)   // 10
h1.compareDocumentPosition(h2)          // 4
h2.compareDocumentPosition(h1)          // 2

标准语句:

element.compareDocumentPosition(otherElement)

返回值定义如下:

  • 1: 两个元素不再同一个文档内
  • 2: otherElement 在 element 之前
  • 4: otherElement 在 element 之后
  • 8: otherElement 包含 element
  • 16: otherElement 被 element 所包含

那么问题来了,为什么上面例子中第一行的结果是20、第二行的结果是10呢?

因为 h1 同时满足“被 container 所包含(16)” 和 “在 container 之后”,所以语句的执行结果是 16+4=20,同理可推出第二条语句的结果是 8+2=10。

DOM 观察者:MutationObserver

在处理用户交互的时候,当前页面的 DOM 元素通常会发生很多变化,而有些场景需要开发者们监听这些变化并在触发后执行相应的操作。MutationObserver 是浏览器提供的一个专门用来监听 DOM 变化的接口,它强大到几乎可以观测到一个元素的所有变化,可观测的对象包括:文本的改变、子节点的添加和移除和任何元素属性的变化。

如同往常一样,如果想构造任何一个对象,那就 new 它的构造函数:

const observer = new MutationObserver(callback)

传入构造函数的是一个回调函数,它会在被监听的 DOM 元素发生改变时执行,它的两个参数分别是:包含本次所有变更的列表 MutationRecords 和 observer 本身。其中,MutationRecords 的每一条都是一个变更记录,它是一个普通的对象,包含如下常用属性:

  • type: 变更的类型,attributes / characterData / childList
  • target: 发生变更的 DOM 元素
  • addedNodes: 新增子元素组成的 NodeList
  • removedNodes: 已移除子元素组成的的 NodeList
  • attributeName: 值发生改变的属性名,如果不是属性变更,则返回 null
  • previousSibling: 被添加或移除的子元素之前的兄弟节点
  • nextSibling: 被添加或移除的子元素之后的兄弟节点

根据目前的信息,可以写一个 callback 函数了:

const callback = (mutationRecords, observer) => {
    mutationRecords.forEach({
    type,
    target,
    attributeName,
    oldValue,
    addedNodes,
    removedNodes,
  } => {
      switch(type) {
      case 'attributes':
        console.log(`attribute ${attributeName} changed`)
        console.log(`previous value: ${oldValue}`)
        console.log(`current value: ${target.getAttribite(attributeName)}`)
        break
      case 'childList':
          console.log('child nodes changed')
        console.log('added: ${addedNodes}')
        console.log('removed: ${removedNodes}')
        break
      // ...
    }
  })
}

至此,我们有了一个 DOM 观察者 observer,也有了一个完整可用的 DOM 变化后的回调函数 callback,就差一个需要被观测的 DOM 元素了:

const target = document.querySelector('#target')
observer.observe(target, {
    attributes: true,
  attributeFilter: ['class'],
  attributesOldValue: true,
  childList: true,
})

在上面的代码中,我们通过调用观察者对象的 observe 方法,对 id 为 target 的 DOM 元素进行了观测(第一个参数就是需要观测的目标元素),而第二个元素,我们传入了一个配置对象:开启对属性的观测 / 只观测 class 属性 / 属性变化时传递属性旧值 / 开启对子元素列表的观测。

配置对象支持如下字段:

  • attributes: Boolean,是否监听元素属性的变化
  • attributeFilter: String[],需要监听的特定属性名称组成的数组
  • attributeOldValue: Boolean,当监听元素的属性发生变化时,是否记录并传递属性的上一个值
  • characterData: Boolean,是否监听目标元素或子元素树中节点所包含的字符数据的变化
  • characterDataOldValue: Boolean,字符数据发生变化时,是否记录并传递其上一个值
  • childList: Boolean,是否监听目标元素添加或删除子元素
  • subtree: Boolean,是否扩展监视范围到目标元素下的整个子树的所有元素

当不再监听目标元素的变化时,调用 observer 的 disconnect 方法即可,如果需要的话,可以先调用 observer 的 takeRecords 方法从 observer 的通知队列中删除所有待处理的通知,并将它们返回到一个由 MutationRecord 对象组成的数组当中:

const mutationRecords = observer.takeRecords()
callback(mutationRecords)
observer.disconnect()

怕啥都不要怕 DOM

尽管大部分 DOM API 的名字都很长(写起来很麻烦),但它们都是非常强大并且通用的。这些 API 往往旨在为开发者提供底层的构建单元,以便在此之上建立更为通用和简洁的抽象逻辑,因此从这个角度出发,它们必须提供一个完整的名称以变得足够明确和清晰。

只要能发挥出这些 API 本应该发挥出的潜能,多敲几下键盘又何妨呢?

DOM 是每个 JavsScript 开发者必不可少的知识,因为我们几乎每天都在使用它。莫怕,大胆激发自己操作 DOM 的洪荒之力吧,尽早成为一个 DOM 高级工程师。

最后

扫码捕获一只还算有趣的前端er
扫码捕获一只还算有趣的前端er

查看原文

Justin 回答了问题 · 2020-10-29

解决:last-child 选择器没有生效,请问怎么回事?

.className:last-child: 匹配的是父元素的最后一个子元素,且同时满足类名为className的。两者缺一不可

关注 3 回答 3

Justin 回答了问题 · 2020-09-25

jquery :visible筛选

是的。你也可以这样 $('ul li :visible') 选取ul下面的li下面的可见元素

关注 3 回答 2

Justin 回答了问题 · 2020-09-24

解决js有合并数组对象的函数吗?

arr.map((ele,index)=>{
    if(arr2[index]) Object.assign(arr[index + 1],arr2[index])
})
console.log(arr);

关注 5 回答 4

认证与成就

  • 获得 58 次点赞
  • 获得 25 枚徽章 获得 1 枚金徽章, 获得 7 枚银徽章, 获得 17 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-10-30
个人主页被 1.8k 人浏览