GithubHelp home page GithubHelp logo

blog's Introduction

Hi there 👋

Zaynex's github stats

blog's People

Contributors

zaynex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

blog's Issues

记有赞前端笔试题

今天面试了有赞。说实话,很向往这种有一定体量的创业公司,业务发展得还不错。
第一题是站在产品设计师的角度将一张底部tab导航按钮组的位置都描述清楚,告诉开发应该如何进行。
这道题比较困难的是,要你在一张图的情况下,告诉开发人员如果是不同屏幕尺寸下的具体实现。这里我不多言论,只是谈谈核心的笔试题。

有赞前端接下来的笔试题绝对是每题都非常经典,而且很容易出错。

第二题:

function b(){
	console.log(myArr1)
	console.log(myArr2); 
}
function a(){
	var myArr1 = 1;
	var myArr2 = 2;
	b();
}
var myArr1 = 3;
a();
console.log(myArr1);

这道题考的地方一个是函数的上下文执行环境。
a()执行以后,内部定义了两个变量。接下来执行b()。这里的关键点是
b()的定义是在外部,而不是在a()内,b()中的变量会首先从b()内查找有无相应的变量,如果b()内未出现相应的变量则从外部(即全局环境)查找。
可能有些人会问b()中的myArr1会不会打印出 undefined? 因为函数声明是提前的。没错函数声明是提前的,在第一个阶段确实会做一个函数声明和定义变量等初始化工作,但是现在是第二个阶段,a()已经执行了。所以
b()中第一项打印出来的是
3;
那么第二项是不是为undefined呢?
答案是:

Uncaught ReferenceError myArr2 is not defined

别看在 a()函数内部定义了 myArr2,但它是局部变量,走出a()后就被内存回收了,而且
函数执行的上下文是在定义该函数的执行环境 而不是在执行时的上下文。
myArr2在 b()中未定义,在global中也未定义,当你执行console.log()的时候就出现刚才的错误了。
所以后面的console.log(myArr1)根本就不执行。

第三题:求在十秒内连续点击三下后弹出什么?

var btn = document.getElementById('btn');
function clickhandle(){
	console.log('click event');
}
function waitTenSeconds(){
	var ms = 10000 + (new Date()).getTime();
	console.log(Date.now())
	while(Date.now() < ms){};
	console.log('wait for me')
}
function over(){
	console.log('finish!!!');
}
btn.addEventListener('click', clickhandle)
waitTenSeconds();
over();

这道题拿到手当时也是不知道该从什么地方下手。后面仔细分析了下,发现自己没区分监听和触发事件的阶段。
其实后面面试官已经在暗示了,btn.addEvent..这段代码做了什么。我回答的是添加一个事件监听click事件。但是当时笔试的时候就以为直接就执行了。

所以后面会先执行waitTenSeconds()。
在这里自己遇到的问题是,这里的while(){}。如果while()的括号内判断为true,那就一直在循环内了啊。这就是个常识啊!那就一直在循环体内吧!等待10秒后,括号内的判断为false接下去执行console.log()。
后面的over函数就能顺利执行了。

由于面试题问的是10秒内。
10秒内还在while中循环呢,所以click事件根本就不会马上触发。等到10s后

wait for me
finish!!!
click event * 3

这题没答对,就是自己**。

第四题

for(var i = 1; i <= 3; i++) {
	setTimeout(function(){
		console.log(i)
	}, 0)
}

考察点是闭包。在setTimeout内的function中的i读取的是外部的变量,就是在for循环内定义的i.所以每次读取的都是同一个i,待循环体结束后的i是4.
输出 3个4
我的答案是3个3
i=3的时候还在循环体啊,最后的结果是 然后再加了1,那就是4...

第五题:
var People = function(){}
var Student = function(){}

请填写代码使得 Student 继承自 People。并写出相应的方法来验证 Student 是否继承自 People
我当时的写法是

var Student = function(){
People.apply(this)
}

这种方式我写的是构造函数继承,共享的是实例。但后面的方法验证就没什么思路了。

Student.prototype = new People();

这种方式是原型继承,重写了Student的原型对象。而原型对象里的 constructor 也被重写成了 People.
如果这时候采用

Student.prototype.constructor === ‘People’ 

判断似乎也可以,但其实很不靠谱,因为你可以随意改写 constructor。

  1. for in 遍历可枚举的属性,包括继承的属性
  2. hasOwnProperty 只检测对象的自身属性
  3. propertyIsEnumberable 只遍历可枚举的自有属性

如果要判断继承类型的话,看样子得用第一种方式,并且排除掉对象自身的属性。
用for in 和 hasOwnProperty 取反 可以判断,但无非就是判断原型链是否相同,如果两个构造函数拥有相同的原型链就说明他们已经实现继承关系了(前提在实现继承之后,后面的子构造函数没有拓展原型对象)

所以可以直接用 instanceof来判断。

var stu = new Student();
stu instanceof Student 
stu instanceof Pepple

或者

People.prototype.isPrototypeOf(stu)

后面顺便问到了
proto 和 prototype 的区别。
首先,在高程三种 proto__的定义是 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。 这个__proto__就是内部指针。
方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性__proto
,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

而在调用构造函数时会为实体添加一个指向最初原型的[[Prototype]]指针。
比如刚才的 stu
就是构造函数的实体,stu有一个内部隐形原型指针指向 Student.prototype。而由于 Student.prototype也是一个对象,它也有内部的__proto__ 内部指针指向 Object.prototype 。 Object.prototype也有一个 proto 指针最后指向的是 null。

后面两题算法题

写一个排序算法,已知数组是0-N,每个数唯一但是是乱序的。求最快的排序算法。
我写的是快速排序。但面试官问我还有没有更快的方法。

最后一题是数组去重,之前有写过相关的面试题。

【WIP】通俗理解 OT 和 CRDT 在协同编辑文档中的应用

在尝试学习一个新的解决方案,先自己思考如何解决这个问题。

独立思考带来的好处会帮助我们尽可能多得分析出潜在条件,深入理解问题的本质,以便我们更好得找到解决方案。

此外,如果通过自己的思考找到了解决方案,我们会对此类的问题加深印象,理解更深刻。

在说介绍本文冲突的解决方案(OT 或 CRDT)之前,我们先思考下自己该如何解决文本冲突。

基于索引的操作

假设以下场景,User1 在第 0 处插入了字符 C,User2 删除了第 0 个字符 H。

此时,User1 的文本是 CHAT, USER2 是 AT。

当我们把操作指令通过服务器传递后,User1 把第 0 个字符删除(即 HAT)。而 User2 则在第0个位置处加入C(即 CAT)

问题出现在哪里?

我们先来分析 User1,delete(0) 指令是相对于 User2 而言的,因为 H 在 User2 中确实是第 0 个,而由于 User1 由于插入了新的字符,导致 H 的出现在了第 1 个位置。所以我们需要修正操作指令,把 delete(0) 在服务器处理成 delete(1) 即可。

我们在服务端简单的做下处理。我们通过标记偏移量来获得正确的指令位置操作

// user1 的指令
const user1Operation = {type: 'insert', payload:{data: 'C', index: 0}}

// user2 的指令
const user2Operation = {type: 'delete', payload: {data: null, index: 0}}


// 标记偏移,
const offset = {
	start: 0,
	gap: 0,
}

// 先根据 User1 计算出 User2 需要更新的偏移量
// 针对 insert 的情况
if(user1Operation.type === 'insert') {
	const length = user1Operation.payload.data.length
	offset.start = user1Operation.payload.index
	offset.gap = length
}

// apply 偏移量,修正 user2Operation

let newUser2Operation = null
if(user2Operation.type === 'delete') {
	// 如果在偏移区内
	if(offset.start <= user2Operation.payload.index) {
		newUser2Operation = oprationTransform(user2Operation, offset.gap)
	} else {
                  newUser2Operation = user2Operation
         }
}

function oprationTransform(operation, gap) {
	const newOperation = {
		type: operation.type,
		payload: {
			data: operation.payload.data || null,
			index: operation.payload.index + gap
		}
	}
	return newOperation
}

// send operation to client

这样,我们就能修正 user2的指令了。

其实,以上的**就是 operation transform。即在服务端根据操作指令的执行生成新的操作指令下发给客户端。

这只是举了最简单的例子,有很多细节方面没有考虑。但没关系,我们的本意是对 OT 和 CRDT 的**入个门,后续的实现细节可以通过阅读相关的论文去自行探索。

我对这类问题的本质概括是,不同上下文环境中的执行差异问题。

这在 javaScript 中,会经常出现。 比如我们的 this 指向,时而指向一个特定的对象,时而又指向 window。 所以不在一个上下文环境内的操作必然会引发问题,而在协同文本编辑中,就会导致数据不一致的现象。

因为,User2 的 delete(0) 指令中的 0 (index) 是相对于文本内容 HAT (理解为当前 User2 的上下文)而言的,而原封不动得传递给 User1 时,此时 User1 的文本内容是 CHAT(User1 收到指令后的上下文)。 他们所依赖的上下文已经发生了变化,即 索引(index) 所指的上下文已经发生变化。

所以我们需要在服务器中执行已收到的 operation 修正上下文。再根据新的上下文得到新的 operation。

这里的上下文,我们通过偏移量来修正。

可以想象,假设用户很多的情况下,我们可能要处理非常多的 operation,处理起来会相当麻烦。

既然通过标记索引需要通过上下文一致来解决冲突以提高正确性。为什么我们不把索引转换成 uniqueId 呢? 这样就可以确保每个字符都是唯一的,插入和删除的时候就不会因为上下文不一致导致冲突。

比如,User1 和 User2 想要同时删除第二个字符 T。

通过unique id

因为 User1, User2 删除的字符的id 是唯一的,所以可以确保删除的位置是正确的。当收到第二个删除操作时,由于该 id 已经被删除,那就忽略改操作即可。

在更真实的环境下,我们需要确保字符是有序的。也就是说,我们既需要确保全局唯一,且每个 id 都能明确当前节点位置的顺序。

此外,我们必须确保一种生成id 的规则,即多端共用一套 id 的生成机制,即 User1 和 User2 任意一端用户在相同的位置生成的 id 是一样的,且能涵盖顺序的含义。不能是简单随机的采用 UUID。

我们简化下,让每个 id 用普通数字来代替,后续新增就是累加 即可。

Inserting a character in between two existing characters

C 对应 0,A 对应 1,T 对应 2。
那么我们插入的 H 应该对应什么呢?

这里,我们采用分数表示法。

分数表示法生成 id

如果你想深入了解这种分数分配策略,可以参考 LSEQ

我们用一个数组表示唯一的 id,也可以通过数组得到每个节点的顺序。

解决了插入字符生成 id 的问题,我们再来看看完整的例子。
Group 2

这种实现思路就是 CRDT (Conflict-free replicated data type)。我们这种实现方式并不需要让服务器处理复杂的指令转换。但带来的代价是,复杂度在客户端增加了。

关于 CRDT 还有其他的实现方式,不过只要我们可以抛开 OT 或者是 CRDT 的概念,尝试用自己的思考去解决实时协同编辑的解决方案,再去翻阅 OT / CRDT 的资料就好像故人相逢般地痛快。

CRDT 的具体实现,会在参考资料中有明确列出。

参考资料

  1. https://conclave-team.github.io/conclave-site/#what-is-a-text-editor

新的项目怎么用 react

为了踩坑,不得不采用 hooks 的方式。(虽然用了一段时间 hooks,也曾想再回到 class component)

全局状态管理: unstated-next
immutable 操作: immer

如果你也采用这种方式,那可能还需要 use-immer 来帮助

实例如下:

  1. Container —— 基于 React 的 context API 定义顶层数据结构和改变数据的方法。
// LessonContainer
import { useImmer } from 'use-immer'
import { createContainer } from 'unstated-next'

const defaultState = {
  now: [new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), new Date().getDay()],
  count: 0,
  student: [
    {
      name: 'zaynex',
      age: '10',
    },
  ],
  mockLessons: [
    {
      teacher: '小李',
      count: 6, // 子课程
      name: '课节名称',
      time: '02-07 周五 12:00(30分钟)'
    }
  ]
}

/**
 * 对外只暴露具体改变的方法和数据,所有的副作用交由内部处理
 * 通过 dispatch 挂载方法,统一所有的副作用
 */
function useLesson() {
  let [data, _setData] = useImmer(defaultState)
  const dispatch = {
    decrement: () =>
      _setData(draftState => {
        draftState.count--
      }),
    increment: () =>
      _setData(draftState => {
        draftState.count++
      }),
    updateAge: () =>
      _setData(draftState => {
        draftState.student[0].age++
      }),
  }
  return {
    data,
    dispatch,
  }
}

const LessonContainer = createContainer(useLesson)

export default LessonContainer
  1. 顶层注入,类似 Redux 的 Provider
import LessonContainer from './LessonContainer'

function App() {
return <LessonContainer.Provider>
            <DetailView />
          </LessonContainer.Provider>
)
  1. 业务层调用
function DetailView() {
  const lessonContainer = LessonContainer.useContainer()
  const mockLessons = lessonContainer.data.mockLessons
  return useMemo(
    () => (
      <div>
        {mockLessons.map(item => (
          <div>
            <span>{item.name}</span>
            <span>teacher: {item.teacher}</span>
          </div>
        ))}
      </div>
    ),
    [mockLessons],
  )
}

记一发新浪前端面试题


title: 记一发前端笔试题
date: 2016-11-09 22:28:25
tags:

HTML/CSS部分

display:nonevisibility:hidden

display:none:关闭一个元素的显示(对布局没有影响);其所有后代元素都也被会被关闭显示。文档渲染时,该元素如同不存在。
visibility:hidden:隐藏元素,但是其他元素的布局不改变,相当于此元素变成透明。要注意若将其子元素设为 visibility: visible,则该子元素依然可见。

注意两点区别:

  1. 文档渲染的区别,为none时不加载,我记得在某本书里有提到这种措施,可以减少重绘和回流;
  2. 前者不占据宽高,页面不显示,后者占据相应的宽高;
  3. 前者后代元素也跟随该元素消失,后者子代元素依然可见。

重绘与回流

为什么不none时不加载呢?

DOM Tree 和样式结构体组合后构建render tree, render tree类似于DOM tree,但区别很大,render tree能识别样式,render tree中每个NODE都有自己的style,而且 render tree不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。

参考页面重绘和回流以及优化

回流与重绘
  1. 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。
  2. 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
    注意:回流必将引起重绘,而重绘不一定会引起回流。
show me the code
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));

但是浏览器也不傻,他们会把这些操作放入到队列里,等到一定数量或者一定间隔再执行操作,就可以减少回流重绘。

行内元素和块级元素

行内元素:padding在四个方向都是有效果的。marginmargin-left,margin-right有效果,其他无效。widthheight都有无效,但line-height有效。
可以自己测试下。(发现网上有些博客说padding-bottom无效,不知道是怎么来的,如果你在自己的浏览器环境下测试得出不一样的结论欢迎补充!)

默认情况下,块级元素会新起一行。
块级元素定义:当元素的 CSS 属性 display 为 block, list-item 或 table 时,它是块级元素 block-level 。块级元素(比如

)视觉上呈现为块,竖直排列。

块级盒(块格式化上下文 block formatting context):每个块级元素至少生成一个块级盒,称为主要块级盒(principal block-level box)。一些元素,比如<li>,生成额外的盒来放置项目符号,不过多数元素只生成一个主要块级盒。 主要块级盒将包含后代元素生成的盒以及生成的内容。

这块还需要再仔细研究下。MDN

一般情况下,行内元素只能包含数据和其他行内元素。
而块级元素可以包含行内元素和其他块级元素。这种结构上的包含继承区别可以使块级元素创建比行内元素更”大型“的结构。

IEhack

.ie6_7_8{
    color:blue; /*所有浏览器*/
    color:red\9; /*IE8以及以下版本浏览器*/
    *color:green; /*IE7及其以下版本浏览器*/
    _color:purple; /*IE6浏览器*/
}

!important的IE hack手段

网上很多资料中常常把!important也作为一个hack手段,其实这是一个误区。!important常常被我们用来更改样式,而不是兼容hack。造成这个误区的原因是IE6在某些情况下不主动识别!important,以至于常常被人误用做识别IE6的hack。可是,大家注意一下,IE6只是在某些情况下不识别(ie6下,同一个大括号里对同一个样式属性定义,其中一个加important 则important标记是被忽略的,例:{background:red!important; background:green;} ie6下解释为背景色green,其它浏览器解释为背景色red;如果这同一个样式在不同大括号里定义,其中一个加important 则important发挥正常作用,例:div{background:red!important} div{background:green},这时所有浏览器统一解释为背景色red。)
参考地址

JS部分

typeof类型判断

  • "undefined"(随便给一个变量)
  • "object" (包括typeof Null)
  • "string"
  • "function"(比如alert)
  • "number"
  • "boolean"
  • "symbol"(ES6新增)

正则表达式

typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1
typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1

遇到的题:

typeof [1,2,"a"][1,2];

嗯,猜猜这是什么类型?
这题其实考的其实是逗号表达式,后面[1,2]取的是2,所以数组里第2个就是"a"("string"类型),当时以为是object,好伤感。

跨域的几种方式

jsonp

因为通过script标签引入的js是不受同源策略的限制的。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。
比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://damonare.cn/data.php, 那么a.html中的代码就可以这样

<script type="text/javascript">
    function dosomething(jsondata){
        //处理获得的json数据
    }
<\/script>
//因为是markdown写的,所以页面显示内容可能有部分差异
<script src=\"http://example.com/data.php?callback=dosomething"><\/script>

我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。

因为是当做一个js文件来引入的,所以http://damonare.cn/data.php 返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的(一定要和后端约定好哦):

<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

最终输出结果:

dosomething(['a','b','c']);
优缺点
  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
  • 它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

获取URL参数转换为对象

这里是《JavaScript高级程序设计三》里的原函数,注意使用的是 location.search返回的字符串是以?开头的,所以这里还需要加一个substring(1)表示从?之后取字符串

function getQueryStringArgs(){
    var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
        args = {},
    items = qs.length ? qs.split('&') : [], 
    item = null,
    name =  null,
    valu = null,
    i = 0,
    len = items.length;
    for(i = 0; i < len; i++){
        item = items[i].split("=");
        name = decodeURIComponent(item[0]);
        value = decodeURIComponent(item[1]);

        if(name.length){
            args[name] = value;
        }
    }
    return args;
}

当然,为了演示方便,我们可以直接传一个url参数到里面再做解析。

function parseQueryString(url)
    {
        var obj={};
        var keyvalue=[];
        var key=null,value=null;       
        var paraString=url.substring(url.lastIndexOf("?")+1,url.length).split("&");
        for(var i in paraString)
        {
            keyvalue = paraString[i].split("=");
            key = decodeURIComponent(keyvalue[0]);
            value = decodeURIComponent(keyvalue[1]);
            obj[key]=value;            
        }        
        return obj;
    }

var w3w = "http://blog.sina.com.cn/s/blog_5082f7b901015r2r.html?key1=1&value=2";
console.log(parseQueryString(w3w));

其实流程和上面的一样。
split() 方法通过把字符串分割成子字符串来把一个 String 对象分割成一个字符串数组。
substring() 返回字符串两个索引之间(或到字符串末尾)的子串。

  1. 取出?之后的查询字符串参数
  2. &为分割符划分为不同的字符串数组
  3. 再以=为分割符划分keyvalue
  4. 保存到新的对象中

随机排序

写一段代码,随机排序array([1,2,3,4,5,6,7,8,9])。

//我自己写的
function array(arr){
    var N = arr.length;
    var newArr = [];
    for(var i = 0; i < N; i++){
        if(Math.random() > 0.5){
            newArr.push(arr[i]);
        }else{
            newArr.unshift(arr[i]);
        }
    }
    return newArr;
}
//利用Math.random随机判断插入顺序

//再看看大师的作品(洗牌算法)
function knuth(arr){
    var N = arr.length;
    for(var i = 0; i < N; i++){
        //保证每个数都能与后面一个数字进行交换
        var r = i + Math.floor(Math.random()*(N - i));
        var temp = arr[r];
        arr[r] = arr[i];
        arr[i] = temp;
        console.log("r现在是 " + r);
    }
    return arr;
}
//还可以这样
str.sort(function(){
    return (0.5 - Math.random());
});

//ES6
function shuffle(arr){
    let n = arr.length,random;
    while(0!=n){
        random = (Math.random()*n--) >>> 0; //无符号右移运算符向下取整
        [arr[n], arr[random]] = [arr[random], arr[n]];
    }
    return arr;
}

实现Drag功能

HTML5自带拖拽,但这里主要用原生JS去实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        div,body{
            padding: 0;
            margin: 0;
        }
        #drag{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
        }
    </style>
</head>
<body>
    <div id="drag">
    </div>

    <script>
        window.onload = function(){
            drag.onmousedown = function(event){
                
                event = window.event || event;
                var drag = document.querySelector('#drag');
                var disX = event.clientX - drag.offsetLeft;
                var disY = event.clientY - drag.offsetTop;

                drag.onmousemove = function(event){
                    event = window.event || event;
                    drag.style.left = event.clientX - disX + "px";
                    drag.style.top = event.clientY - disY + "px";
                }
                drag.onmouseup = function(evebt){
                    drag.onmouseup = null;
                    drag.onmousemove = null;
                }
            };
        }
    </script>
</body>
</html>

这么写太low了。要参考高程的逼格去写啊。

<div id="drag" class="draggable">
    </div>
var Drag = function() {
            var dragging = null,
                disX = 0,
                disY = 0;
            function handlerEvent(event){
                event = window.event || event;
                var target = event.target || event.srcElement;

                switch(event.type){
                    case "mousedown": 
                        if(target.className.indexOf("draggable") > -1){
                            dragging = target;
                            disX = event.clientX - target.offsetLeft;
                            disY = event.clientY - target.offsetTop;
                        };
                        break;
                    case "mousemove":
                        if(dragging !== null){
                            dragging.style.left = event.clientX - disX + "px";
                            dragging.style.top = event.clientY - disY + "px";
                        }
                        break;
                    case "mouseup":
                        dragging = null;
                        break;
                }   
            };
            return {
                enable: function(){
                    document.addEventListener("mousedown",handlerEvent, false);
                    document.addEventListener("mousemove",handlerEvent, false);
                    document.addEventListener("mouseup",handlerEvent, false);   
                },
                disable: function(){
                    document.removeEventListener("mousedown",handlerEvent, false);
                    document.removeEventListener("mousemove",handlerEvent, false);
                    document.removeEventListener("mouseup",handlerEvent, false);
                }
            };
        }();
        Drag.enable();

运动框架

这道题主要是对一个物体进行移动,并且实现透明度相应的变化。
从x=100,y=100移动到x=10,y=10,移动过程中实现透明度等于x值。
最好是能封装一个比较通用的函数。手写代码还真是感觉不一样,说明自己掌握的还是很薄弱。

<div id="dialog" class="box"></div>
<button class='jclick'>啦啦啦</button>
//这段函数获取样式表和行内样式
function getStyle(obj, attr){
    if(obj.currentStyle){
        return obj.currentStyle[attr];
    }else {
        return getComputedStyle(obj,false)[attr];
    }
}
function startMove(obj, json, fn){
    clearInterval(obj.timer);
    obj.timer = setInterval(function(){
        var bStop = true;
        for(var attr in json){
            var iCur = 0;
            if(attr === 'opacity'){
                iCur = parseInt(parseFloat(getStyle(obj,attr))*100);
            }else {
                iCur = parseInt(getStyle(obj, attr));
            }

            var iSpeed = (json[attr] - iCur)/8;
            iSpeed = iSpeed > 0 ? Math.ceil(iSpeed): Math.floor(iSpeed);

            if(iCur != json[attr]){
                bStop = false;
            }
            if(attr == 'opacity'){
                obj.style.filter = 'alpha(opacity:' + (iCur+iSpeed)+')';
                obj.style.opacity = (iCur + iSpeed)/ 100;
            }else {
                obj.style[attr] = iCur + iSpeed + "px";
            }
        }

        if(bStop){
            clearInterval(obj.timer);
            if(fn){
                fn();
            }
        }
    },30)
}
var dialog = document.getElementById("dialog");
var jclick = document.querySelector(".jclick");
//这段函数严重的问题,地狱回调啊
jclick.onclick = function(){
        startMove(dialog, {left:10,top:10,opacity:10},function(){
            startMove(dialog, {left:100,top:100,opacity:100},function(){
                startMove(dialog, {left:1220,top:100,opacity:100},function(){
                    startMove(dialog, {left:120,top:100,opacity:10});
                })
            });
        });
    }

所以还是得改改代码。说实话,面向对象虽然我都理解,但就是写不出,不知道是咋回事,所以也是参考了别人写的代码。

var Animate = function(oElement, options, callback){this.init.apply(this, arguments)};
    Animate.prototype = {
        init: function(oElement, options, callback){
            var oThis = this;
            this.options = options;
            this.callback = callback;
            this.oElement = typeof oElement === 'string'?document.getElementById(oElement) : oElement;
            clearInterval(this.timer);
            this.timer = setInterval(function(){
                oThis.doMove();
            }, 30)
        },
        css: function(attr, value){
            if(arguments.length == 1){
                return parseFloat(this.oElement.currentStyle? this.oElement.currentStyle[attr]: getComputedStyle(this.oElement, null)[attr]);
            }else if(arguments.length === 2){
                attr === 'opacity' ? (this.oElement.style.filter ==  'alpha(opacity=' + value +')',this.oElement.style.opacity =value/100) : this.oElement.style[attr] = value + "px";
            }
        },
        doMove: function(){
            var opt = this.options;
            var bComplete = true;
            for(var p in opt){
                var iCur = p === 'opacity' ? parseInt(this.css(p).toFixed(2) * 100) : this.css(p);
                var iSpeed = (opt[p] - iCur) / 8;
                iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
                opt[p] == iCur || (bComplete = false, this.css(p, iCur + iSpeed));
            }
            bComplete && (clearInterval(this.timer), this.callback && this.callback.call(this))
        }
    };
    window.onload = function(){
        var dialog = document.getElementById("dialog");
        var jclick = document.querySelector(".jclick");
        var aData = [
            {left:10,opacity:10},
            {left:1000,opacity:100,top:10},
        ];
        jclick.onclick = function(){

            var i = -1;
            function begin(){
                i< aData.length ? i++ : i--;
                var obj = new Animate(dialog, aData[i], begin);
                if(i == aData.length || i < 0) {
                    clearInterval(obj.timer);
                    return;
                }
            }
            begin();
        }
    }

正则表达式

写一个正则匹配表达式匹配包含数字和字母且在8-16位。其实就是匹配密码的合法输入。

var reg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}/;

这段正则的含义:
^ 匹配开头
$ 匹配到结尾
[0-9a-zA-Z] 匹配0-9,a-z,A-Z中任意字符
{8, 16} 至少8位,至多16位

  • 至少出现一次
    (?!...) 负向零宽断言(真难理解),否定顺序环视,也就是说右边必须在[0-9]后必须出现非[0-9]的结构

上面判断的条件拆分为

  1. 必须为数字和字母
  2. 不能全是数字 (第一个负向零宽断言)
  3. 不能全是字母 (第二个负向零宽断言)

小结

通过这次笔试,发现自己在基础的JS代码上还是不够熟练,特别是写段代码的时候。想要写出一手健壮的JS代码还是很难的。
通过这次笔试还需要

  • 把MDN的JavaScript面向对象再过一遍
  • 再看看设计模式,据说JavaScript设计模式与开发实践不错
  • 从小demo入手,用面向对象改写。
  • 《JavaScript高级程序设计三》确实是好书。面试题都是现成的!是该好好看下,Ajax这块尤为重要
  • webkit技术内幕

错过了秋招,除了这次笔试题之外,也了解到一些加分项:

  • Node.js
  • MV*框架

要学的太多了,加油吧,小伙子。机会很多,可需要能力才能驾驭机遇。

sentry-cli: electron crash 监控的最后一步

Sentry 是非常优秀的异常监控平台,支持多种语言和平台(比如 ReactNative,Electron)。并且是开源的,开发人员可以将 Sentry 部署在自己的云服务器上。

文本的重点不是如何接入 Sentry 到 Electron,而是如何将 Sentry 捕获到的 Electron crash 解析出来。
请参考 Sentry 接入 Electron

当 Sentry 捕获到 Electron 的崩溃时,会将 Electron 的 minidump 文件上传到指定的 Sentry 服务器,
其配置参考如下:

import * as Sentry from '@sentry/electron';
const { crashReporter } = require('electron');
crashReporter.start({
    companyName: 'xxx',
    productName: 'xxx',
    ignoreSystemCrashHandler: true,
    submitURL: 'https://sentry.io/api/9/minidump/?sentry_key=xxxx',
  });

如果是自己搭建的服务器,那么 submitURL 就是自己的服务域名。

当我们制造 Electron 崩溃时,我们在 sentry 平台上会看到类似的监控崩溃信息。

image

虽然能在平台上收到崩溃的监控,上传的 minidump 文件是汇编级别的代码,我们还需要对应的 symbol 文件来解析这些调用栈,使其还原至我们在项目中写的具体函数名。

上传 Debug Information Files

Debug information files are used to convert addresses and minified function names from native crash reports into function names and locations.

调试信息文件用于将地址和简化的函数名从本机崩溃报告转换为函数名和位置。

sentry-cli

我们需要 sentry-cli 来帮助我们查找对应的 symbol 文件。

image

我们根据 uuid 在本地中查找所需的文件

npm install -g @sentry/cli

sentry-cli 有非常完善的文档,这里只介绍如何在本地上传 symbol 文件

首先利用 sentry-cli 登录,url 默认是 sentry.io,如果是自己的服务,那么你需要按照如下操作进行登录。

sentry-cli --url xxxxx login

按照指示登录后,我们需要获取相应的 auth-token。

校验完毕后,我们可以通过 uuid 查找本地的 symbol 文件

sentry-cli difutil find uuid

找到本地的 symbol 文件后,我们需要上传到服务器

sentry-cli --auth-token xxxx upload-dif -o org -p project xxxx(localpath)

如果是 sentry 自己搭建的服务,在上传可能会遇到 413 实体过大的问题,需要将 nginx 配置设置为
client_max_body_size 20M

上传之后,我们就能解析具体的异常信息了。
image

rollup 插件机制的探索

在使用 rollup 的时候发现一个很有意思的问题:
计算体积的插件总是没办法正确计算出文件体积。(rollup-plugin-sizes)

虽然没有仔细去研究它的 API,但是这里就会存在一个关于插件的问题:
插件与插件之间是独立的,可如果下一个插件依赖了上一个插件的执行结果,那么应该通过怎样的方式使其正确运行呢?
比如,我在 rollup 中使用了 terser 插件用来减少包体积,然后再使用 sizes(或其他能计算出文件体积的插件)插件来计算文件体积。
由于 terser 对文件体积有副作用,那么 sizes 看起来似乎必须得依赖上一个插件 terser 执行结果之后继续调用才会得到最终正确的结果。
这也就意味着,如果先用 terser 再用 sizes和先用 sizes 再用 terser 的话,sizes 计算出的体积大小是不一样的。(后者相对体积更大)

然而,实际测试出来的结果这两种方式的产生的体积大小都是一样的。

这可以验证插件与插件之间是独立的。
那么,究竟怎样我才能真正得到正确的文件体积大小呢?

接下来,确实是需要深入了解 rollup 内这两个插件分别做了哪些事情。
插件本质上就是在一个程序的各个实际提供类型的事件机制(类似 eventEmitter),并且在各个特定时机将当前的程序状态作为事件的参数触发,外层通过对应的执行时机和获取的程序状态进行一些业务逻辑操作,这便是整个插件机制的核心流程。

  • rollup 插件机制
  • sizes terser 分别在什么时机执行了什么操作
  • 如何真正计算出打包文件的体积

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.