Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试题整理 #22

Open
AlexZ33 opened this issue Jun 3, 2019 · 13 comments
Open

面试题整理 #22

AlexZ33 opened this issue Jun 3, 2019 · 13 comments

Comments

@AlexZ33
Copy link
Owner

AlexZ33 commented Jun 3, 2019

5种方式实现值交换

  1. var temp = a; a = b; b = temp; (传统,但需要借助临时变量)

  2. a ^= b; b ^= a; a ^= b; (需要两个整数)

  3. b = [a, a = b][0] (借助数组)

  4. [a, b] = [b, a]; (ES6,解构赋值)

  5. a = a + b; b = a - b; a = a - b; (小学奥赛题)
    5种方式去掉小数部分
    2,3,4种方法是对位进行操作,故数字大小范围限制更大,在 正负2的31次方(2,147,483,648) 之间

  6. parseInt(num)

  7. ~~num

  8. num >> 0

  9. num | 0

  10. Math.floor(num) //或ceil等
    判断 x 是否是整数
    function isInt(x) {
    return (x ^ 0) === x
    }
    // return Math.round(x) === x
    // return (typeof x === 'number') && (x % 1 === 0)
    // ES6 -> Number.isInteger()
    递归求阶乘
    function factorial(n) {
    return (n > 1) ? n * f(n - 1) : n
    }
    判断符号是否相同
    function sameSign(a, b) {
    return (a ^ b) >= 0
    }
    克隆数组
    arr.slice(0)
    数组去重
    // ES6
    Array.from(new Set(arr))

// ES5
arr.filter(function(ele, index, array){
return index===array.indexOf(ele)
})
数组最大值
function maxArr(arr) {
return Math.max.apply(null, arr)
}
数组最小值
function minArr(arr) {
return Math.min.apply(null, arr)
}
随机获取数组的一个成员
function randomOne(arr) {
return arr[Math.floor(Math.random() * arr.length)]
}
产生随机颜色
function getRandomColor() {
return #${Math.random().toString(16).substr(2, 6)}
}
随机生成指定长度的字符串
function randomStr(n) {
let standard = 'abcdefghijklmnopqrstuvwxyz9876543210'
let len = standard.length
let result = ''

for (let i = 0; i < n; i++) {
result += standard.charAt(Math.floor(Math.random() * len))
}

return result
}
简易对象的深拷贝
JSON.parse(JSON.stringify(obj))
美化console
console.info("%c哈哈", "color: #3190e8; font-size: 30px; font-family: sans-serif");

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Aug 5, 2019

数据转换

1、 实现一个函数筛选出客户1290所有的消费数据:即将下面的数据data转换成push输出

原数据

const mydata = [
    {
        "20181202": [
            {
                "data": "1200",
                "peopleId": "1290"
            },
            {
                "data": "7887",
                "peopleId": "1290"
            }
        ]
    },
    {
        "20190730": [
            {
                "data": "8889",
                "people": "1290"
            },
            {
                "data": "9889",
                "people": "1230"
            }
        ]
    }
]

function dataFormat(data, peopleId) {

 //筛选出客户1290所有的消费数据

}

转换后

let push = [
    {
        "time": "2018-12-02",
        "data": "1200"
    },
    {
        "time": "2018-12-02",
        "data": "7887"
    },
    {
        "time": "2019-07-30",
        "data": "8889"
    }
]

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Aug 5, 2019

答案:

function formatData(mydata, peopleId) {
  return mydata.map(item => {
    return {
      time: Object.keys(item)[0],
      data: item[Object.keys(item)[0]].filter(i => i.peopleId)[0].data
    }
  })
}

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

CSS

盒模型

  • 介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?
    (1)有两种, IE 盒子模型、W3C 盒子模型;
    (2)盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
    (3)区 别: IE的content部分把 border 和 padding计算了进去;
    box-sizing:border-box 包括border、padding、margin
    box-sizing:content-box 默认值。不包括border、padding、margin

BFC

https://blog.csdn.net/sinat_36422236/article/details/88763187

BFC是什么

BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
如何创建BFC
1、float的值不是none。
2、position的值不是static或者relative。
3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
4、overflow的值不是visible

利用BFC避免margin重叠。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>防止margin重叠</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    p {
        color: #f55;
        background: yellow;
        width: 200px;
        line-height: 100px;
        text-align:center;
        margin: 30px;
    }
    div{
        overflow: hidden; /* 为了构造成两个BFC,这样margin就不会重叠了*/
    }
</style>
<body>
    <p>�看看我的 margin是多少</p>
    <div>
        <p>�看看我的 margin是多少</p>
    </div>
</body>
</html>

解决float重叠

为了解决float浮动重叠,BFC的区域不会与float box重叠。
所以我们让right单独成为一个BFC

 .right {
        overflow: hidden;
        height: 300px;
        background: rgb(170, 54, 236);
        text-align: center;
        line-height: 300px;
        font-size: 40px;
    }

清除浮动

计算BFC的高度时,浮动元素也参与计算。

.par {
      border: 5px solid rgb(91, 243, 30);
      width: 300px;
      overflow: hidden;
}
    
.child {
    border: 5px solid rgb(233, 250, 84);
    width:100px;
    height: 100px;
    float: left;
}

水平垂直居中

行内元素

CSS设置行内元素的垂直居中

div{height:30px; line-height:30px} /*DIV内的行内元素均会垂直居中*/ 
 

PS:当然,如果既要水平居中又要垂直居中,那么综合一下

div{text-align:center; height:30px; line-height:30px} 

垂直居中:vertical-align: middle;
水平居中:text-align:center;

块级元素

hawx1993/tech-blog#12

*  水平居中:
        * 用margin:子:width;margin:0 auto;
        * 用test-align:父:text-align:center;子:display:inline-block;
        * 用transform:父:relative;子:absolute;transform:translate(-50%);
        * 用Table:父:display:table;margin:0 auto;
        * 用flex:父:flex;justify-content:center;
        * 用flex——2:父:flex;子:margin:0 auto;

* 垂直居中:
        * 用vertical-align:
        父:vertical-align:middle;display:table-cell;height:20px;只有一个元素是inline或者inline-block或者table-cell时vertical-align才管用。
        * 用vertical-align:父:vertical-align:middle;display:inline-block;line-height:20px;
        * 用transform:父:relative;子:absolute;transform:translate(0,-50%);
        * 用flex:父:flex;align-item:center;

* 水平垂直居中:
        * 1)利用vertical-align,text-align,inline-block实现
        .parent{display:table-cell;vertical-align:middle;text-align:center;height:100px}
        .child{display:inline-block;}

        * 2) transform
        .parent{position:relative;}
        .child{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
        
        * 3)利用flex实现
         .parent{display:flex;justify-content:center;align-items:center;}

两列、三列布局

两列:
方法1.浮动布局 左:固定宽度+左浮动; 右:margin-left:左侧宽度。
方法2:浮动布局—+负外边距 左:固定宽度+左浮动+margin-right:-100%; ⽗父(右):左浮动+width:100%; 右:margin-left:左宽度。
三列:
中间⾃自适应,两边固定宽度
1.绝对定位加margin留空 左、右⽤用绝对定位在左右两侧,中间栏利利⽤用margin-left和margin-right
2.浮动+负外边距(太复杂放弃)
3.浮动定位法(中间栏也不不是等⾼高) 中间栏:放最后+margin-left:左侧宽+margin-right:右侧宽。 左:左浮动;
右:浮动;

 <!--左右侧栏的位置可以更更改--> 
 <div id="left"></div> 
 <div id="right"></div> 
 <!--中间栏放最后-->
<div id="main"></div>
*{
  margin:0;
  padding:0;
  height:100%;
} 
#left{
  width:300px;
  background-color:yellow;
  float:left;
} 
#right{
  width:200px;
  background-color:orange;
  float:right;
} 
#main{
  background-color:aqua;
  margin-left:300px;
  margin-right:200px;
}

4 flexbox

 #box{
            height: 500px;
            padding: 0;
            margin: 0;
            display: flex;
}
        #left{
            background: yellow;
            order: 1;
            flex: 20%;
} #content{
            flex:3 1 60%;
            background: blue;
            order: 2;
} #right{
            flex: 20%;
            background: red;
            order: 3;
}

两栏等高
1 左浮动+margin-bottom和padding-bottom
2 table-row加table-cell
3 flex (width不不起作⽤用的。)

移动端适配(css函数)

查看桌面文件
rem meta scale=1/dpr font-size
vw
vw+rem

1px问题

从第一部分的讨论可知 viewport 的 initial-scale 具有缩放页面的效果。对于 dpr=2 的屏幕,1px压缩一半便可与1px的设备像素比匹配,这就可以通过将缩放比 initial-scale 设置为 0.5=1/2 而实现。以此类推 dpr=3的屏幕可以将 initial-scale设置为 0.33=1/3 来实现。

动画

@Keyframe:https://www.runoob.com/cssref/css3-pr-animation-keyframes.html
transition: https://www.runoob.com/cssref/css3-pr-transition.html
transform、transition和animation 三者的区别:http://www.divcss5.com/css3-style/c57157.shtml

css3特性

@function () {

@return ....
}

继承样式 @

@mixin textrow($row : 1) {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: $row;
  -webkit-box-orient: vertical;
}

flex: 0 1 auto是什么意思

首先明确一点是, flex 是 flex-grow、flex-shrink、flex-basis的缩写。

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

回流重绘

Advanced-Frontend/Daily-Interview-Question#24

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

webpack

webpack dev

实时预览

Webpack 在启动时可以开启监听模式,开启监听模式后 Webpack 会监听本地文件系统的变化,发生变化时重新构建出新的结果。Webpack 默认是关闭监听模式的,你可以在启动 Webpack 时通过 webpack --watch 来开启监听模式。通过 DevServer 启动的 Webpack 会开启监听模式,当发生变化时重新执行完构建后通知 DevServer。 DevServer 会让 Webpack 在构建出的 JavaScript 代码里注入一个代理客户端用于控制网页,网页和 DevServer 之间通过 WebSocket 协议通信, 以方便 DevServer 主动向客户端发送命令。 DevServer 在收到来自 Webpack 的文件变化通知时通过注入的客户端控制网页刷新。

模块热替换

除了通过重新刷新整个网页来实现实时预览,DevServer 还有一种被称作模块热替换的刷新技术。 模块热替换能做到在不重新加载整个网页的情况下,通过将被更新过的模块替换老的模块,再重新执行一次来实现实时预览。 模块热替换相对于默认的刷新机制能提供更快的响应和更好的开发体验。 模块热替换默认是关闭的,要开启模块热替换,你只需在启动 DevServer 时带上 --hot 参数,重启 DevServer 后再去更新文件就能体验到模块热替换的神奇了。

支持 Source Map

https://webpack.wuhaolin.cn/1%E5%85%A5%E9%97%A8/1-6%E4%BD%BF%E7%94%A8DevServer.html

webpack 优化

webpack tree-shaking

ES6 module 特点:
只能作为模块顶层的语句出现import 的模块名只能是字符串常量import binding 是 immutable的ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。
所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道引用的什么模块,这个就不能通过静态分析去做优化。

  • 结论

我们已经知道,想要使用 tree shaking 必须注意以下……

使用 ES2015 模块语法(即 import 和 export)。
确保没有 compiler 将 ES2015 模块语法转换为 CommonJS 模块(这也是流行的 Babel preset 中 @babel/preset-env 的默认行为 - 更多详细信息请查看 文档)。

在项目 package.json 文件中,添加一个 "sideEffects" 属性。

通过将 mode 选项设置为 production,启用 minification(代码压缩) 和 tree shaking。
你可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。

avatar
avatar

CDN 加速

https://webpack.wuhaolin.cn/4%E4%BC%98%E5%8C%96/4-9CDN%E5%8A%A0%E9%80%9F.html

按需加载

查看webpack4优化配置
import().then()
从中可以看到,import() 返回的是一个 Promise 对象,其主要就是利用 JSONP 实现动态加载,返回的 res 结果不同的 export 方式会有不同,如果使用的 module.exports 输出,那么返回的 res 就是 module.exports 输出的结果;如果使用的是 ES6 模块输出,即 export default 输出,那么返回的 res 结果就是 res.default

  • 方法一:Route+import()异步加载
  • 方法二:React.lazy() (要用React.Suspense包裹)
const HotList = React.lazy(() => import(/* webpackChunkName: "index-page" */ '../../components/hotList'));
  • 方法三:异步回调+import()

压缩代码

查看webpack4优化配置

webpack异步加载(动态导入)

https://juejin.im/post/5d26e7d1518825290726f67a

多页面打包

autoWebPlugin

source-map

6.cheap-module-eval-source-map: 常用于开发环境,使用 cheap 模式可以大幅提高 souremap 生成的效率,加上 module 同时会对引入的库做映射,eval 提高打包构建速度,并且不会产生 .map 文件减少网络请求。

凡是带 eval 的模式都不能用于生产环境,因为其不会产生 .map 文件,会导致打包后的文件变得非常大。通常我们并不关心列信息,所以都会使用 cheap 模式,但是我们也还是需要对第三方库做映射,以便精准找到错误的位置。

抽取公共代码

splitChunks

多线程

thread-loader
把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行

在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:

这些 loader 不能产生新的文件。
这些 loader 不能使用定制的 loader API(也就是说,通过插件)。
这些 loader 无法获取 webpack 的选项设置。
每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。
请仅在耗时的 loader 上使用

// threadLoader文件
const threadLoader = workerParallelJobs => {
    const options = { workerParallelJobs }
    if (constants.APP_ENV === 'dev') {
        Object.assign(options, { poolTimeout: Infinity })
    }
    return { loader: 'thread-loader', options }
}
// 配置文件
module.exports = [
    {
        test: /\.svg$/,
        loader: [cacheLoader, threadLoader(), '@svgr/webpack'],
        include: [resolve('src')]
    }
]

缓存loader

cache-loader

HappyPack

HappyPack 允许 Webpack 使用 Node 多线程进行构建来提升构建的速度。

new HappyPack({
  id: 'jsx',
  threads: 4,
  loaders: ['babel-loader?presets[]=react,presets[]=latest&compact=false'],
})

其中,threads 指明 HappyPack 使用多少子进程来进行编译,一般设置为 4 为最佳。

完整版配置

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//css分离打包
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");//js压缩
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //css压缩
const createHtml =require("./config/create-html");// html配置
const getEntry = require("./config/get-entry");
const entry = getEntry("./src/pages");
const htmlArr = createHtml("./src/pages");

//主配置
module.exports = (env, argv) => ({
	entry: entry,
	output: {
		path: path.join(__dirname, "build"),
		filename: "[name].js"
	},
	module: {
		rules: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: {
					loader:"babel-loader",
					options:{
						presets: [ //presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets 可以叠加。 Presets 其实是一组 Plugins 的集合,每一个 Plugin 完成一个新语法的转换工作。
							"@babel/preset-env",
							"@babel/preset-react",
							{"plugins": ["@babel/plugin-proposal-class-properties"]} //这句很重要 不然箭头函数出错
						], 
					}
				},
			},
			{
				test: /\.css$/,
				use: ["style-loader", "css-loader"],
				exclude: /node_modules/,
			},
			{
				test: /\.(scss|css)$/, //css打包 路径在plugins里
				use: [
					argv.mode == "development" ? { loader: "style-loader"} :MiniCssExtractPlugin.loader,
					{ loader: "css-loader", options: { url: false, sourceMap: true } },
					{ loader: "sass-loader", options: { sourceMap: true } }
				],
				exclude: /node_modules/,
			},
			{
        test: /\.(png|jpg)$/,
        loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]',
        options:{
            publicPath:'/'
        }
    },
		],
	},
	devServer: {
		port: 3100,
		open: true,
	},
	resolve:{
		alias:{optimization
			src:path.resolve(__dirname,"src/"),
			component:path.resolve(__dirname,"src/component/"),
			store:path.resolve(__dirname,"src/store/"),
		}
	},
	plugins: [
		...htmlArr, // html插件数组
		new MiniCssExtractPlugin({ //分离css插件
			filename: "[name].css",
			chunkFilename: "[id].css"
		})
	],
	optimization: {
		minimizer: [//压缩js
			new UglifyJsPlugin({
				cache: true,
				parallel: true,
				sourceMap: false
			}),
			new OptimizeCSSAssetsPlugin({})
		],
		splitChunks: { //压缩css
			cacheGroups: {
				styles: {
					name: "styles",
					test: /\.css$/,
					chunks: "all",
					enforce: true
				}
			}
		}
	}
});

webpack工作原理

https://webpack.wuhaolin.cn/5%E5%8E%9F%E7%90%86/5-1%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E6%A6%82%E6%8B%AC.html

https://juejin.im/post/5ec169786fb9a043721b46ad#heading-3
avatar

webpack plugin编写:https://webpack.wuhaolin.cn/5%E5%8E%9F%E7%90%86/5-4%E7%BC%96%E5%86%99Plugin.html

流程概括

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件;
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

bundle.js 能直接运行在浏览器中的原因在于输出的文件中通过 webpack_require 函数定义了一个可以在浏览器中执行的加载函数来模拟 Node.js 中的 require 语句。

模块化历程

https://www.processon.com/view/link/5c8409bbe4b02b2ce492286a#map

webpack打包原理

https://juejin.im/post/5e116fce6fb9a047ea7472a6

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

算法

深度优先遍历

// 递归
function deepTraversal(node,nodeList) {  
    if (node) {
      nodeList.push(node);    
      var children = node.children;    
      for (var i = 0; i < children.length; i++) {
          deepTraversal(children[i],nodeList);    
      }    
    return nodeList;  
  }
}  

// 非递归
function deepTraversal(node) {  
    var nodeList = [];  
    if (node) {  
        var stack = [];  
        stack.push(node);  
        while (stack.length != 0) {  
            var childrenItem = stack.pop();  
            nodeList.push(childrenItem);  
            var childrenList = childrenItem.children;  
            for (var i = childrenList.length - 1; i >= 0; i--)  
                stack.push(childrenList[i]);  
        }  
    }    
    return nodeList;  
}   
var root = document.getElementById('root')
console.log(deepTraversal(root))

广度优先遍历

// 
function wideTraversal(node) {  
    var nodes = [];  
    if (node != null) {  
        var queue = [];  
        queue.unshift(node);  
        while (queue.length != 0) {  
            var item = queue.shift();  
            nodes.push(item);  
            var children = item.children;  
            for (var i = 0; i < children.length; i++)  
                queue.push(children[i]);  
        }  
    }  
    return nodes;  
}
var root = document.getElementById('root');
console.log(wideTraversal(root)); 
var nodes = {
  node: 6,
  left: {
    node: 5,
    left: {
      node: 4
    },
    right: {
      node: 3
    }
  },
  right: { 
    node: 2, 
    right: { 
      node: 1 
    } 
  }
}
function BFS(node, nodeList, stack) {
  if(node) {
    if (nodeList.length === 0) {
      nodeList.push(node.node);
    }
    if(node.left) {
      stack.unshift(node.left)
      nodeList.push(node.left.node);
    } 
    if(node.right) {
      stack.unshift(node.right)
      nodeList.push(node.right.node);
    }
    node = stack.pop()
    BFS(node, nodeList, stack)
  }
  return nodeList;
}
let res = BFS(nodes, [], [])

set

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

二叉树路径总和

https://leetcode-cn.com/problems/path-sum/solution/javascript-di-gui-die-dai-by-guyuejiajie/

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
  if (root === null) return false;
  if (root.left === null && root.right === null && root.val === sum)
    return true;

  let left = hasPathSum(root.left, sum - root.val);
  let right = hasPathSum(root.right, sum - root.val);
  return left || right;
};
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
  if (root === null) return false;
  let stack = [root];
  let sumStack = [sum - root.val];
  while (stack.length > 0) {
    let node = stack.pop();
    let curSum = sumStack.pop();
    if (node.left === null && node.right === null && curSum === 0) {
      return true;
    }
    if (node.right !== null) {
      stack.push(node.right);
      sumStack.push(curSum - node.right.val);
    }
    if (node.left !== null) {
      stack.push(node.left);
      sumStack.push(curSum - node.left.val);
    }
  }
  return false;
}

反转链表

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function ReverseList(pHead) {
    var node=pHead, arr=[];
    while(node!=null){
        arr.push(node.val);
        node=node.next;
    }
    node = pHead;
    while(node!=null){
        node.val = arr.pop();
        node = node.next;
    }
    return pHead;
}

数组里的两数的和

var twoSum = function(nums, target) {
    let map = {};//key数字 value下标
    let loop = 0;//循环次数
    let dis;//目标与当前值的差
    while(loop < nums.length){
        dis = target - nums[loop];
        if(map[dis] != undefined){
            return [map[dis], loop];
        }
        map[nums[loop]] = loop;
        loop++;
    }
    return;
};

将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
Advanced-Frontend/Daily-Interview-Question#8

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

  • set
    ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。
Set 结构的实例有以下属性。

Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。

  • map
    JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
    为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

链表环结点 ???

二分查找

https://segmentfault.com/a/1190000008584438
复杂度O(logn)

function bsearch(array, low, high, target) {
  if(low > high ) return -1;
  var mid = Math.floor((low + hight)/2)
  if (array[mid]> target){
        return  bsearch(array, low, mid -1, target);
    } else if (array[mid]< target){
        return  bsearch(array, mid+1, high, target);
    } else {return mid;}
}
// 用二分查找法找寻下界
function BSearchUpperBound(array, low, high, target) {
  if(low > high || target < array[low]) return -1;
  var mid = Math.floor((low + high) / 2);
  while (high > low) {
    if(array[mid] > target) {
      high = mid;
    } else {
      low = mid + 1;
    }
    mid = Math.floor((low+high)/2);
  }
  return mid-1
}

合并两个有序链表

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function Merge(pHead1, pHead2) {
    // write code here
    if (pHead1 == null) {
        return pHead2;
    } else if (pHead2 == null) {
        return pHead1;
    }
    var result = {};
    if (pHead1.val < pHead2.val) {
        result = pHead1;
        result.next = Merge(pHead1.next, pHead2);
    } else {
        result = pHead2;
        result.next = Merge(pHead1, pHead2.next);
    }
    return result;
}

二叉树镜像

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function Mirror(root)
{
    // write code here
    if(root === null) {
        return;
    }
    var temp = root.left;
    root.left = root.right;
    root.right = temp;
    Mirror(root.left);
    Mirror(root.right);
}

二叉树前中后遍历

https://juejin.im/entry/5847c17a128fe10058bcf2c5

  1. 前序遍历:访问根–>遍历左子树–>遍历右子树;
  2. 中序遍历:遍历左子树–>访问根–>遍历右子树;
  3. 后序遍历:遍历左子树–>遍历右子树–>访问根;
  4. 广度遍历:按照层次一层层遍历;
// 前序遍历
var preListRec = []; //定义保存先序遍历结果的数组
var preOrderRec = function(node) {
    if (node) { //判断二叉树是否为空
        preListRec.push(node.value); //将结点的值存入数组中
        preOrderRec(node.left); //递归遍历左子树
        preOrderRec(node.right); //递归遍历右子树
    }
}
preOrderRec(tree);
console.log(preListRec);
// 前序遍历 非递归
var preListUnRec = []; //定义保存先序遍历结果的数组
var preOrderUnRecursion = function(node) {
    if (node) { //判断二叉树是否为空
        var stack = [node]; //将二叉树压入栈
        while (stack.length !== 0) { //如果栈不为空,则循环遍历
            node = stack.pop(); //从栈中取出一个结点
            preListUnRec.push(node.value); //将取出结点的值存入数组中
            if (node.right) stack.push(node.right); //如果存在右子树,将右子树压入栈
            if (node.left) stack.push(node.left); //如果存在左子树,将左子树压入栈
        }
    }
}
preOrderUnRecursion(tree);
console.log(preListUnRec);
// 中序遍历
var inListRec = []; //定义保存中序遍历结果的数组
var inOrderRec = function(node) {
    if (node) { //判断二叉树是否为空
        inOrderRec(node.left); //递归遍历左子树
        inListRec.push(node.value); //将结点的值存入数组中
        inOrderRec(node.right); //递归遍历右子树
    }
}
inOrderRec(tree);
console.log(inListRec);
//[ 'a', '+', 'b', '*', 'c', '-', 'd', '/', 'e' ]
// 后序遍历
var postListRec = []; //定义保存后序遍历结果的数组
var postOrderRec = function(node) {
    if (node) { //判断二叉树是否为空
        postOrderRec(node.left); //递归遍历左子树
        postOrderRec(node.right); //递归遍历右子树
        postListRec.push(node.value); //将结点的值存入数组中
    }
}
postOrderRec(tree);
console.log(postListRec);
//[ 'a', 'b', 'c', '*', '+', 'd', 'e', '/', '-' ]

二叉树深度 2

var maxDepth = function(root) {
    if (!root) return 0;
    else {
        let leftDepth = maxDepth(root.left),
            rightDepth = maxDepth(root.right);
        let childDepth = leftDepth > rightDepth ? leftDepth : rightDepth;
        return childDepth + 1;
    }
};

最长公共子序列 2 ???

https://leetcode-cn.com/problems/longest-common-subsequence/solution/1143-zui-chang-gong-gong-zi-xu-lie-by-alexer-660/

删除链表中重复结点 2 ???

链表表示大数求和 2 ???

function addTwoNumbers(l1, l2) {
  let res;
  let temp; // 当前的节点
  let isMoreThan10 = false;
  while (l1 && l2) { // 两个链表公共部分
    const sum = l1.val + l2.val;
    const r = sum % 10;
    if (temp) {
      //  一边创建一边取下一个
      temp.next = new ListNode();
      temp = temp.next;
    }
    if (!res) {
      // 第一次进来
      res = new ListNode(r);
      temp = res;
    }
    // 所有的加法操作都要这样
    temp.val = (r + isMoreThan10) % 10;
    isMoreThan10 = sum + isMoreThan10 > 9;
    l1 = l1.next;
    l2 = l2.next;
  }
  // 剩下那段
  let rest = l1 || l2;
  while (rest) {
    temp.next = rest;
    temp = temp.next;
    if (isMoreThan10) {
      isMoreThan10 = temp.val + 1 > 9;
      temp.val = (temp.val + 1) % 10;
    }
    rest = rest.next;
  }
  // 如果还有进位,追加一个1
  if (isMoreThan10) {
    temp.next = new ListNode(1);
  }
  return res;
}

二叉树的最近公共祖先

https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/

var lowestCommonAncestor = function(root, p, q) {
  if (!root || root === p || root === q) return root;
  const left = lowestCommonAncestor(root.left, p, q);
  const right = lowestCommonAncestor(root.right, p, q);
  if (!left) return right; // 左子树找不到,返回右子树
  if (!right) return left; // 右子树找不到,返回左子树
  return root;
};

复杂度分析
时间复杂度:O(N)
空间复杂度:O(N)

二叉树同行输出

https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function Print(pRoot)
{
    if(!pRoot){
        return [];
    }
    var queue = [],
        result = [];
    queue.push(pRoot);
    while(queue.length){
        var len = queue.length;
        var tempArr = [];
        for(var i = 0;i<len;i++){
            var temp = queue.shift();
            tempArr.push(temp.val);
            if(temp.left){
                queue.push(temp.left);
            }
            if(temp.right){
                queue.push(temp.right);
            }
        }
        result.push(tempArr);
    }
    return result;
}
function Print(pRoot)
{
    // write code here
    if(!pRoot) return [];
    let list = [];
    function depthP(root, depth, list){
        if(!root) return;
        if(!list[depth]) list[depth] = [];
        list[depth].push(root.val);
        depthP(root.left, depth+1, list);  
        depthP(root.right, depth+1, list);
        
    }
    depthP(pRoot, 0, list);
    return list
}

LRU ???

跳台阶和变态跳台阶

https://www.nowcoder.com/profile/2902658/codeBookDetail?submissionId=32971873

function jumpFloor(number)
{
    if (number < 2) {
        return 1
    }
    let arr = [1, 1]
    for (let i = 2; i <= number; i ++) {
        arr[i] = arr[i - 1] + arr[i - 2]
    }
    return arr[number]
}

https://www.nowcoder.com/profile/291358634/codeBookDetail?submissionId=77010415

function jumpFloorII(number)
{
    // write code here
    if(number===0){
        return 0;
    }
    if(number===1){
        return 1
    }
    if(number>=2){
        return 2*jumpFloorII(number-1);
    }
}

之字形打印二叉树

与同行类似,加个flag就行

数组扁平化

function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

快速排序

var quickSort = function(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 < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

js基础

一 继承

1 原型链继承

  • 方法一
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat('大毛', '黄色');
alert(cat1.species);

avatar

2 类式继承

class P {
  constructor (height) {
    this.height = height;
  }
  fn() {

  }
}
class Sun extends P {
  constructor(length) {
    super(length);
    // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    this.name = 'Square';
  }
}

extends核心代码

function _inherits(subType, superType) {
  // 创建对象,创建父类原型的一个副本
  // 增强对象,弥补因重写原型而失去的默认的constructor属性
  // 指定对象,将新创建的对象赋值给子类的原型
  subType.prototype = Object.create(superType && superType.prototype, {
    constructor: {
      value: subType,
      enumerable: false,
      writable: true,
      configurable: true
    }
  })

  if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
  }
}

Object.create(proto[, propertiesObject])
参数:

  • proto
    新创建对象的原型对象。
  • propertiesObject
    可选。如果没有指定为 undefined,则是要添加到新创建对象的不可枚举(默认)属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。

3 借用构造继承,几种组合继承方式

使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)
不能继承方法;

function  SuperType(){
    this.color=["red","green","blue"];
}
function  SubType(){
    //继承自SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"

var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"

核心代码是SuperType.call(this),创建子类实例时调用SuperType构造函数,于是SubType的每个实例都会将SuperType中的属性复制一份。

  • 缺点:
    只能继承父类的实例属性和方法,不能继承原型属性/方法
    无法实现复用,每个子类都有父类实例函数的副本,影响性能

4 组合继承

组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

function SubType(name, age){
  // 继承属性
  // 第二次调用SuperType()
  SuperType.call(this, name);
  this.age = age;
}

// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType(); 
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

5 三个继承方式的优缺点,优化列出代码

基于构造函数的继承,可以执行父类构造函数,不能继承方法
基于原型链的继承,可以继承方法,无法执行父类构造函数
组合式继承,可以继承方法和执行构造函数,父类构造函数会执行2次

二 fetch取消

https://juejin.im/entry/5af85b6f518825426a1fc8c8
fetch api:https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

var controller = new AbortController();
var signal = controller.signal;

// AbortController兼容性问题
abortBtn.addEventListener('click', function() {
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo() {
  ...
  fetch(url, {signal}).then(function(response) {
    ...
  }).catch(function(e) {
    reports.textContent = 'Download error: ' + e.message;
  })
}
function wrap (func, ...args) {
    const cancel = {};
    return () => {
        const promiseHandle = new Promise((resolve, reject) => {
            Object.defineProperty(cancel, 'signal', {
                set () {
                    reject('Abort');
                }
            })

            func(...args).then(v => resolve(v)).catch(err => reject(err))
        });

        return Object.assign(promiseHandle, {
            cancel () {
                cancel.signal = true;
            }
        });
    }

}

三 instanceof

语法:
object instanceof constructor;
instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

// a instanceof b
function _instanceof (a, b) {
  while (a) {
    if (a.__proto__ === b.prototype) return true;
    a = a.__proto__;
  }
  return false;
}

typeof和instanceof的区别
https://segmentfault.com/a/1190000000730982

四 .promise封装setstate

手写promise
resolve和reject的settimeout里改变state状态,修改self.val,执行resolveQueue里的函数;

then方法里,判断this.state的状态,并return的promise,如果是FulFiled和Rejected执行then方法里的参数,如果是值直接返回,function则执行后判断是否是promise再继续返回。如果状态是pending,则把执行then方法里参数的内容push到一个队列中等待resolve后执行。

catch方法里,就是then传入参数(null, OnReject);

const PENDING = 1;
const FULFILLED = 2;
const REJECTED = 3;

function MyPromise(executor) {
    let self = this;
    this.resolveQueue = [];
    this.rejectQueue = [];
    this.state = PENDING;
    this.val = undefined;
    function resolve(val) {
        if (self.state === PENDING) {
            setTimeout(() => {
                self.state = FULFILLED;
                self.val = val;
                self.resolveQueue.forEach(cb => cb(val));
            });
        }
    }
    function reject(err) {
        if (self.state === PENDING) {
            setTimeout(() => {
                self.state = REJECTED;
                self.val = err;
                self.rejectQueue.forEach(cb => cb(err));
            });
        }
    }
    try {
        // 回调是异步执行 函数是同步执行
        executor(resolve, reject);
    } catch(err) {
        reject(err);
    }
}

MyPromise.prototype.then = function(onResolve, onReject) {
    let self = this;
    // 不传值的话默认是一个返回原值的函数
    onResolve = typeof onResolve === 'function' ? onResolve : (v => v);
    onReject = typeof onReject === 'function' ? onReject : (e => { throw e });
    if (self.state === FULFILLED) {
        return new MyPromise(function(resolve, reject) {
            setTimeout(() => {
                try {
                    let x = onResolve(self.val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (self.state === REJECTED) {
        return new MyPromise(function(resolve, reject) {
            setTimeout(() => {
                try {
                    let x = onReject(self.val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (self.state === PENDING) {
        return new MyPromise(function(resolve, reject) {
            self.resolveQueue.push((val) => {
                try {
                    let x = onResolve(val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
            self.rejectQueue.push((val) => {
                try {
                    let x = onReject(val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
}

MyPromise.prototype.catch = function(onReject) {
    return this.then(null, onReject);
}

all方法, 有一个计数变量用于记录已经执行完的promise,还有一个存储执行的promise结果的数组,当变量达到promise.length,则执行resolve返回结果数组;如果出现err,则直接reject;

MyPromise.prototype.all = function (promises) {
  return new MyPromise(function(resolve, reject) => {
    let cnt = 0;
    let res = [];
    for(let i = 0; i< promises.length; i++) {
      promises[i].then(res => {
        result[i] = res;
        if (++cnt === promises.length) resolve(result);
      }, err => reject(err))
    }
  })
}

MyPromise.prototype.race = function(promises) {
    return new MyPromise(function(resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    });
}

MyPromise.prototype.resolve = function(val) {
    return new MyPromise(function(resolve, reject) {
        resolve(val);
    });
}

MyPromise.reject = function(err) {
    return new MyPromise(function(resolve, reject) {
        reject(err);
    })
}

promise化setState

function promiseSetState(state, callback) {
  var _this = this;
  return new Promise((resolve, reject) => {
    _this.setState(state, () => {
      callback();
      resolve();
    })
  })
}

promise.prototype.retry(函数名,次数,delay时长(ms))

retry = function(fn, times,delay) {
  var err = null;
  retrun new Promise((resolve, reject) => {
    var attempt = function() {
      fn().then(resolve).catch((err) => {
        console.log(`Attempt #${times} failed`);
        if (0 == times) {
          reject(err);
        } else {
          times--;
          setTimeout(function(){
            attempt()
          }, delay);
        }
      })
    }
    attempt();
  })
}

五 实现一个new

function _new(func, ...args) {
  if(typeof func !== 'function') {
    return new Error('参数必须是一个函数');
  }
  let obj = Object.create(func.prototype); // 返回值:一个新对象,带着指定的原型对象和属性(也就是func.ptototype)。
  let res = func.call(obj, ...args);
  if (res !== null && (typeof res === 'object' || typeof res === 'function')) {
      return res;
  }
  return obj;
}

为什么执行func.call(obj, ...args)?是因为func.call(obj, ...args)在obj这个对象作用域下执行func的构造函数,也就是下面的函数:

function func() {
  // some code
  this.a = 'a';
  // some code
}

这样obj作用域下就有了func的属性值。
但如果func有return,如果return的是string,那个res = func.call(obj, ...args)不是object或function,那么返回obj也就是返回拥有func属性的新对象。例如下面例子:

function fn(){
    this.a = 'a'
    return 'hello'
}

let f1 = new fn()
f1() // VM1074:1 Uncaught TypeError: f1 is not a function
f1 // fn {a: "a"}  !!!!注意不是return的‘hello’而是fn {a: "a"}对象。
function fn(){
    this.a = 'a'
    return {b : 'b'}
}

let f1 = new fn()
f1() // VM1074:1 Uncaught TypeError: f1 is not a function
f1 // {b: "b"}

实例没有constructor,实例的constructor都是指向new的构造函数的。
子函数继承父函数,子函数的prototype指向父函数的实例,那么子函数的所有实例的prototype上也就拥有了父函数的属性,也就是都能继承父函数了。
但是,子函数的constructor要指向自己。

六 手写 bind、call 和 apply

call:

Function.prototype.call = function(context, ...bindArgs) {
  context = (typeof context === 'object' ? context : window)
  let key = new Symbol();
  context[key] = this;
  let result = context[key](...bindArgs);
  delete context[key]
  return result
}

bind:

Function.prototype.bind = function(context, ...bindArgs) {
  // func为调用bind原函数
  const func = this;
  context = context || window;
  if (typeof func !== 'function') {
    throw new TypeError('Bind must be called on a function');
  }
  // bind 返回一个绑定 this 的函数
  return function(...callArgs) {
    let args = bindArgs.concat(callArgs);
    return func.call(context, ...args);
  }
}

apply第二个参数为一个数组,call是多个参数:
所以call第二个参数传进去是...,contextkey也是...;而apply第二个参数传进去是args,使用时...args

Function.prototype.apply = function(context, args) {
  context = context || window;
  context.func = this;

  if (typeof context.func !== 'function') {
    throw new TypeError('apply must be called on a function');
  }

  let res = context.func(...args);
  delete context.func;
  return res;
}

七 async await、Promise、setTimeout执行顺序

队列任务优先级:promise.Trick()>promise的回调>setTimeout>setImmediate
https://juejin.im/post/5b191d585188257d831e338e

写一个eventEmitter类,包括on()、off()、once()、emit()方法

class EventEmitter() {
  constructor() {
    this._events = {};
  }
  on(event, callback) {
    let callbacks = this._events[event] || [];
    this._events[event] = callbacks.push(callback);
    return this;
  }

  off(event, callback) {
    let callbacks = this._events[event];
    this._events[event] = callbacks && callbacks.filter(fn => {
      return fn !== callback;
    })
    return this;
  }

  once(event, callback) {
    let wrap = (...args) => {
      callback.apply(this, arguments);
      this.off(event, wrap);
    }
    this.on(event, wrap);
    return this;
  }

  emit(event, ...args) {
    const callbacks = this._events[eventName]
      callbacks.map(cb => {
          cb(...args)
      })
      return this;
  }
}

八、==的隐式转化

比较运算 x==y, 其中 x  y 是值,返回 true 或者 false。这样的比较按如下方式进行:

1、若 Type(x)  Type(y) 相同, 

    1*  Type(x)  Undefined, 返回 true。
    2*  Type(x)  Null, 返回 true。
    3*  Type(x)  Number, 
  
        (1)、若 x  NaN, 返回 false。
        (2)、若 y  NaN, 返回 false。
        (3)、若 x  y 为相等数值, 返回 true。
        (4)、若 x  +0  y  −0, 返回 true。
        (5)、若 x  −0  y  +0 返回 true。
        (6)、返回 false。
        
    4*  Type(x)  String, 则当 x  y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。 否则, 返回 false。
    5*  Type(x)  Boolean,  x  y 为同为 true 或者同为 false 时返回 true。 否则, 返回 false。
    6*   x  y 为引用同一对象时返回 true。否则,返回 false。
  
2、若 x  null  y  undefined, 返回 true。
3、若 x  undefined  y  null, 返回 true。
4、若 Type(x)  Number  Type(y)  String,返回比较 x == ToNumber(y) 的结果。
5、若 Type(x)  String  Type(y)  Number,返回比较 ToNumber(x) == y 的结果。
6、若 Type(x)  Boolean, 返回比较 ToNumber(x) == y 的结果。
7、若 Type(y)  Boolean, 返回比较 x == ToNumber(y) 的结果。
8、若 Type(x)  String  Number,且 Type(y)  Object,返回比较 x == ToPrimitive(y) 的结果。
9、若 Type(x)  Object  Type(y)  String  Number, 返回比较 ToPrimitive(x) == y 的结果。
10、返回 false。

上面主要分为两类,x、y类型相同时,和类型不相同时。

类型相同时,没有类型转换,主要注意NaN不与任何值相等,包括它自己,即NaN !== NaN。
类型不相同时,

1、x,y 为null、undefined两者中一个 // 返回true

2、x、y为Number和String类型时,则转换为Number类型比较。

3、有Boolean类型时,Boolean转化为Number类型比较。

4、一个Object类型,一个String或Number类型,将Object类型进行原始转换后,按上面流程进行原始值比较。

Obj=》string=》number

Bool=》number

Undefined=》

九 闭包

一个可以操作外部其他函数定义的局部变量的函数
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
        return this.name;

    }

  };

  alert(object.getNameFunc());// My Object
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());
// The Window
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());
// My Object

十、slice和splice

slice:返回一个新的对象;这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。

splice:splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

const months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
// inserts at index 1
console.log(months);
// expected output: Array ["Jan", "Feb", "March", "April", "June"]

months.splice(4, 1, 'May');
// replaces 1 element at index 4
console.log(months);
// expected output: Array ["Jan", "Feb", "March", "April", "May"]

十一、跨域

https://juejin.im/post/5c23993de51d457b8c1f4ee1
https://juejin.im/post/5c23993de51d457b8c1f4ee1

1 JSONP

// index.html
function jsonp({url, params, callback}) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    window[callback] = function(data) {
      resolve(data);
      document.body.removeChild(script)
    }
    let params = {...params, callback};
    let arrs = [];
    for(let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}`;
    document.body.appendChild(script);
  })
}
jsonp({
  url: 'http://localhost:3000/say',
  params: { wd: 'Iloveyou' },
  callback: 'show'
}).then(data => {
  console.log(data)
})
// server.js
let express = require('express');
let app = express();
app.get('/say', function(req, res) {
  let { wd, callback} = req.query;
  res.end( `${callback}('我不爱你')`);
})

jQuery的jsonp形式

$.ajax({
  url:"http://crossdomain.com/jsonServerResponse",
  dataType:"jsonp",
  type:"get",//可以省略
  jsonpCallback:"show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
  jsonp:"callback",//->把传递函数名的那个形参callback,可省略
  success:function(data) {
    console.log(data);
  }
});

2 CORS

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept: 指定客户端能够接收的内容类型
Accept-Language: 浏览器可接受的语言 Accept-Language: en,zh
Content-Language:响应体的语言
Last-Event-ID
Content-Type:返回内容的MIME类型 只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

// index.html
let xhr = new XHLHttpRequest();
document.cookie = 'name=xiamen';
xhr.withCredentials = true;
xhr.open('PUT', 'http://localhost:4000/getData', true);
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //得到响应头,后台需设置Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
}
xhr.send();
app.use(function(req, res, next) {
  let origin = req.headers.origin;
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next();
})
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end('我不爱你')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('我不爱你')
})
app.use(express.static(__dirname))
app.listen(4000)

上述代码由http://localhost:3000/index.html向http://localhost:4000/跨域请求,正如我们上面所说的,后端是实现 CORS 通信的关键。

3 postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

  • otherWindow.postMessage(message, targetOrigin, [transfer]);

message: 将要发送到其他 window的数据。

targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。

transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

//a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
  //内嵌在http://localhost:3000/a.html
  <script>
    function load() {
      let frame = document.getElementById('frame');
      frame.contentWindow.postMessage('woaini', 'http://localhost:4000')// 发送数据
      window.onmessage = function(e) {
        console.log(e.data)
      }
    }
  </script>
// b.html
window.onmessage = function(e) {
  console.log(e.data)
  e.source.postMessage('我不爱你', e.origin)
}

websocket

本地文件socket.html向localhost:3000发生数据和接受数据

// socket.html
<script>
  let socket = new WebSocket('ws://localhost:3000');
  socket.onopen = function() {
    socket.send('iu')
  }
  socket.onmessage = function(e) {
    console.log(e.data)
  }
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port: 3000});
wss.on('connection', function(ws) {
  wx.on('message', function(data) {
    console.log(data);
    ws.send('i love iu')
  })
})

node中间件代理

Nginx反向代理

Nginx正向代理:正向代理指的是,一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。即,目标服务器在前端设置
Nginx反向代理:指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

十二、事件委托

function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;

    // 获取到目标阶段指向的元素
    var target = event.target || event.srcElement;

    // 获取到代理事件的函数
    var currentTarget = event.currentTarget;

    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }

    // 遍历外层并且匹配
    while (target !== currentTarget) {
      // 判断是否匹配到我们所需要的元素上
      if (target.matches(targetSelector)) {
        var sTarget = target;
        // 执行绑定的函数,注意 this
        foo.call(sTarget, Array.prototype.slice.call(arguments))
      }

      target = target.parentNode;
    }
  }

  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

十三、tab 页面间通信

ZSI2017/blog#10
1 利用postMessage+localStorage

// one.html
 <script>
    window.addEventListener("storage",function(ev){
       if(ev.key === "message") {
           // removeItem 同样触发storage 事件,此时ev.newValue 为空
          if(!ev.newValue)
              return;
          var message = JSON.parse(ev.newValue);
          console.log(message);
       }
    });
    function sendMessage(message) {
      console.log("exacted sendMessage");
         localStorage.setItem('message',message);
         localStorage.removeItem("message");
    };
    document.querySelector('button').onclick = function(){
         sendMessage('this is message from A');
  }
    // 发送消息给B 页面。
// second.html
<script>
 window.addEventListener("storage",function(ev){
       if(ev.key === "message") {
           if(!ev.newValue)
              return;
            var message = ev.newValue;
            console.log(message)
            // 发送消息给A 页面
            sendMessage("message echo from B");
       }
 });
 function sendMessage(message) {
    localStorage.setItem('message',JSON.stringify(message));
    localStorage.removeItem("message");
 }
</script>

十四、前端做并发请求控制

请实现如下的函数,可以批量请求数据,所有的 URL 地址在 urls 参数中,同时可以通过 max 参数控制请求的并发度,当所有请求结束之后,需要执行 callback 回调函数。发请求的函数可以直接 使用 fetch 即可

function handleFetchQueue(urls, max, callback) {
  const urlCount = urls.length;
  const requestsQueue = [];
  const results = [];
  let i = 0;
  const handleRequest = (url) => {
    const req = fetch(url).then(res => {
      const len = results.push(res);
      if (len < urlCount && i + 1 < urlCount) {
        requestsQueue.shift();
        handleRequest(urls[++i])
      } else if (len === urlCount) {
        'function' === typeof callback && callback(results)
      }
    }).catch(e => {
      results.push(e)
    });
    if (requestsQueue.push(req) < max) {
      handleRequest(urls[++i])
    }
  };
  handleRequest(urls[i])
}

十五、节流防抖

函数节流: 指定时间间隔内只会执行一次任务;
函数防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

上文函数节流和函数防抖的实现中,调用fn的时候都是用的fn.apply(this, arguments)调用,而不是fn()直接调用。主要原因是为了fn函数内的this与原本的事件回调函数绑定的this保持一致。
setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字会指向 window (或全局)对象。因此在setTimeout中使用箭头函数(箭头的this绑定的是当前的词法作用域)此时的词法作用域为scroll事件回调函数中绑定的this(jquery中强制this指向dom元素),再将fn函数内的this绑定为这个this。我们以函数防抖为例看看这两种的区别。

function throttle(fn, interval = 300) {
    let canRun = true;
    return function () {
        if (!canRun) return;
        canRun = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            canRun = true;
        }, interval);
    };
}
function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}

十七、实现co

https://juejin.im/post/5e9e6005e51d4546f70d2777#heading-9

十八、arguments变数组

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
// es2015
const args = Array.from(arguments);
const args = [...arguments];

十九、获取页面所有img并且下载

//一个对象,存储页面图片数量和下载的数量
var monitorObj = {
    imgTotal: 0,
    imgLoaded: 0
}
//创建a标签,赋予图片对象相关属性,并插入body
var createA = function (obj) {
    var a = document.createElement("a");
    a.id = obj.id;
    a.target = "_blank";//注意:要在新页面打开
    a.href = obj.url;
    a.download = obj.url;

    document.body.appendChild(a);
}

//获取页面的图片
var imgs = document.images;
//创建每个图片对象的对应a标签
for (var i = 0; i < imgs.length;i++){
    var obj = {
        id: "img_" + i,
        url: imgs[i].src
    }
    //过滤掉不属于这几种类型的图片
    if (["JPG", "JPEG", "PNG","GIF"].indexOf(obj.url.substr(obj.url.lastIndexOf(".")+1).toUpperCase()) < 0) {
        continue;
    }
    //这里是为了去掉知乎用户头像的图片,头像大小是50*50
    if (imgs[i].width <= 50 || imgs[i].height <= 50) {
        continue;
    }
    //统计图片数量
    monitorObj.imgTotal++;
    createA(obj);
}
//开始下载图片
for (var i = 0; i < imgs.length; i++) {
    if (document.getElementById("img_" + i)) {
        //重点:触发a标签的click事件        
        document.getElementById("img_" + i).click();
        monitorObj.imgLoaded++; //统计已下载的图片数量
    } 
} 
console.log("已下载:"+monitorObj.imgLoaded + "/" + monitorObj.imgTotal);

二十、 [1,2,3].map(parseInt) 执行结果

[1,NaN,NaN]

二十一、如何实现for循环内定时器依次输出123

https://miss-me.github.io/2018/09/01/%E7%94%A8setTimeout%E5%AE%9E%E7%8E%B0for%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E8%AE%A1%E6%97%B6%E5%99%A8/
文章里的箭头函数是错的;

二十二、mixin原理

Object.assign

二十三、curry函数实现

https://segmentfault.com/a/1190000017064541

function curry(fn) {
    var length = fn.length; //获取原函数的参数个数
    var args = []; // args存储传入参数
    return function curryFn() {
        // 将arguments转换成数组
        var curryArgs = Array.prototype.slice.call(arguments); 
        args = args.concat(curryArgs);
        if (args.length > length) {
            throw new Error('arguments length error')
        }
        // 存储的参数个数等于原函数参数个数时执行原函数
        if (args.length === length) {
            return fn.apply(null, args);
        }
        // 否则继续返回函数
        return curryFn;
    };
}

二十四、js实现依赖注入

装饰器 + reflect-metadata

二十五、react setState promise化

https://juejin.im/post/59a699fd6fb9a0247d4f5970
https://exp-team.github.io/blog/2017/11/04/js/setState-promise/

this问题

https://zhuanlan.zhihu.com/p/37911534

ES6

class

Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器
严格模式 不存在变量提成 this问题
上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。

super
子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。

super作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。

副作用 纯函数

https://www.jianshu.com/p/5d88a569f6c1

| 和 ||

https://www.jianshu.com/p/86b994348a16

懒加载

https://juejin.im/post/5b0c3b53f265da09253cbed0

async/await实现

深拷贝

https://juejin.im/post/5b235b726fb9a00e8a3e4e88

// 只解决date,reg类型,其他的可以自己添加

function deepCopy(obj, hash = new WeakMap()) {
    let cloneObj
    let Constructor = obj.constructor
    switch(Constructor){
        case RegExp:
            cloneObj = new Constructor(obj)
            break
        case Date:
            cloneObj = new Constructor(obj.getTime())
            break
        default:
            if(hash.has(obj)) return hash.get(obj)
            cloneObj = new Constructor()
            hash.set(obj, cloneObj)
    }
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}

async和defer

https://juejin.im/entry/5a7ad55ef265da4e81238da9

将连字符串转为驼峰

var camelCase = (function () {
    var DEFAULT_REGEX = /[-_]+(.)?/g;

    function toUpper(match, group1) {
        return group1 ? group1.toUpperCase() : '';
    }
    return function (str, delimiters) {
        return str.replace(delimiters ? new RegExp('[' + delimiters + ']+(.)?', 'g') : DEFAULT_REGEX, toUpper);
    };
})();

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

node

node的module和Es6的module

  • node的module
    node是require,require() 方法可以在任何地方使用,它是运行时执行,也就是代码运行过程中进行编译和执行的,因此可以使用 变量或参数,实现按需加载;
    模块使用 require 方法时,引用的都是文件的 module.exports 对象,exports 对象和 module.exports 对象都是指 向的同一个存储地址,所以不能使用 exports = xxx 来导出模块,只能在 exports 对象上添加属性。
    require会优先使用缓存,如果缓存没有再进行引用加载并存入缓存中。

  • ES6的module
    ES6 为实现动态加载功能,定义了 import() 函数,这是一个异步加载函数,import() 方法返回的是一个Promise, 适用于按需加载或需要运行时确定加载路径的场景。
    webpack按需加载就可以用es6 import()
    https://juejin.im/post/5bf61082f265da616a474b5c
    ES6 出现前,JS 没有自己的模块体系。无法将一个大型程序分解成互相依赖的小文件,再用简单的方法拼接起 来。这时常用的模块加载方案是 CommonJS 和 AMD ,CommonJS 用于服务器,AMD 用于浏览器。而 ES6 模块在语 言标准上实现了模块功能,可以取代二者通用于浏览器和服务器端。目前服务器端对于ES6模块支持性不够好,但通 过各种工具库可以解决这个问题。
    ES6 模块思想是尽量的静态化,在编译时就确定模块的依赖关系,可以按需从大模块中读取某些方法,而不用加 载整个模块,加载效率更高。 而 CommonJS 和 AMD 是动态加载的,在运行时才会确定依赖关系。
    4.1 差异点
    模块的输出不一样,Node 中引用的是一个缓存,而 ES6 模块引用的是一个值的引用
    Node 中的模块是运行时加载,输出 module.exports 对象,而ES6的模块是编译时输出接口

canary中:
浏览器中通过 Babel+Browserify打包代码支持浏览器
node中通过 require('babel-core/register')支持es6语法
https://segmentfault.com/a/1190000012709705

node eventloop

Process.nextTick,setImmediate 和promise.then 的优先级
Process.nextTick,pronise, setImmediate的优先级

在node.js中

process.nextTick > Promise.then > setTimeout > setImmediate

avatar

从上图中,大致看出node中的事件循环的顺序:
外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(按照该顺序反复运行)...

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
  • idle, prepare 阶段:仅node内部使用
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调

在浏览器中

Promise.then > setImmediate > setTimeout > requestAnimationFrame

avatar

avatar

浏览器和node 事件循环

avatar

setTimeout(()=>{
  console.log('timer1')
  Promise.resolve().then(function() {
      console.log('promise1')
  })
}, 0)
setTimeout(()=>{
  console.log('timer2')
  Promise.resolve().then(function() {
      console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
      console.log('promise3')
})

浏览器端运行结果:promise3=>timer1=>promise1=>timer2=>promise2
因为整个大脚本是个宏任务,先执行大脚本宏任务,然后就执行代码里的promise微任务;

Node端运行结果分两种情况:

如果是node11版本一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列,这就跟浏览器端运行一致,最后的结果为timer1=>promise1=>timer2=>promise2
如果是node10及其之前版本:要看第一个定时器执行完,第二个定时器是否在完成队列中。

如果是第二个定时器还未在完成队列中,最后的结果为timer1=>promise1=>timer2=>promise2
如果是第二个定时器已经在完成队列中,则最后的结果为timer1=>timer2=>promise1=>promise2(下文过程解释基于这种情况下)

node事件循环:宏任务=>微任务
宏任务每次事件循环只执行一个,微任务每次事件循环全部执行

koa的原理

https://wiki.maoyan.com/display/~ousiwei/Koa

function compose (middleware) {
 return function (context, next) {
    // 记录上一次执行中间件的位置 #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      // 理论上 i 会大于 index,因为每次执行一次都会把 i递增,
      // 如果相等或者小于,则说明next()执行了多次
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      // 取到当前的中间件
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

koa-body原理

koa-body自己的代码其实没几行,先处理一堆参数(大多是传给co-body和formidable用的),然后用type-is这个包(ctx.is函数)判断出请求的数据类型,然后根据不同类型用co-body和formidable来解析,拿到解析结果以后放到body或者files里面,齐活。
https://blog.csdn.net/u011393161/article/details/104479860

使用过的koa2中间件

https://chenshenhai.github.io/koa2-note/note/route/koa-router.html

const Koa = require('koa');
const fs = require('fs');
const app = new Koa()
const Router = require('koa-router')

let home = new Router()

// 子路由
home.get('/', async (ctx) => {
  let html = `
   <ul>
      <li><a href="/page/helloworld">/page/helloworld</a></li>
      <li><a href="/page/404">/page/404</a></li>
    </ul>
  `
  ctx.body = html;
})

// 子路由2
let page = new Router();
page.get('/404', async ( ctx )=>{
  ctx.body = '404 page!'
}).get('/helloworld', async ( ctx )=>{
  ctx.body = 'helloworld page!'
});

let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())

// 加载路由中间件
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000, () => {
  console.log('[demo] route-use-middleware is starting at port 3000')
})

介绍自己写过的中间件

进程和线程

ruanyifeng.com/blog/2013/04/processes_and_threads.html

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

React

setState异步同步

https://zhuanlan.zhihu.com/p/39512941

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。

  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

react中this.state.count和 {count} = this.state的区别

http://www.ptbird.cn/react-hook-usestate-setState.html#menu_index_5
state 是 Immutable 的,setState 后一定会生成一个全新的 state 引用。
但 Class Component 通过 this.state 方式读取 state,这导致了每次代码执行都会拿到最新的 state 引用,所以快速点击4次的结果是 4 4 4 4。

react里的this绑定

https://juejin.im/post/5cc7ea71f265da03867e5dda
必须要用bind是因为类里的函数不能绑定this,onclick执行时类的函数的this会是undefined(严格模式)。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。

react生命周期

https://www.jianshu.com/p/514fe21b9914

shouldComponentUpdate(nextProps,nextState){
      if(nextState.Number == this.state.Number){
        return false
      }
  }

  class Child extends Component {
    constructor(props) {
        super(props);
        this.state = {
            someThings: props.someThings
        };
    }
    componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法 在componentWillReceiveProps方法中,将props转换成自己的state 在该函数(componentWillReceiveProps)中调用 this.setState() 将不会引起第二次渲染。
    //是因为componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引起state变化,从而引起render,此时就没必要再做第二次因重传props引起的render了,不然重复做一样的渲染了。
        this.setState({someThings: nextProps.someThings});
    }
    render() {
        return <div>{this.state.someThings}</div>
    }
}
  • componentWillReceiveProps(nextProps)
    此方法只调用于props引起的组件更新过程中,响应 Props 变化之后进行更新的唯一方式,参数nextProps是父组件传给当前组件的新props。但父组件render方法的调用不能保证重传给当前组件的props是有变化的,所以在此方法中根据nextProps和this.props来查明重传的props是否改变,以及如果改变了要执行啥,比如根据新的props调用this.setState出发当前组件的重新render

  • shouldComponentUpdate(nextProps, nextState)
    此方法通过比较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新停止,以此可用来减少组件的不必要渲染,优化组件性能。
    ps:这边也可以看出,就算componentWillReceiveProps()中执行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及当前组件的this.state的对比就一直是true了。

  • componentWillUpdate(nextProps, nextState)
    此方法在调用render方法前执行,在这边可执行一些组件更新发生前的工作,一般较少用。

  • render
    根据组件的props和state(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render) ,return 一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用),不能在里面执行this.setState,会有改变组件状态的副作用。

  • componentDidUpdate(prevProps, prevState)
    此方法在组件更新后被调用,可以操作组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state

  • componentWillUnmount
    此方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。

key diff操作

react和vue的区别

react和vue的区别
相同点:
• 都支持服务端渲染
• 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
• 数据驱动视图
• 都有支持native的方案,React的React native,Vue的weex
不同点:
• React严格上只针对MVC的view层,Vue则是MVVM模式
• virtual DOM 不一样 vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,
• 组件写法不一样 React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即”all in js” Vue 推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;
• 数据绑定:Vue有实现了双向数据绑定,React数据流动是单向的
• state对象在react应用中是不可变的,需要使用setState方法更新状态;在Vue中,state对象并不是必须的,数据由data属性在Vue对象中进行管理。

hook用法和原理思想

https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
http://www.ruanyifeng.com/blog/2019/09/react-hooks.html

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。)

只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook。你可以:

✅ 在 React 的函数组件中调用 Hook
✅ 在自定义 Hook 中调用其他 Hook (我们将会在下一页 中学习这个。)
遵循此规则,确保组件的状态逻辑在代码中清晰可见。

fiber

新旧生命周期(放弃)

从Mixin到HOC再到Hook

https://juejin.im/post/5cad39b3f265da03502b1c0a

mixin

缺点:
Mixin 可能会相互依赖,相互耦合,不利于代码维护
不同的Mixin中的方法可能会相互冲突
Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性

function setMixin(target, mixin) {
  if (arguments[2]) {
    for (var i = 2, len = arguments.length; i < len; i++) {
      target.prototype[arguments[i]] = mixin.prototype[arguments[i]];
    }
  }
  else {
    for (var methodName in mixin.prototype) {
      if (!Object.hasOwnProperty(target.prototype, methodName)) {
        target.prototype[methodName] = mixin.prototype[methodName];
      }
    }
  }
}
setMixin(User,LogMixin,'actionLog');
setMixin(Goods,LogMixin,'requestLog');

HOC

  • HOC可以实现什么功能:
  1. 组合渲染
function stylHOC(wrappedComponent) {
  return class extends Component {
    render() {
      return (
        <div>
          <div className="title">{this.props.title}</div>
          <WrappedComponent {...this.props} />
        </div>
      )
    }
  }
}
  1. 条件渲染
  2. 操作props
    HOC的实际应用:
    日志打点
    可用、权限控制
    双向绑定
    表单校验
  • context
    通过给 MessageList(context 的生产者)添加 childContextTypes 和 getChildContext,React 自动向下传递信息,子树上的所有组件(在这个例子中是 Button)可以通过定义 contextTypes 来访问 context。
  • 使用HOC的动机
  1. 回顾下上文提到的 Mixin 带来的风险:

Mixin 可能会相互依赖,相互耦合,不利于代码维护
不同的Mixin中的方法可能会相互冲突
Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性

avatar

  1. 而HOC的出现可以解决这些问题:

高阶组件就是一个没有副作用的纯函数,各个高阶组件不会互相依赖耦合
高阶组件也有可能造成冲突,但我们可以在遵守约定的情况下避免这些行为
高阶组件并不关心数据使用的方式和原因,而被包裹的组件也不关心数据来自何处。高阶组件的增加不会为原组件增加负担

  • HOC的缺陷
    HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难。
    HOC可以劫持props,在不遵守约定的情况下也可能造成冲突。

Hooks

// 模拟componentDidUpdate
// componentDidUpdate就相当于除去第一次调用的useEffect,我们可以借助useRef生成一个标识,来记录是否为第一次执行:
function useDidUpdate(callback, prop) {
  const init = useRef(true);
  useEffect(() => {
    if (init.current) {
      init.current = false;
    } else {
      return callback();
    }
  }, prop);
}
  1. 不要在循环,条件或嵌套函数中调用Hook。

实现tree组件

https://exp-team.github.io/blog/2017/04/05/js/react-tree-data/

Redux

基本组成和设计单向数据流

redux异步

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

浏览器和网络

1 浏览器与Node的事件循环(Event Loop)有何区别

https://juejin.im/post/5c337ae06fb9a049bc4cd218

进程与线程

进程是 CPU资源分配的最小单位;线程是 CPU调度的最小单位
avatar

进程好比图中的工厂,有单独的专属自己的工厂资源。

线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;

工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存。
多个工厂之间独立存在。

微任务API:queueMicrotask() 方法 。react setstate异步效果实现就是通过queueMicrotask();

补充

Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
avatar

2 webSocket和Socket

https://blog.csdn.net/wwd0501/article/details/54582912
https://www.ruanyifeng.com/blog/2017/05/websocket.html

网络

https://mp.weixin.qq.com/s/baMDhvKQXs7OAQUosfefgg

header

avatar

定长包体:(res/req)
content-length

不定长包体:(res/req)
Transfer-Encoding: chunked
表示分块传输数据,设置这个字段后会自动产生两个效果:
Content-Length 字段会被忽略
基于长连接持续推送动态内容

Accept-Ranges: none
前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头,用来告知客户端这边是支持范围请求的。

而对于客户端而言,它需要指定请求哪一部分,通过Range这个请求头字段确定,格式为bytes=x-y。接下来就来讨论一下这个 Range 的书写格式:

0-499表示从开始到第 499 个字节。
500- 表示从第 500 字节到文件终点。
-100表示文件的最后100个字节。

// 单段数据
Range: bytes=0-9
// 多段数据
Range: bytes=0-9, 30-39

XSS和CSRF

[浅说 XSS 和 CSRF · Issue #68 · dwqs/blog · GitHub|https://github.com/dwqs/blog/issues/68]

HTTP 中如何处理表单数据的提交?

在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:

application/x-www-form-urlencoded

multipart/form-data

由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中。

HTTP1.1 如何解决 HTTP 的队头阻塞问题

HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,但值得注意的是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是著名的HTTP队头阻塞问题。
解决方案:
并发连接
域名分片

Cookie和Session

cookie生命周期

  • Expires即过期时间
  • Max-Age用的是一段时间间隔,单位是秒,从浏览器收到报文开始计算。
    若 Cookie 过期,则这个 Cookie 会被删除,并不会发送给服务端

安全相关

如果带上Secure,说明只能通过 HTTPS 传输 cookie。

如果 cookie 字段带上HttpOnly,那么说明只能通过 HTTP 协议传输,不能通过 JS 访问,这也是预防 XSS 攻击的重要手段。
如果 cookie 字段带上HttpOnly,那么说明只能通过 HTTP 协议传输,不能通过 JS 访问,这也是预防 XSS 攻击的重要手段。

  • xss攻击
    XSS(Cross Site Scripting)即跨站脚本攻击,是一种代码注入攻击,攻击者通过提交包含特殊格式的数据,使数据中包含的代码得以在网站用户的浏览器上运行。
    通过运行这些代码,攻击者可以获取网站用户的隐私信息如cookie,授权认证信息等,进而危害信息安全。

从攻击者角度,根据产生XSS攻击的数据是否是持久存储的,可以分成存储型和反射型

cookie缺点

容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。

性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。但可以通过Domain和Path指定作用域来解决。

安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。

浏览器缓存

https://segmentfault.com/a/1190000006741200

1.web缓存原因:
1)请求更快;2)节省宽带;3)降低服务器压力。

2.缓存器分类:
1)服务器端:CDN缓存。
2)客户端:浏览器缓存:a)强缓存(expires和cache-control);b)协商缓存(lost-modified和Etag);

  • Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回,用GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2016 23:55:55 GMT。缺点:如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。
  • Cache-Control描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires,Cache-Control的缓存管理更有效,安全一些。

流程描述:

1)首先浏览器请求的时候会判断浏览器是否有缓存,如果有缓存会判断是否过期(expries和 catch-control的max-age)

  1. 如果没有过期则直接从缓存里获取;如果过期了,则就是强缓存没有命中,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回(304),但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;若未命中请求,则将资源返回客户端,并更新本地缓存数据(200)。 强缓存的参数是:(lost-modified和Etag),先Etag(if-none-match)再Last-Modified(if-modified-since);

跨域

浏览器遵循同源政策(scheme(协议)、host(主机)和port(端口)都相同则为同源)。非同源站点有这样一些限制:

  • 不能读取和修改对方的 DOM
  • 不读访问对方的 Cookie、IndexDB 和 LocalStorage
  • 限制 XMLHttpRequest 请求。(后面的话题着重围绕这个)

WebKit 渲染引擎和V8 引擎都在渲染进程当中。
当xhr.send被调用,即 Ajax 请求准备发送的时候,其实还只是在渲染进程的处理。为了防止黑客通过脚本触碰到系统资源,浏览器将每一个渲染进程装进了沙箱,并且为了防止 CPU 芯片一直存在的Spectre 和 Meltdown漏洞,采取了站点隔离的手段,给每一个不同的站点(一级域名不同)分配了沙箱,互不干扰。具体见YouTube上Chromium安全团队的演讲视频。

现在数据传递给了浏览器主进程,主进程接收到后,才真正地发出相应的网络请求。

在服务端处理完数据后,将响应返回,主进程检查到跨域,且没有cors(后面会详细说)响应头,将响应体全部丢掉,并不会发送给渲染进程。这就达到了拦截数据的目的。

HTTP三次握手

avatar
TCP三次握手

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据。这样就保证了,每次传送数据都会准确到达目标设备了。

TCP四次挥手

1.客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

2.服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

3.服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

4.客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

七层协议

https://blog.csdn.net/qq_18425655/article/details/52314970
avatar
avatar
avatar
avatar

HTTPS加密

图解http
157页
161-162页

HTTP2有哪些改进

头部压缩

多路复用

当然还有一些颠覆性的功能实现:

设置请求优先级

服务器推送

这些重大的提升本质上也是为了解决 HTTP 本身的问题而产生的。接下来我们来看看 HTTP/2 解决了哪些问题,以及解决方式具体是如何的。

头部压缩:

HPACK 算法是专门为 HTTP/2 服务的,它主要的亮点有两个:

  • 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。
  • 其次是对于整数和字符串进行哈夫曼编码,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。

多路复用

首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输01串,方便了机器的解析。

原来Headers + Body的报文格式如今被拆分成了一个个二进制的帧,用Headers帧存放头部字段,Data帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。

通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。

可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?

首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能。

avatar

输入地址到页面展示经过的步骤

(DNS, TCP握手, HTTP服务器)
1.你在浏览器输入一个URL地址;

2.浏览器查找域名的IP地址
DNS查找过程:1)浏览器缓存;2)操作系统缓存;3)路由器缓存;4)ISP DNS缓存——检查缓存ISP(互联网服务提供商)的DNS服务器。ISP的DNS服务器开始递归搜索,从根命名服务器,通过com顶级命名服务器,到Facebook的命名服务器。

3.浏览器发送一个http请求到web服务器(这里还有三次握手)

请求报文:(get和post区别)

 其中,Cookie(笔记链接)对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,保存有用户登录名、密码以及一些配置信息。一个request或者response同时使用多个Cookie。

4.Facebook服务器回复永久重定向
服务器发送一个301永久移除回复来告知浏览器定向于“http://www.facebook.com/” 而不是“http://facebook.com/”.

 重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)。

我们在网站建设中,时常会遇到需要网页重定向的情况:
1.网站调整(如改变网页目录结构);
2.网页被移到一个新地址;
3.网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。

5.浏览器重定向
浏览器重新发送URL为http://www.facebook.com/的get请求。

6.服务器的处理请求
服务器获得一个get请求,并处理回应这个请求。即,请求处理程序读取请求(其参数、cookies)。它将读取和更新一些数据存储在服务器上,然后,请求处理程序生成一个HTML响应。

7.服务器发回一个HTML响应
content-encoding header告知我们服务器发送的回复使用了gzip压缩,当我们解压缩后,就可以看到我们期望的HTML文件:

8.浏览器开始解析HTML文件。

9.浏览器在解析HTML文件的过程中又会碰到一些要求获得的其他URL,如获得HTML的过程一样,将发送get请求获得每一个文件。

10.浏览器发送异步请求(AJAX)
例如,Facebook聊天将会继续更新的列表你登录朋友来来去去。 更新登录好友列表,在浏览器中执行的JavaScript代码必须向服务器发送异步请求。 异步请求是通过编程构造GET或POST请求到一个特殊的URL。 在Facebook的例子中,客户端发送一个POST请求到http://www.facebook.com/ajax/chat/buddy_list.php去拿你的朋友在线列表。Facebook返回JavaScript代码片段来响应异步请求。
Facebook聊天提供了一个示例AJAX的一个有趣的问题:把数据从服务器推送到客户端。 因为HTTP是一个请求-响应协议,聊天服务器不能把新消息给客户端。 相反,客户机必须轮询服务器每隔几秒钟,以查看是否有任何新消息到达。

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 14, 2021

小程序 + 拓展

gpu加速

当发生座位图动画时,需要在动画结束时开启gpu加速,启用gpu加速,解决座位图放大后,ios设备锁屏开屏选座,选座图片不展示问题;

<view bindtransitionend="setOrigin"></view> 
  • bindtransitionend是在动画结束时触发。
setOrigin() {
  this.setData({ allowScroll: true });
  // 必须要分两步,因为要先设置可滑动,之后才能够scroll,如果写在一起会导致滑动不生效;
  setTimeout(() => {
    this.setState({
      gpuSpeed: true, // 启用gpu加速,解决座位图放大后,ios设备锁屏开屏选座,选座图片不展示问题
    })
  }, 50)
}
... ...
function hanleScale() {
  ... ...
  // gpu加速先关掉再运行动画,不然会闪烁
  context.setData({ gpuSpeed: false }, () => {
    context.setData({
      scaleFrom: scaleInfo.scaleTo,
      scaleInfo,
      allowScroll,
      originX,
      originY,
    });
  });
}

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 15, 2021


# 基础面试题

这里指技术面试,面试题收集自网络,修正补充部分试题答案。

PS. 这里省略了过时的swing(第57~71题)和jsp知识,有兴趣的可以自学了解!

1. 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。

Java被设计成允许应用程序可以运行在任意的语言,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。各个平台只要安装了Java虚拟机,即可运行Java程序。

 
2. JDK和JRE的区别是什么?

Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。
Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。


3. ”static”关键字是什么意思?Java中是否可以覆盖(override)一个static的方法?

“static”关键字表明一个变量或者方法,属于类而不是实例,可以在没有所属的类的实例的情况下被访问。

Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

 
4. 是否可以在static环境中访问非static变量?

static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

 
5. Java支持的数据类型有哪些?什么是自动拆装箱和拆箱?

Java语言支持的8中基本数据类型是:

  • byte 一个字节
  • short 两个字节
  • int 四个字节
  • long 八个字节
  • float 四个字节
  • double 八个字节
  • boolean 一个字节
  • char 两个字节

一个字节等于8个bit。

自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成double,等等。反之就是自动拆箱。


PS. Java采用unicode编码,2个字节(16位)来表示一个字符(https://baike.baidu.com/item/Unicode%E7%A0%81/7704811)
```java
    /**
     * 输入字符串,得到每个字符的Unicode码
     */
    public static void main(String[] args) {
        System.out.println("请输入字符串:");
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        //字符串转换为字符串数组
        char[] charArray = str.toCharArray();
        for (char c : charArray) {
            // char 长度2个字节,但数字字母空格等只用了一种1个字节(浪费了1个字节),中日韩文等才用满2个字节
            // (int)将字符强制类型转为int,toHexString再转为16进制(16进制每一位4bit,一个字节8bit,故16进制的2位表示一个字节),即为Unicode码
            String s = Integer.toHexString((int) c);
            System.out.println(" \" " + c + " \" " + "的Unicode码是:\\u" + s + ", 实际占用" + s.length()*4 + "bit,"+ ", " + s.length()/2 + "字节");
        }
        sc.close();
    }
  1. Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?
Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。
  1. Java中,什么是构造函数?什么是构造函数重载?
当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。
在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的无参构造函数,反之在程序员有给类提供构造函数的情况下,Java编译器则不会为这个类创建一个默认的构造函数。

Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。

  1. Java支持多继承么?
不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。
  1. 接口和抽象类的区别是什么?
Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:
接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类
类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。
  1. 什么是值传递和引用传递?
值传递:指在调用函数时将实际参数复制一份传递到函数参数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递:是指在调用函数时将实际参数的地址直接传递到函数参数中,那么在函数中对参数所进行的修改,将影响到实际参数。

对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。
 
对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

java中是没有指针的,java中只存在值传递。然而我们经常看到对于对象(数组,类,接口)的传递似乎有点像引用传递,可以改变对象中某个属性的值。但是不要被这个假象所蒙蔽,实际上这个传入函数的参数值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递。我们改变函数参数值(非参数值的某个属性的值),实际是改变了参数的指向地址,这并不会影响源对象。
  • 线程
  1. 进程和线程的区别是什么?
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
  1. 创建线程有几种不同的方式?你喜欢哪一种?为什么?
有三种方式可以用来创建线程:
继承Thread类
实现Runnable接口
应用程序可以使用Executor框架来创建线程池
 
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
  1. 概括的解释下线程的几种可用状态。
线程在执行过程中,可以处于下面几种状态:
就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O阻塞(Blocked on I/O):等待I/O操作完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。
  1. 同步方法和同步代码块的区别是什么?
在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。
  1. 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。
 
java中每个对象都有唯一的一个monitor
1.同时只能有一个线程可以获取某个对象的monitor
2.一个线程通过调用某个对象的wait()方法释放该对象的monitor并进入休眠状态,
直到其他线程调用该对象的notify()或者notifyAll()再次获取该对象的monitor
3.只有拥有该对象monitor的线程才可以调用该对象的notify()和notifyAll()方法
如果没有该对象monitor的线程调用了该对象的notify()或者notifyAll()方法将会抛出java.lang.IllegalMonitorStateException
  1. 什么是死锁(deadlock)?
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
  1. 如何确保N个线程可以访问N个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
  • Java集合类
  1. Java集合类框架的基本接口有哪些?
集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
 
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:
Collection:代表一组对象,每一个对象都是它的子元素。
Set:不包含重复元素的Collection。
List:有顺序的collection,并且可以包含重复元素。interface List<E> extends Collection<E>
Map:可以把键(key)映射到值(value)的对象,键不能重复。

扩展:请列举Java集合类!

  1. 为什么集合类没有实现Cloneable和Serializable接口?
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
  1. 什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的

迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。
  1. Iterator和ListIterator的区别是什么?
下面列出了他们的区别:
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
  1. Iterator快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响,故Iterator遍历同时可以删除元素。
java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
  1. Java中的HashMap的工作原理是什么?
Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。
HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
  1. hashCode()和equals()方法的重要性体现在什么地方?
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。
如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。
而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

equals相等的两个对象,hashCode也一定相等,反之则不一定。
  1. HashMap和Hashtable有什么区别?
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。
Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
一般认为Hashtable是一个遗留的类。

扩展:HashMap原理(面试重点):https://blog.csdn.net/zyjtoto/article/details/94174588

  1. 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
下面列出了Array和ArrayList的不同点:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
  1. ArrayList和LinkedList有什么区别?
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
也可以参考ArrayList vs. LinkedList。
  1. Comparable和Comparator接口是干什么的?列出它们的区别。
Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
 
Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

JAVA8中,Comparator 是函数式接口:@FunctionalInterface标记在接口上,“函数式接口”是指仅仅只包含一个抽象方法的接口。
  1. 什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
  1. 你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?
时间复杂度,用另一个(通常更简单的)函数来描述一个函数数量级的渐近上界。

大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。

大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
  1. 如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。
  1. Java集合类框架的最佳实践有哪些?
根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。
 
有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。
 
为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
 
使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。(不要求掌握)
 
编程的时候接口优于实现。
 
底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。
  1. Enumeration接口和Iterator接口的区别有哪些(不要求掌握)?
Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象(经验证,没有这个安全性,假的)。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
  1. HashSet和TreeSet有什么区别?
HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。
 
另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。
 
  • 垃圾收集器(Garbage Collectors)
  1. Java中垃圾回收有什么目的?什么时候进行垃圾回收?
垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
当对象变成(GC Roots)不可达时(没有被持有引用),即可以被垃圾回收。
  1. System.gc()和Runtime.gc()会做什么事情?
这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。
  1. finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
在释放对象占用的内存之前,垃圾收集器会调用对象的finalize()方法。一般建议在该方法中释放对象持有的资源。
Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行。不建议使用。
  1. 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
不会,在下一个垃圾回收周期中,这个对象将是可被回收的。
  1. Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?
JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。
 
堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。


永久代(Perm Gen space) 用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置
  1. 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。
  1. 在Java中,对象什么时候可以被垃圾回收?
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
  1. JVM的永久代中会发生垃圾回收么?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区
(译者注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

永久代的垃圾回收主要包括类型的卸载和废弃常量池的回收。
当没有对象引用一个常量的时候,该常量即可以被回收。
而类型的卸载更加复杂。必须满足一下三点,该类型的所有实例都被回收了,该类型的ClassLoader被回收了,该类型对应的java.lang.Class没有在任何地方被引用,在任何地方都无法通过反射来实例化一个对象 
  • 异常处理
  1. Java中的两种异常类型是什么?他们有什么区别?
Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。这里有Java异常处理的一些小建议。
  1. Java中Exception和Error有什么区别?
Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。
  1. throw和throws有什么区别?
throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。
  1. 异常处理的时候,finally代码块的重要性是什么?(译者注:作者标题的序号弄错了)
无论是否抛出异常,finally代码块总是会被执行。就算是没有catch语句同时又抛出异常的情况下,finally代码块仍然会被执行。最后要说的是,finally代码块主要用来释放资源,比如:I/O缓冲区,数据库连接。
  1. 异常处理完成以后,Exception对象会发生什么变化?
Exception对象会在下一个垃圾回收过程中被回收掉。
  1. finally代码块和finalize()方法有什么区别?
无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。
finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。
  • JDBC(先了解即可)
  1. 什么是JDBC?
JDBC是允许用户在不同数据库之间做选择的一个抽象层。JDBC允许开发者用JAVA写数据库应用程序,而不需要关心底层特定数据库的细节。
  1. 解释下驱动(Driver)在JDBC中的角色。
JDBC驱动提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection, Statement, PreparedStatement,CallableStatement, ResultSet和Driver。
  1. JDBC中使用Class.forName()加载驱动方法有什么作用?
这个方法用来载入跟数据库建立连接的驱动。
  1. PreparedStatement比Statement有什么优势?
PreparedStatements是预编译的,因此,性能会更好。同时,不同的查询参数值,PreparedStatement可以重用。
  1. 介绍下CallableStatement?
CallableStatement用来执行存储过程。存储过程是由数据库存储和提供的。
存储过程可以接受输入参数,也可以有返回结果。
不鼓励使用存储过程,因为它提供了安全性和模块化,但逻辑不透明,且降低了数据库的可移植性、可扩展性和可维护性。
准备一个CallableStatement的方法是:CallableStament.prepareCall();
  1. 数据库连接池是什么意思?
像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求。
  • 远程方法调用(RMI:remote method invoke)(先了解即可)
  1. 什么是RMI?
Java远程方法调用(Java RMI)是Java API对远程过程调用(RPC)提供的面向对象的等价形式,支持直接传输序列化的Java对象和分布式垃圾回收。远程方法调用可以看做是激活远程正在运行的对象上的方法的步骤。RMI对调用者是位置透明的,因为调用者感觉方法是执行在本地运行的对象上的。看下RMI的一些注意事项。
  1. RMI体系结构的基本原则是什么?
RMI体系结构是基于一个非常重要的行为定义和行为实现相分离的原则。RMI允许定义行为的代码和实现行为的代码相分离,并且运行在不同的JVM上。
  1. RMI体系结构分哪几层?
RMI体系结构分以下几层:
存根和骨架层(Stub and Skeleton layer):这一层对程序员是透明的,它主要负责拦截客户端发出的方法调用请求,然后把请求重定向给远程的RMI服务。
远程引用层(Remote Reference Layer):RMI体系结构的第二层用来解析客户端对服务端远程对象的引用。这一层解析并管理客户端对服务端远程对象的引用。连接是点到点的。
 
传输层(Transport layer):这一层负责连接参与服务的两个JVM。这一层是建立在网络上机器间的TCP/IP连接之上的。它提供了基本的连接服务,还有一些防火墙穿透策略。
  1. RMI中的远程接口(Remote Interface)扮演了什么样的角色?
远程接口用来标识哪些方法是可以被非本地虚拟机调用的接口。远程对象必须要直接或者是间接实现远程接口。实现了远程接口的类应该声明被实现的远程接口,给每一个远程对象定义构造函数,给所有远程接口的方法提供实现。
  1. java.rmi.Naming类扮演了什么样的角色?
java.rmi.Naming类用来存储和获取在远程对象注册表里面的远程对象的引用。Naming类的每一个方法接收一个URL格式的String对象作为它的参数。
  1. RMI的绑定(Binding)是什么意思?
绑定是为了查询找远程对象而给远程对象关联或者是注册以后会用到的名称的过程。远程对象可以使用Naming类的bind()或者rebind()方法跟名称相关联。
  1. Naming类的bind()和rebind()方法有什么区别?
bind()方法负责把指定名称绑定给远程对象,rebind()方法负责把指定名称重新绑定到一个新的远程对象。如果那个名称已经绑定过了,先前的绑定会被替换掉。
  1. 让RMI程序能正确运行有哪些步骤?
为了让RMI程序能正确运行必须要包含以下几个步骤:
编译所有的源文件。
使用rmic生成stub。
启动rmi registry。
启动RMI服务器。
运行客户端程序。
  1. RMI的stub扮演了什么样的角色?
远程对象的stub扮演了远程对象的代表或者代理的角色。调用者在本地stub上调用方法,它负责在远程对象上执行方法。当stub的方法被调用的时候,会经历以下几个步骤:
初始化到包含了远程对象的JVM的连接。
序列化参数到远程的JVM。
等待方法调用和执行的结果。
反序列化返回的值或者是方法没有执行成功情况下的异常。
把值返回给调用者。
  1. 什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。
  1. RMI中使用RMI安全管理器(RMISecurityManager)的目的是什么?
RMISecurityManager使用下载好的代码提供可被RMI应用程序使用的安全管理器。如果没有设置安全管理器,RMI的类加载器就不会从远程下载任何的类。
  1. 解释下Marshalling和demarshalling。
当应用程序希望把内存对象跨网络传递到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。
  1. 解释下Serialization和Deserialization。
Java提供了一种叫做对象序列化的机制,他把对象表示成一连串的字节,里面包含了对象的数据,对象的类型信息,对象内部的数据的类型信息等等。因此,序列化可以看成是为了把对象存储在磁盘上或者是从磁盘上读出来并重建对象而把对象扁平化的一种方式。反序列化是把对象从扁平状态转化成活动对象的相反的步骤。
  • Servlet
  1. 什么是Servlet?
Servlet是用来处理客户端请求并产生动态网页内容的Java类。Servlet主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。
  1. 说一下Servlet的体系结构。
所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者是间接实现这个接口,或者是继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,Servlet使用多线程可以并行的为多个请求服务。
  1. Applet和Servlet有什么区别?(因过时而废弃)
Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。
  1. GenericServlet和HttpServlet有什么区别?
GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。继承自GenericServlet的Servlet应该要覆盖service()方法。最后,为了开发一个能用在网页上服务于使用HTTP协议请求的Servlet,你的Servlet必须要继承自HttpServlet。这里有Servlet的例子。
  1. 解释下Servlet的生命周期。
对每一个客户端的请求,Servlet引擎载入Servlet,调用它的init()方法,完成Servlet的初始化。然后,Servlet对象通过为每一个请求单独调用service()方法来处理所有随后来自客户端的请求,最后,调用Servlet(译者注:这里应该是Servlet而不是server)的destroy()方法把Servlet删除掉。
  1. doGet()方法和doPost()方法有什么区别?
doGet:GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。
 
doPOST:POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。
  1. 什么是Web应用程序?
Web应用程序是对Web或者是应用服务器的动态扩展。有两种类型的Web应用:面向表现的和面向服务的。面向表现的Web应用程序会产生包含了很多种标记语言和动态内容的交互的web页面作为对请求的响应。而面向服务的Web应用实现了Web服务的端点(endpoint)。一般来说,一个Web应用可以看成是一组安装在服务器URL名称空间的特定子集下面的Servlet的集合。
  1. 什么是服务端包含(Server Side Include)?
服务端包含(SSI)是一种简单的解释型服务端脚本语言,大多数时候仅用在Web上,用servlet标签嵌入进来。SSI最常用的场景把一个或多个文件包含到Web服务器的一个Web页面中。当浏览器访问Web页面的时候,Web服务器会用对应的servlet产生的文本来替换Web页面中的servlet标签。
  1. 什么是Servlet链(Servlet Chaining)?
Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。
  1. 如何知道是哪一个客户端的机器正在请求你的Servlet?
ServletRequest类可以找出客户端机器的IP地址或者是主机名。getRemoteAddr()方法获取客户端主机的IP地址,getRemoteHost()可以获取主机名。看下这里的例子。
  1. HTTP响应的结构是怎么样的?
HTTP响应由三个部分组成:
 
状态码(Status Code):描述了响应的状态。可以用来检查是否成功的完成了请求。请求失败的情况下,状态码可用来找出失败的原因。如果Servlet没有返回状态码,默认会返回成功的状态码HttpServletResponse.SC_OK。
 
HTTP头部(HTTP Header):它们包含了更多关于响应的信息。比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式。如何在Serlet中检索HTTP的头部看这里。
 
主体(Body):它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。
  1. 什么是cookie?session和cookie有什么区别?
cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的cookie。下面列出了session和cookie的区别:
无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但是,session仍然是能够工作的,因为客户端无法禁用服务端的session。
在存储的数据量方面session和cookies也是不一样的。session能够存储任意的Java对象,cookie只能存储String类型的对象。
  1. 浏览器和Servlet通信使用的是什么协议?
浏览器和Servlet通信使用的是HTTP协议。
  1. 什么是HTTP隧道?
HTTP隧道是一种利用HTTP或者是HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把其他协议的请求掩盖成HTTP的请求就是HTTP隧道。
  1. sendRedirect()和forward()方法有什么区别?
sendRedirect()方法会创建一个新的请求,而forward()方法只是把请求转发到一个新的目标上。重定向(redirect)以后,之前请求作用域范围以内的对象就失效了,因为会产生一个新的请求,而转发(forwarding)以后,之前请求作用域范围以内的对象还是能访问的。一般认为sendRedirect()比forward()要慢。
  1. 什么是URL编码和URL解码?
URL编码是负责把URL里面的空格和其他的特殊字符替换成对应的十六进制表示,反之就是解码。

@AlexZ33
Copy link
Owner Author

AlexZ33 commented Apr 15, 2021

python

https://zhuanlan.zhihu.com/p/41199930

深拷贝和浅拷贝之间的区别是什么?

答:深拷贝就是将一个对象拷贝到另一个对象中,这意味着如果你对一个对象的拷贝做出改变时,不会影响原对象。在Python中,我们使用函数deepcopy()执行深拷贝,导入模块copy,如下所示:

>>> import copy
>>> b=copy.deepcopy(a)

而浅拷贝则是将一个对象的引用拷贝到另一个对象上,所以如果我们在拷贝中改动,会影响到原对象。我们使用函数function()执行浅拷贝,使用如下所示:

>>> b=copy.copy(a)

列表和元组之间的区别是?

答:二者的主要区别是列表是可变的,而元组是不可变的。举个例子,如下所示:

>>> mylist=[1,3,3]
>>> mylist[1]=2
>>> mytuple=(1,3,3)
>>> mytuple[1]=2
Traceback (most recent call last):
File "<pyshell#97>", line 1, in <module>
mytuple[1]=2

会出现以下报错:

TypeError: ‘tupleobject does not support item assignment

关于列表和元组的更多内容,可以查看这里:

https://data-flair.training/blogs/python-tuples-vs-lists/

解释一下Python中的三元运算子

不像C++,我们在Python中没有?:,但我们有这个:

[on true] if [expression] else [on false]

如果表达式为True,就执行[on true]中的语句。否则,就执行[on false]中的语句。

下面是使用它的方法:

>>> a,b=2,3
>>> min=a if a<b else b
>>> min

运行结果:

2
>>> print("Hi") if a<b else print("Bye")

运行结果:

Hi

在Python中如何实现多线程?

一个线程就是一个轻量级进程,多线程能让我们一次执行多个线程。我们都知道,Python是多线程语言,其内置有多线程工具包。

Python中的GIL(全局解释器锁)确保一次执行单个线程。一个线程保存GIL并在将其传递给下个线程之前执行一些操作,这会让我们产生并行运行的错觉。但实际上,只是线程在CPU上轮流运行。当然,所有的传递会增加程序执行的内存压力。

解释一下Python中的继承

当一个类继承自另一个类,它就被称为一个子类/派生类,继承自父类/基类/超类。它会继承/获取所有类成员(属性和方法)。

继承能让我们重新使用代码,也能更容易的创建和维护应用。Python支持如下种类的继承:

单继承:一个类继承自单个基类
多继承:一个类继承自多个基类
多级继承:一个类继承自单个基类,后者则继承自另一个基类
分层继承:多个类继承自单个基类
混合继承:两种或多种类型继承的混合
更多关于继承的内容,参见:

https://data-flair.training/blogs/python-inheritance/

Python中的字典是什么?

字典是C++和Java等编程语言中所没有的东西,它具有键值对。

>>> roots={25:5,16:4,9:3,4:2,1:1}
>>> type(roots)
<class 'dict'>
>>> roots[9]

运行结果为:

3

字典是不可变的,我们也能用一个推导式来创建它。

>>> roots={x**2:x for x in range(5,0,-1)}
>>> roots

运行结果:

{25: 5, 16: 4, 9: 3, 4: 2, 1: 1}

请解释使用*args和**kwargs的含义

当我们不知道向函数传递多少参数时,比如我们向传递一个列表或元组,我们就使用*args。

>>> def func(*args):
    for i in args:
        print(i)  
>>> func(3,2,1,4,7)

运行结果为:

3
 
2
 
1
 
4
 
7

在我们不知道该传递多少关键字参数时,使用**kwargs来收集关键字参数。

>>> def func(**kwargs):
    for i in kwargs:
        print(i,kwargs[i])
>>> func(a=1,b=2,c=7)

运行结果为:

a.1
 
b.2
 
c.7

请写一个Python逻辑,计算一个文件中的大写字母数量

>>> import os

>>> os.chdir('C:\\Users\\lifei\\Desktop')
>>> with open('Today.txt') as today:
    count=0
    for i in today.read():
        if i.isupper():
            count+=1
print(count)

运行结果:

26

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant