整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何训练 JS 才能达到自己写框架的水平?JavaScript核心进阶攻略

为前端的一枚小菜鸟也憧憬着自己有朝一日能写出自己的框架,哪怕是流利理解三大框架的源码也好,当然这没有关公面试耍大刀的意味╮(‵▽′)╭

一、找到问题,思考解决思路,参考同类或者类比同类框架实现方式,抽象自己的方案,不断的优化重构去写就可以了。

框架的目的解决一系列特定的问题,复刻不是水平,创新才是。

二、框架是解决方案的代码实现。要写出优秀的框架,首先要善于发现问题(当然也要善于发现机会,总是亦步亦趋解决别人已经解决过的问题是不被认可的),其次才是用精巧的思路进行代码实现。

三、现在的前端mvvm(react、vue,)框架基本都是由以下几部分组成

  • 虚拟dom
  • diff算法(本质是如何找到一个对象树的差异并更新,当然为了避免一股脑diff可能造成页面卡顿,可以设计成时间切片的形式)
  • 如何设计组件化(函数组件、类组件)
  • 数据更新机制(vue是数据劫持、react是调用setState)
  • 全局状态管理(vuex、redux...)
  • 路由设计
  • 逻辑复用机制(hooks、Function based )
  • 模板语法的选择(jsx or template)
  • 实现模板解析

综上,再复杂的框架都是由各个小的技术点累计而成的,那么将以上每一个技术点都能钻研透彻,并能够将其灵活的组合起来的那么你离完成一个框架就不远了。

当然,能写框架到写出好的框架再到能够投入到生产环境还有相当长的一段路要走,大部分人也就停留到能写框架的地步吧。

JavaScript进阶攻略

高级前端进阶导学

前端高级课程体系梳理

高效源码学习技巧

设计模式-jQuery源码分析

整体架构-jQuery核心功能函数揭秘

Sizzle-选择器

回调对象设计

Callbacks入门

Callbacks原理分析

异步回调解决方案

延时对象-Deferred概念

延时对象-Deferred源码解析

事件绑定

事件绑定-bind/delegate/on

事件绑定-体系结构/委托设计

事件绑定-自定义设计/模拟事件

DOM操作

DOM操作核心

DOM操作方法-html.text.css

UNDERSCORE.JS源码分析

设计篇

函数式编程思想概述

underscors整体机构&面向对象

undefned的处理/函数式编程回调iteratee设计

rest参数/underscors创建对象方式

辅助功能篇

数组篇

数组定位&摊平数组

数组运算uniq去重函数 原生对象扩展

函数篇

对象篇

模块化编程-自研模块加载器

大家可以关注我,私信回复“前端资源”获取JavaScript进阶资料

用TypeScript

这是你可以通过不编写JS来改善JS的第一件事。对于初学者来说,TypeScript(TS)是JS的“已编译”的超集(在JS中运行的所有内容都可以在TS中运行)。TS在原生JS体验的基础上增加了一个全面的类型静态检查系统。长期以来,整个生态系统中对TS的支持都不够好,所以以前我不建议大家使用。

幸运的是,那些日子已经过去很久了,大多数框架都开箱即用地支持TS。现在我们都在讨论了什么是TS,下面我们来谈谈为什么要使用它。

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

TypeScript可以保证类型安全

类型安全性可以确保在编译阶段验证整个代码段中所有类型是否都以正确方式使用。换句话说,如果您创建一个数字参数的函数foo:

function foo(someNum: number): number {
 return someNum + 5;
}

函数foo只接受number类型的参数

正确的例子
console.log(foo(2)); // 打印 "7"
错误的例子
console.log(foo("two")); // 不正确的TS代码

除了在代码中添加类型检查的开销之外,类型安全性的负面影响几乎为零。另一方面,它带来了极大的好处,类型安全性提供了针对常见错误的额外保护,这对于像JS这样有缺陷的语言来说是一种幸运。

TypeScript让重构大型项目成为可能



重构大型JS应用程序可能是一场噩梦。重构JS的最大苦恼是由于它不强制检查函数参数签名。这意味着绝对不能滥用JS函数,假设,如果我有一个函数myAPI提供给1000个不同的调用方使用:

function myAPI(someNum, someString) {
 if (someNum > 0) {
 leakCredentials();
 } else {
 console.log(someString);
 }
}

当我改变了一下这个函数签名:

function myAPI(someString, someNum) {
 if (someNum > 0) {
 leakCredentials();
 } else {
 console.log(someString);
 }
}

我必须100%确定使用此功能的每个地方(成千上万个地方),我都正确地更新了用法。如果我错过一个,系统就会崩溃。

如果使用TS的话:

修改前
function myAPITS(someNum: number, someString: string) { ... }
修改后
function myAPITS(someString: string, someNum: number) { ... }

如您所见,myAPITS函数与JavaScript对应函数进行了相同的更改。但是,此代码不是生成有效的JavaScript,而是导致生成无效的TypeScript,因为它所使用的成千上万个位置现在出现了错误的提示。而且由于我们前面讨论的类型安全性,这数千种情况将阻止编译,这样你可以从容进行修改重构。



TypeScript使得团队架构沟通更为顺畅

正确配置TS后,如果不先定义接口和类就很难编写代码。这提供了一种共享简洁,可交流的体系结构建议的方法。在TS之前,存在解决此问题的其他解决方案,但是没有一个解决方案可以在不使您做额外工作的情况下自动解决。例如,如果我想为后端提出新的请求类型,则可以使用TS将以下内容发送给队友。

interface BasicRequest {
 body: Buffer;
 headers: { [header: string]: string | string[] | undefined; };
 secret: Shhh;
}

我必须已经编写代码,但是现在我可以共享我的增量进度并获得反馈,而无需花费更多时间。我不知道TS本质上是否比JS更不容易出错。我坚信,强制开发人员首先定义接口和API会产生更好的代码。总体而言,TS已发展成为比原生JS的更为成熟且更可预测的替代方案。开发人员绝对仍然需要熟练使用原生JS,但是我最近开始的大多数新项目从一开始就是TS。

使用现代语法

JavaScript是世界上最流行(即使不是最多)的编程语言之一。您可能希望到现在为止,大多数人已经知道了数以亿计的人使用20多年的语言,但实际上相反。最近,对JS进行了许多更改和添加(是的,我知道,从技术上讲ECMAScript),从根本上改变了开发人员的体验。作为最近两年才开始写JS的人,我的优势是没有偏见或期望。这就导致了关于语言的哪些功能要使用和哪些要避免的更加务实的选择。

async和await



在长时间内,异步的基于事件驱动的回调方式是JS开发中常规做法:

传统回调

makeHttpRequest('google.com', function (err, result) {
 if (err) {
 console.log('Oh boy, an error');
 } else {
 console.log(result);
 }
});

我不会花时间解释上述问题的原因,为了解决回调问题,在JS中添加了一个新概念Promises。使用Promise,您可以编写异步逻辑,同时避免以前困扰基于回调的代码的嵌套问题。

Promises

makeHttpRequest('google.com').then(function (result) {
 console.log(result);
}).catch(function (err) {
 console.log('Oh boy, an error');
});

与回调相比,Promises的最大优点是可读性和可链式调用。尽管Promise很棒,但他们仍有一些不足之处。对于许多人来说,Promise的体验仍然让人联想到回调。具体来说,开发人员正在寻求Promise模型的替代方案。为了解决这个问题,ECMAScript委员会决定添加一种新的利用promise,async和await的方法:

try {
 const result = await makeHttpRequest('google.com');
 console.log(result);
} catch (err) {
 console.log('Oh boy, an error');
}

一个警告是,你await的任何内容都必须声明为async,上一示例中makeHttpRequest的必须定义成

async function makeHttpRequest(url) {
 // ...
}

由于异步功能实际上只是精美的Promise包装器,因此也可以直接等待Promise。这也意味着async/await代码和Promise代码在功能上是等效的。因此,可以在不感到内疚的情况下随意使用async/await。

let和const

对于JS的大部分存在,只有一个可变范围限定符:var。var在处理范围方面有一些非常独特/有趣的规则。var的作用域范围不一致且令人困惑,并且导致了意外的行为,因此导致整个JS生命周期中的错误。但是从ES6开始,还有var的替代方法:const和let。几乎不再需要使用var了,任何使用var的逻辑都可以始终转换为等效的const和let的代码。

至于何时使用const和let,我总是从声明所有const开始。const的限制要严格得多且“固定不变”,这通常会导致更好的代码。没有大量的“真实场景”需要使用let,我会说我用let声明的1/20变量,其余的都是const。

我说const是“不变的”,因为它不能以与C / C++中的const相同的方式工作。const对JavaScript运行时的含义是,对该const变量的引用永远不会改变。这并不意味着存储在该引用上的内容将永远不会改变。对于原始类型(数字,布尔值等),const确实会转换为不变性(因为它是单个内存地址)。但是对于所有对象(类,数组,字典),const不能保证不变性。

箭头=>函数



箭头函数是在JS中声明匿名函数的简洁方式,匿名函数描述未明确命名的函数。通常,匿名函数作为回调或事件挂钩传递。

原生匿名函数

someMethod(1, function () { // has no name
 console.log('called');
});

在大多数情况下,这种风格没有任何“错误”。原生匿名函数在作用域方面表现出“有趣”,这可能会(或已经)导致许多意外错误。借助箭头功能,我们不必再为此担心。这是使用箭头功能实现的相同代码:

someMethod(1, () => { // has no name
 console.log('called');
});

除了更加简洁之外,箭头功能还具有更多实用的作用域行为。箭头函数从定义它们的范围继承作用域。在某些情况下,箭头函数功能可能更简洁:

const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
const merged = Object.assign({}, obj1, obj2);
console.log(merged) // prints { dog: 'woof', cat: 'meow' }

我想说清楚,这不是可变的情况;仍然存在原生匿名函数(特别是类方法)的有效用例。话虽这么说,但我发现,如果始终使用默认箭头功能,则与默认使用原始匿名函数相比,您进行的调试要少得多。

展开操作符 ...



提取一个对象的键/值对并将它们添加为另一个对象的子代是一种非常常见的事情。从历史上看,有几种方法可以做到这一点,但是所有这些方法都相当笨拙:

const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
const merged = Object.assign({}, obj1, obj2);
console.log(merged) // prints { dog: 'woof', cat: 'meow' }

这种模式非常普遍,因此上述方法很快变得乏味。多亏了展开操作符,您无需再使用它:

const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' }

而且更棒的是它也能和数组搭配使用:

const name = 'Ryland';
const helloString =
`Hello
 ${name}`;

这个也许不是最重要的JS新特性,但它是我最喜欢的一个。

模板字符串



字符串是最常见的编程结构之一。这就是为什么它如此尴尬,以至于在许多语言中仍然很少支持原生声明字符串。长期以来,JS属于“坡脚的字符串”家族。但是,模板字符串的添加使JS成为了自己的类别。模板字符串本身可以方便地解决编写字符串的两个最大问题:添加动态内容和编写多行的字符串:

const name = 'Ryland';
const helloString =
`Hello
 ${name}`;



对象解构

对象解构是一种从数据集合(对象,数组等)中提取值的方法,而无需遍历数据或显式访问其键值:

老方法

[a, b] = [10, 20];

console.log(a); // prints 10

对象解构

function animalParty(dogSound, catSound) {}

const myDict = {
 dog: 'woof',
 cat: 'meow',
};

const { dog, cat } = myDict;
animalParty(dog, cat);

你还可以在函数参数签名中进行解构:

function animalParty({ dog, cat }) {}

const myDict = {
 dog: 'woof',
 cat: 'meow',
};

animalParty(myDict);

解构也能和数组配合使用:

[a, b] = [10, 20];

console.log(a); // prints 10

永远默认你的系统是分布式的

编写并行化的应用程序时,您的目标是一次优化您的工作量。如果您有四个可用的核心,而您的代码只能使用一个核心,那么您的潜力就浪费了75%。这意味着阻塞,同步操作是并行计算的最终敌人。但是考虑到JS是单线程语言,事情就不会在多个内核上运行。那有什么意义呢?

JS是单线程的,但不是单文件的(如学校里的代码)。即使不是并行的,它仍然是并发的。发送HTTP请求可能需要几秒钟甚至几分钟,因此,如果JS停止执行代码,直到请求返回响应,该语言将无法使用。

JavaScript通过事件循环解决了这个问题。事件循环遍历已注册的事件,并根据内部调度/优先级逻辑执行它们。这使发送数千个同时的HTTP请求或同时从磁盘读取多个文件成为可能。要注意的是:只有您使用正确的功能,JavaScript才能利用此功能。

最简单的例子是for循环:

const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

async function testCall() {
 // do async stuff here
}

for (let i = 0; i < 10; i += 1) {
 testCall();
}

原始for循环是编程中存在的最少并行构造之一。在我的上一份工作中,我带领一个团队花费了几个月的时间尝试将传统的R lang for循环转换为自动并行代码。从根本上讲,这是一个不可能解决的问题,只有等待深度学习的改善才能解决。并行化for循环的困难源于一些有问题的模式。顺序for循环非常少见,但仅凭它们就无法保证for循环可分解性:

const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
items.map(async (item) => {
 // do async stuff here
});

如果按顺序执行该代码,并且每次迭代执行,则只会产生预期的结果。如果尝试一次执行多个迭代,则处理器可能会基于不正确的值错误的分支,这会使结果无效。如果这是C代码,我们将有不同的讨论,因为用法不同,并且编译器可以使用循环完成很多技巧。在JavaScript中,仅在绝对必要时才应使用传统的for循环。否则,请使用以下构造:

const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
 const allResults = await Promise.all(items.map(async (item) => {
 // do async stuff here
 }));

我将解释为什么这些是对传统for循环的改进。诸如map之类的结构无需按顺序执行每个迭代,而是采用所有元素并将它们作为单独的事件提交给用户定义的map函数。在大多数情况下,各个迭代之间没有固有的联系或依赖性,因此它们可以并行运行。这并不是说您无法使用for循环来完成同一件事。实际上,它看起来像这样:

const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

async function testCall() {
 // do async stuff here
}

for (let i = 0; i < 10; i += 1) {
 testCall();
}

如您所见,for循环并不能阻止我以正确的方式进行操作,但是它肯定也不会使其变得更容易。与map版本比较:

var fooVar = 3; // airbnb rules forebid "var"

如您所见,map就可以了。如果要在所有单个异步操作完成之前进行阻塞,则map的优势将变得更加明显。使用for循环代码,您将需要自己管理一个数组。这是map版本:

const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
 const allResults = await Promise.all(items.map(async (item) => {
 // do async stuff here
 }));

真的很容易,在许多情况下,与map或forEach相比,for循环的性能相同(或可能更高)。我仍然认为,现在丢失几个周期值得使用定义良好的API的优点。这样,将来对该数据访问模式实现的任何改进都会使您的代码受益。for循环过于笼统,无法对同一模式进行有意义的优化。

在map和forEach之外还有其他有效的异步选项,例如for-await-of.

静态检查你的代码

没有一致风格(外观)的代码很难阅读和理解。因此,以任何语言编写高端代码的一个关键方面是保持一致且明智的风格。由于JS生态系统的广度,对于lint风格和style细节有很多选择。我要强调的是,与使用特制的lint/style相比,使用lint并强制使用一种style(其中的任何一种)更为重要。归根结底,没有人会完全按照我的意愿来编写代码,因此对此进行优化是不切实际的目标。

我看到很多人问他们应该使用eslint还是prettier。对我来说,它们的用途非常不同,因此应结合使用。ESlint大部分时间都是传统的linter。它将确定与style无关,而与正确性有关的代码问题。例如,我将eslint与Airbnb规则结合使用。使用该配置,以下代码将强制linter失败:

var fooVar = 3; // airbnb rules forebid "var"

很明显,eslint如何为您的开发周期增加价值。从本质上讲,它可以确保您遵循关于什么是好习惯的准则。因此,lint在本质上是强制执行的。与所有意见一样,将其与盐一同食用。Linter可能是错误的。

Prettier是代码格式化程序。它较少关注正确性,而更担心均匀性和一致性。Prettier不会抱怨使用var,但是它将自动对齐代码中的所有方括号。在我的个人开发过程中,在将代码推送到Git之前,我总是使用Prettier来美化代码格式。在许多情况下,让Prettier在每次对存储库的每次提交时自动运行甚至是有意义的。这样可以确保所有进入源代码管理的代码都具有一致的样式和结构。

测试你的代码

编写测试是改善您编写的JS代码的一种间接但非常有效的方法。我建议你需要适应使用各种各样的测试工具。您的测试需求会有所不同,并且没有一种工具可以处理所有问题。JS生态系统中有大量完善的测试工具,因此选择工具主要取决于个人喜好。与往常一样,请自己考虑。

Test Driver – Ava

Test drivers是一种简单的框架,它可以提供高级别的结构和实用性。它们通常与其他特定的测试工具结合使用,具体取决于您的测试需求。

Ava是表达力和简洁性的完美平衡。Ava的并行,隔离的体系结构是我大部分爱的源泉。运行速度更快的测试可节省开发人员时间和公司资金。Ava拥有大量不错的功能,例如内置断言,同时设法将其保持在最小限度。

其他选项: Jest, Mocha, Jasmine



Mocks – Nock

HTTP模拟是伪造http请求过程的一部分的过程,因此测试人员可以注入自定义逻辑来模拟服务器行为。

Http模拟可能是一个真正的痛苦,但是Nock减轻了痛苦。Nock直接覆盖nodejs的内置请求,并拦截传出的http请求。这又使您可以完全控制响应。

其他选项: 我也不知道其他

测试自动化 – Selenium

我对推荐Selenium感到喜忧参半。由于它是网络自动化的最受欢迎的选项,因此它拥有庞大的社区和在线资源集。不幸的是,学习曲线相当陡峭,并且它依赖于许多实际使用的外部库。话虽如此,它是唯一真正的免费选项,因此,除非您要进行企业级的网络自动化,否则Selenium会是一个好选择。

其他选项: Cypress, PhantomJS

永无终点的旅途

与大多数事情一样,编写更好的JavaScript是一个持续的过程。代码始终可以变得更加简洁,一直在添加新功能,并且永远没有足够的测试。看起来似乎势不可挡,但是由于有很多潜在的方面需要改进,因此您可以按照自己的步调真正进步。一次接着一步,在不知不觉中,您将成为JavaScript的王者。

原文:https://stackoverflow.blog/2019/09/12/practical-ways-to-write-better-javascript/

作者:Ryland Goldstein

翻译:奶爸码农

局元素

语义化是html5的一个特色,他不仅能规范代码,清晰结构,更好地开发和维护,而且还有利于SEO。当然你在实际开发中可以不用,但衡量一个人专不专业就在于一些小细节。


现如今是2021年,距离发布HTML5的2014年已有7年之久!当初对于HTML5标准规范,很多人张口就来说,浏览器不兼容、不支持,国情还是用IE的多等等。反正就是拒绝拥抱新技术和新标准。


不管这些快被优化下岗的老鸟愿不愿意,时代还是要进步的。智能手机的快速发展,win10、MAC等硬软件市场占有率提高,毕业生扎堆涌入IT行业等等,直接、间接推动HTML5的普及率。现如今还有哪个浏览器不支持html5,甚至不支持html5都不是这个时代的浏览器。


所以如果你是外行刚进入it前端行业,尤其是单单看了一些老到掉牙的教学视频,当面试官问你关于一些html5的问题,千万不要在张口就说html5是新技术。。。 毕竟这已是个标准规范,不是加分项,而是必填项,还不能错的那种。


言归正传,本篇介绍HTML5里的一大特色:语义化。相信面试时肯定也被问到语义化理解。


语义学是研究语言中单词和短语的含义。语义化呢?就是用合理、正确的标签来展示内容。更具体一点,就是使用新增的语义标签。在HTML5以前,我们有使用过标题标签(h1~h6)、表格标签(table)、表单标签(form)等。这些标签都带有很明显的意义,无法乱用。


然而最基础、最重要的html布局元素却是被人忽视,绝大部分都是使用非语义元素(div和span)来布局,对其内容意义一无所知。代码一多,实在让人看得头疼。


问你眼花没有,通篇div标签


倘若是以下的布局,相信不是专业的程序员,有点基础的小学生都能看得懂!

Html5布局标签


<header>

<header>元素描述了文档的头部区域,相当于一篇文章的头部。

主要是用在网页的头部,或者某个部分的头部。

在一个文档中,您可以定义多个<header>元素。

header


<main>

<main>元素描述了文档的主体区域,相当于一篇文章的中间主体。

主要是用在网页的主体部分。

该内容在文档中应当是独一无二的,不包含任何在文档中重复的内容,建议只出现一次。

main


<footer>

<footer>元素描述了文档的尾部区域,相当于一篇文章的尾部。

主要是用在网页的末尾部分,或者某个部分的尾部。常见于版权信息、友情链接等等。

在一个文档中,您可以定义多个<header>元素。

footer


<nav>

<nav>元素描述了多个超链接的区域。

主要是作用于菜单栏、导航栏等多个超链接集合。

nav


<article>

<article>元素描述了相对比较独立、完整的的内容区块

主要是作用于定义独立模块,例如一个网站有娱乐、新闻、动漫三个区块,就用<article>定义3个模块。

article


<section>

<section>元素描述了一个区域或者章节

主要是作用于<article>的子模块,<article>定义大模块,<section>则是填充里面的子模块;亦可以单独做一个小模块。

section


<aside>

<aside>元素描述了和页面内容几乎无关的部分,可以被单独的拆分出来而不会影响整体。

主要是作用于侧边栏或嵌入内容(广告之类)。

aside


总结