前端越往深度发展,越需要了解底层实现原理,借鉴他们的思想去实现业务需求,去实现性能优化,而且去学习新的东西时候也是在这些知识基础上去学习~ 事半功倍
面试什么样的岗位就应该关注什么样的题目,这个系列我会一直写下去,如果觉得不错可以点个在看,关注下公众号~
面试题越往后会越难,React-native,Taro开发小程序,Electron,Node.js源码...都会有
一、为什么会出现模块化,以及各种模块化标准
前端模块化出现是必定的,一个很复杂的应用不可能所有的内容都在一个文件中~
模块化的历程:
-> 传统的命令空间
代码实现:
index.js
(function(w){
w.a = 1
})(window)
原理:在window
这个全局对象下面,挂载属性,那么全局都可以拿到这个属性的值,原则上一个js
文件作为一个模块,就是一个IIFE
函数
-> require.js
基于AMD
规范
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块。
代码实现:
// 简单的对象定义
define({
color: "black",
size: "unisize"
});
// 当你需要一些逻辑来做准备工作时可以这样定义:
define(function () {
//这里可以做一些准备工作
return {
color: "black",
size: "unisize"
}
});
// 依赖于某些模块来定义属于你自己的模块
define(["./cart", "./inventory"], function(cart, inventory) {
//通过返回一个对象来定义你自己的模块
return {
color: "blue",
size: "large",
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
}
);
-> sea.js
基于CMD
规范
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
代码实现:
define(function(require, exports, module) {
var $ = require('jquery');
exports.sayHello = function() {
$('#hello').toggle('slow');
};
});
seajs.config({
alias: {
'jquery': 'http://modules.seajs.org/jquery/1.7.2/jquery.js'
}
});
seajs.use(['./hello', 'jquery'], function(hello, $) {
$('#beautiful-sea').click(hello.sayHello);
});
原理:顶部引入sea.js
的源码文件,运行时转换代码,一开始指定入口文件,根据入口文件定义的数组(或者引入的依赖),去继续寻找对应的依赖。
-> commonJs
Node.js
原生环境支持commonJs
模块化规范
先简单实现一个require
:
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
// 模块代码在这里,在这个例子中,我们定义了一个函数
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
// 当代码运行到这里时,exports 不再是 module.exports 的引用,并且当前的
// module 仍旧会导出一个空对象(就像上面声明的默认对象那样)
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
// 当代码运行到这时,当前 module 会导出 someFunc 而不是默认的对象
})(module, module.exports);
return module.exports;
}
require
就相当于把被引用的 module
拷贝了一份到当前 module
中
export
和module.exports
暴露出来接口
export
和module.exports
的区别:
export 是 module.exports 的引用。作为一个引用,如果我们修改它的值,实际上修改的是它对应的引用对象的值。
commonJS
用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
一句话简单总结就是,exports
-> {} <- module.exports
同时指向一个对象
-> ES6
模块化
ES6
模块化规范原生的浏览器环境和Node.js
环境都不识别,但是要使用,就必须要使用babel
编译成浏览器或者Node.js
可以识别的代码,为了节省时间,那么就会出现自动化一键打包编译代码的工具, - webpack
.
ES6
最牛逼的地方,不仅支持了静态校验,可以同步异步加载,而且统一了前后端的模块化规范,Node
和传统前端,都可以用这套规范。
ES6
模块与CommonJS
模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用 (首次require不同路径的文件,会在require.cache中保存一份缓存,下次读取的时候就直接从缓存中读取了)
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
这也是为什么TypeScript
支持静态类型检查的原因 因为他使用的是ES6
模块化方案
特别提示:现在Node
也可以用ES6
模块化方案的 用experimental
即可
看看commonJs
的
index.js
const a = require('./test1.js');
const func = require('./test2');
a.a = 2;
console.log(a.a,'test1');
func()
test2.js
const a = require('./test1')
module.exports = function(){
console.log(a.a,'test2')
}
test1.js
let a={
a:1
}
module.exports=a
运行node index.js
输出结果
看看ES6
的
// math.js
export let val = 1
export function add () {
val++
}
// test.js
import { val, add } from './math.js'
console.log(val) // 1
add()
console.log(val) // 2
React Vue
框架实现基本原理以及设计思想~
设计思想和基本原理:
1.由传统的直接DOM操作改成了数据驱动的方式去间接替我们操作DOM。
2.每次数据改变需要重新渲染时,只对存在差异对那个部分DOM进行操作。--diff算法
3.框架并不能解决性能问题,只是为了方便我们开发而已
有一系列对生命周期,其实就是代码执行顺序中给定了一部分的特定函数名称进行执行,一种约定。
常见的diff
算法,有上一个虚拟dom
和这次更新后的虚拟dom
去对比,然后给真实dom
打补丁的方式,也有用真实dom
和虚拟dom
直接对比的方式。
前端需要了解的常见的算法和数据结构
常见的数据结构:栈,队列,树,图,数组,单链表,双链表,图等...
冒泡排序
比较相邻的两个元素,如果前一个比后一个大,则交换位置。
第一轮的时候最后一个元素应该是最大的一个。
按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较
function bubble_sort(arr){
for(var i=0;i
for(var j=0;j
if(arr[j]>arr[j+1]){
var swap=arr[j];
arr[j]=arr[j+1];
arr[j+1]=swap;
}
}
}
}
var arr=[3,1,5,7,2,4,9,6,10,8];
bubble_sort(arr);
console.log(arr);
快速排序
js代码实现 解析:快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
function quick_sort(arr){
if(arr.length<=1){
return arr;
}
var pivotIndex=Math.floor(arr.length/2);
var pivot=arr.splice(pivotIndex,1)[0];
var left=[];
var right=[];
for(var i=0;i
if(arr[i]
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
return quick_sort(left).concat([pivot],quick_sort(right));
}
var arr=[5,6,2,1,3,8,7,1,2,3,4,7];
console.log(quick_sort(arr));
时间复杂度概念:
一个算法的时间复杂度反映了程序运行从开始到结束所需要的时间。
空间复杂度概念:
一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
具体可以看这篇文章:
JavaScript 算法与数据结构
Node.js
的底层fs
,net
,path
,stream
等模块以及express
框架使用和操作数据库注意,Node.js
中很多回调函数的首个参数都是error
根据路径同步读取文件流:
// 在 macOS、Linux 和 Windows 上:
fs.readFileSync('<目录>');
// => [Error: EISDIR: illegal operation on a directory, read <目录>]
异步地读取文件的全部内容:
fs.readFile('路径', (err, data) => {
if (err) throw err;
console.log(data);
});
上面读取到的data
都是buffer
数据 ,Buffer 类是一个全局变量,用于直接处理二进制数据。
如果路径存在,则返回 true,否则返回 false。:
fs.existsSync(path)
Node.js
中一般同步的API
都是sync
结尾,不带的一般是异步的,我们一般都用异步API
Node.js 中有四种基本的流类型:
Writable - 可写入数据的流(例如 fs.createWriteStream())。
Readable - 可读取数据的流(例如 fs.createReadStream())。
Duplex - 可读又可写的流(例如 net.Socket)。
Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate() )。
使用Node.js编写的静态资源服务器 这是我的自己编写的静态资源服务器
里面有大量的Buffer
操作
Node
里面这些常用的模块,是走向全栈工程师的基础。越是复杂的应用,对二进制操作会越多,比如自己定义的即时通讯协议,你需要把数据一点点的从Buffer
里切出来。如果是prob
协议,那么还要反序列化。但是原理大都类似,还有涉及音视频等。
Node.js
作为中间件,同构服务端渲染单页面应用,以及做转发请求等操作为了解决单页面应用的SEO
问题
传统的SSR
渲染是在服务端把代码都运行好了然后通过字符串都形式传给前端渲染
现在都单页面应用是只传输一个空的HTML
文件和很多个js
文件 给前端,然后拿到文件后动态生成页面。这就导致搜索引擎的爬虫无法爬到网页的信息,所有有了同构。
同构就是把单页面应用,React和Vue
这样框架写的代码,在服务端运行一遍(并不是运行全部),然后返回字符串给前端渲染,这个时候搜索引擎就可以爬取到关键字了。前端根据服务端返回的字符串渲染生成页面后,js
文件接管后续的逻辑。这样就是一套完整的同构
React服务端渲染源码 这个是我的React
服务端渲染源码
客户端入口文件:
//client/index. js
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { getClientStore } from '../containers/redux-file/store';
import {renderRoutes} from 'react-router-config'
import routers from '../Router';
const store = getClientStore();
const App = () => {
return (
{renderRoutes(routers)} );
};
ReactDom.hydrate(
, document.getElementById('root'));
同构的入口代码:
// server/index.js
import express from 'express';
import { render } from '../utils';
import { serverStore } from '../containers/redux-file/store';
const app = express();
app.use(express.static('public'));
app.get('*', function(req, res) {
if (req.path === '/favicon.ico') {
res.send();
return;
}
const store = serverStore();
res.send(render(req, store));
});
const server = app.listen(3000, () => {
var host = server.address().address;
var port = server.address().port;
console.log(host, port);
console.log('启动连接了');
});
render
函数:import Routes from '../Router';
import { renderToString } from 'react-dom/server';
import { StaticRouter, Link, Route } from 'react-router-dom';
import React from 'react';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import routers from '../Router';
import { matchRoutes } from 'react-router-config';
export const render = (req, store) => {
const matchedRoutes = matchRoutes(routers, req.path);
matchedRoutes.forEach(item => {
//如果这个路由对应的组件有loadData方法
if (item.route.loadData) {
item.route.loadData(store);
}
});
console.log(store.getState(),Date.now())
const content = renderToString(
{renderRoutes(routers)} );
看起来眼花缭乱 其实就是把代码运行在服务端,然后拼接成字符串给前端
唯一有点特别的地方:
服务端代码注水:
客户端代码脱水:
store.js
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const getClientStore = () => {
//下面这行代码就是代码脱水,createStore是可以传入第二个参数的,去阅读源码可以了解
const defaultState = window.context ? window.context.state : {};
return createStore(reducers, defaultState, applyMiddleware(thunk));
};
export const serverStore = () => {
return createStore(reducers, applyMiddleware(thunk));
};
跟我一起默念:
同构的秘诀:
1.代码现在服务端运行
2.返回字符串和注水后的数据给前端
3.前端拿到字符串和注水数据后,脱水渲染,然后js
文件接管,这时候又是单页面应用的逻辑了
1、点个赞呗,可以让更多的人看到这篇文章,顺便激励下我。
2、老铁们,关注我的原创微信公众号「FUNS社区」,还可访问FUNS在线社区http://www.htmlfuns.cn,专注小白从零开始系列。包括HTML5、css3、javascript、vue涵盖所有前端知识体系,保证让你看完有所收获,不信你打我。后台回复『学习资料』送你一份2020年最新技术资料(超2000G!)现在全部免费给你!包含各类技能的优质学习视频。
作者简介
作者:大家好,我是FUNS大师兄,工作至今,从一个小白成长为大厂的技术总监,一直想写点什么,种种原因不知什么时候开始,也不知道怎么下手。一路走来,见证了很多Programmer的大起大落,其实程序员这个行业,坚持到最后的才是真大神。我们身处在一个浮躁的社会,不仅爱情如此,连程序开发也是这样。大家都习惯了喜新厌旧,忘记了当初选择的初心。我觉得我是幸运的,当年带我的导师是阿里出来的,本应风光无限,可惜她热爱午后的斜阳,去开了一家咖啡店,听说后来经营不善未果。最好的时光错过了最好的机遇吧。
期间碰到过一些学弟找我,大家都很迷茫,后来我才发现,迷茫这个东西跟你年龄没什么关系,迷茫说到底没有安全感,我们的薪资、家庭、未来等等都是安全感的一部分。我在此也不是为了教导种种,只是分享自己的一路走来的坑,我一直相信一句话,我们所羡慕的生活背后都有我们吃不了的苦。其次是写点自己想写的代码,让自己开心一些吧!
转载说明:未获得授权,禁止转载