全称「块格式化上下文」(Block Formatting Context), 是一块独立的 css 作用域,外部如何变化都影响不到他。
网页加载过程大致分为 5 步:
其中第 4 步为排列布局,重排指的就是第 4 步重做;第 5 步是绘制,那重绘指的就是第 5 步重做。按照网页加载顺序,第 4 步之后执行第 5 步,所以重排必将触发重绘,而重绘不一定会触发重排。
重绘:修改元素字体颜色,背景颜色,阴影效果等
重排:位置发生改变,如margin
padding
width
。 删除增加dom
调用offsetHeight
等读取实时量值时
由于重排性能消耗大,那么减少重排次数也就成了一个优化页面的方法,下面总结几个常用方法:
@import
属于 css 语法,只能加载 css,link
是 html 语法,不仅可以加载 css,还可以加载网站 icon 等
类似 scss(sass3+),less 等预处理 css 语言,扩展了 css,具备变量
、mixins
、函数
, 嵌套
,带有作用域
,支持块注释 //
src 用于替代这个元素,而 href 用于建立这个标签与外部资源之间的关系。src 加载资源会阻塞页面,href 是加载并行。
src:
<img src="/img.jpg" />
<script src="/jquery.js"></script>
href:
<link rel="stylesheet" href="/main.css" /> <a href="/"></a>
margin
和padding
,但是垂直方向无法更改布局width
和height
属性无效相比于基本的 script 标签、都可以实现异步加载脚本
,只是执行脚本的时机不同,
立即执行
,可以在 dom 没完全加载执行,如果多个 script async,那么哪个先加载完就
执行哪个,适合加载无依
赖的 jsdom加载完成后
执行,如果多个 script defer,会严格按照他们的书写顺序依次执行
,适合加载互相依赖
的 js// 冷门深拷贝利用MessageChannel
function deepClone(obj) {
return new Promise(function (resolve, reject) {
var { port1, port2 } = new MessageChannel();
port1.onmessage = function (e) {
resolve(e.data);
};
port2.postMessage(obj);
});
}
// 利用weakmap解决了循环引用报错问题,完全版
function deepClone1(_target, map = new WeakMap()) {
if (!_target || typeof _target !== "object") {
return _target;
}
if (map.get(_target)) {
console.log("进入map判断");
console.log(map.get(_target));
return map.get(_target);
}
let res = Array.isArray(_target) ? [] : {};
console.log("map set");
console.log(_target, res);
map.set(_target, res);
for (let i in _target) {
console.log("循环");
if (typeof _target[i] === "object") {
console.log("是对象");
res[i] = deepClone1(_target[i], map);
} else {
console.log("不是对象");
res[i] = _target[i];
}
}
return res;
}
function debounce(event, delay) {
let timer = null;
return function (...args) {
// ...args用来接受fun原有的参数,比如事件就是event对象
clearTimeout(timer); // 利用clearTimeout的特性,处理delay内,再此触发函数不做处理
timer = setTimeout(() => {
event.apply(this, args);
}, delay);
};
}
function throttle(event, delay) {
let prev = Date.now();
return function (...args) {
// ...args用来接受fun原有的参数,比如事件就是event对象
let now = Date.now();
if (now - prev >= delay) {
event.apply(this, args);
prev = Date.now();
}
};
}
原型链继承: Cat 继承了 Animal,缺点:所有 Cat 实例都会共享原型链,不可以向 Animal 传参。
function Cat() {}
Cat.prototype = new Animal();
call 继承/构造继承: 解决上面原型链建成俩问题,但是只是 Cat 实例,不是 Animal 的实例
function Cat(name) {
Animal.call(this);
}
组合继承:也就是前面两个方法合并,解决上面两个继承的缺点,唯一的缺点调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
function Cat(name) {
Animal.call(this);
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; //修复构造函数指向
// 优点:
// 既是cat实例,又是Animal实例
// 没有共享原型方法问题,并可以传参数
jsonp: 利用 script 标签跨域,本质是加载一个外部 js
cors:全程跨域资源共享,服务端操作,把请求头的origin
的值取出,响应头会增加Access-Control-Allow-Origin
key 并把值设置为origin
取出的值也可以是通配符,浏览器就不会拦截响应了,从而解决跨域问题
postMassage:利用可访问服务资源的iframe
,和当前页面互相通信,传递消息,来实现跨域,postMassage
本质是两个页面的消息传递方法。
// 页面1(可以获取后端数据的)传递数据,第二个参数是url匹配规则,可以是通配符
window.frames[0].postMessage(data, "*");
// 页面2(不能接收后端数据的),接收页面1的数据
window.addEventListener("message", function (e) {
console.log(e.data);
});
服务器中转: 由于服务端没跨域,服务端代替客户端请求目标接口,并把响应数据返回给客户端
array
.fill(value, startIndex | 0, endIndex | array.length)
[(1, 2, 3, 4, 5)].fill("a", 1, 3);
// [1, "a", "a", 4, 5]
var str = "hello world";
str.charAt(1); //e 返回指定下标的字符
str.charcodeAt(1); //101 返回指定下标字符的字符编码
str.concat(123); // hello world123 连接字符串,生成新的
str.slice(0, 2); // he 截取字符串,参数为下标
str.substring(0, 2); // he 截取字符串,参数为下标
str.substr(0, 2); // he 截取字符串,第一个参数是起始下标,第二个参数为截取数量
str.indexOf("h", 0); // 0 查子字符下标,第一个参数是字符,第二个参数是起始下标
str.lastIndexOf("h", 0); // 0 查子字符下标,第一个参数是字符,第二个参数是结尾下标(和indexof区别)
str.toLowerCase(); // hello world 转小写
str.toUpperCase(); // HELLO WORLD 转大写
str.trim(); // 删除前后空格
str.split("e"); // ["h", "llo world"] 转数组,参数为分割符
str.replace("o", "a"); // hella world 替换字符,第一个参数可以是正则
str.match(/hello/g); // ["hello"] 匹配字符串返回一个数组,参数常是正则
当内存中的数据不需要时候,js 会自动使用垃圾回收算法释放掉
new Number(1) instanceof Number;
// true
1 instanceof Number;
// false
Object.prototype.toString.call(1)
// "[object Number]"
Object.prototype.toString.call('1')
// "[object String]"
Object.prototype.toString.call([1])
// "[object Array]"
// 如果修改原型
Object.prototype.toString = Array.prototype.toString
Object.prototype.toString.call([1,2,3])
// "1,2,3"
...
对象转字符串或者数字的过程:如果转换成字符串,会优先调用 toString 方法,转数字,会优先调用 valueOf 方法
相关面试题:
// 下面代码a在什么值情况下会输出1
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
// 解
var a = {
n : 0,
valueOf(){
return ++this.n;
}
};
对目标读取、函数调用等操作进行拦截,然后操作处理。它不直接操作对象,而是像代理模式。
let target = {
name: "Tom",
age: 24,
};
let handler = {
get: function (target, key) {
console.log("getting " + key);
return target[key]; // 不是target.key
},
set: function (target, key, value) {
console.log("setting " + key);
target[key] = value;
},
};
// target为目标对象,handler为代理处理
let proxy = new Proxy(target, handler);
优化了 object 内部操作,修改一些函数的返回值,如 Object.defineProperty,Reflect 与 Proxy 是一一对应的对象,是 Proxy 的方法也是 Reflect 的方法,Proxy 修改的过的方法可以在 Reflect 获取默认值。
// 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
Object.defineProperty(target, property, attributes) // 报错
Reflect.defineProperty(target, property, attributes // false
// 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
'assign' in Object // true
Reflect.has(Object, 'assign') // true
针对实现动画 setInterval 的优化方法,执行速率和设备屏幕帧率有关系,不需要调用者指定帧速率,交给浏览器自行决定最佳帧效率,每一帧只执行一次,多次改变触发一次重拍,这样不会引起丢帧现象,也不会导致动画卡顿
同步任务在执行栈,异步任务执行后回调区分微任务和宏任务放入对应的微宏队列中,等同步执行栈执行完毕,会先检查微任务队列,执行完毕后,检查宏任务队列,执行。其中 Promise.resolve 调用静态方法,会优先于正常的 promise 的 resolve 方法
异步微任务有哪些:
异步宏任务有哪些:
执行顺序: 同步放入执行栈 => 异步放入任务队列 => 异步区分微宏任务 => 微任务 => 宏任务
async/await,await 后的语句是同步的,在函数体内,await 下的语句会被放入 promise 回调函数体中,async 函数默认返回一个 promise 对象
经典面试题:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
解析:按照从上往下顺序先执行同步代码,前两个函数async1
和async2
是函数声明,忽略。然后第 1 个输出的是script start
,然后看到setTimeout
, 由于它是异步且属于宏任务,放到最后执行。然后看到 async1 函数被执行了,进入async1
函数体,第 2 个输出的是async1 start
, 看到 await 规则是:await 右的语句被同步执行,所以第 3 个输出的是async2
,await 下的语句(函数体内)被当作 async 函数返回一个 promsise 的回调函数体的语句去处理,且被放入了异步任务队列中,等待中。然后跳出 async1 函数体,继续执行同步代码,遇到 new Promise,第 4 次输出promise1
,看到 resolve,就把 then 中回调,放入任务队列中,然后继续向下执行同步代码,第 5 次输出的最后的script end
。至此同步执行栈空了,开始把任务队列中的上下文拿到执行栈执行,由于队列先进先出,所以第一个被入栈的是 async1 的回调,也就是第 6 次输出的是async1 end
,然后把第二个队列中的上下文放入执行栈,也就是 promise 的 resolve,所以第 7 次输出promise2
,至此微任务执行完成,现在执行宏任务,也就是 timeout,所以第 8 次输出为setTimeout
。
结果:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
先来瞅一段代码:
function createIterator(items) {
var i = 0;
return {
// 返回一个迭代器对象
next: function () {
// 迭代器对象一定有个next()方法
let done = i >= items.length;
let value = !done ? items[i++] : undefined;
return {
value: value,
done: done,
};
},
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
这就是迭代器,迭代器是一个特殊对象,可以通过每调用一次 next,获取下一项结果。可以用迭代器替代 for 循环,因为 for 通过下标获取,多层嵌套比较复杂。
// 用迭代器模拟for
var colors = ["red", "green", "blue"];
var iterator = createIterator(colors);
while (!iterator.next().done) {
console.log(iterator.next().value);
}
迭代器模拟 for 的方法看起来太麻烦了,不如 for 来的方便,这个时候可以用生成器
(* 和 yield)来快速生成迭代器。
function* createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
通过*
和yield
关键字创建,*
来表示这个函数内部是可以用 yield 返回每次迭代的值,和 async、await 一样。需要组合使用,yield
后面接每次迭代的值。这样就可以外部调用.next()获取下一个值。
在 es6 中,数组、Set、Map、字符串集合,都属于可迭代对象,内部都有默认迭代器,可通过object[Symbol.iterator]()
访问默认迭代器。
es6 中也提供了一个通过方法来快速使用迭代器
for(item of 集合迭代器) {...}
常见的迭代器和得到 item 的值:
什么是事件委托:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
什么是冒泡和捕获:其实都是事件传播路径。冒泡:从外到事件源进行事件传播;捕获:从内向事件源进行事件传播;
DOM 标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发 dom 事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。addEventListener
事件监听方法的第三个参数默认值是false
,表示冒泡阶段调用,true
为捕获阶段调用
<body>
<button>append</button>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</body>
window.onload = function () {
document.querySelector("button").onclick = function (e) {
let li = document.createElement("li");
li.innerHTML = "5";
document.querySelector("ul").appendChild(li);
};
document.querySelector("ul").onclick = function (e) {
// li 新增的li也是可以被点击的
console.log(e.target.nodeName.toLowerCase());
};
};
addeventListener 和 onclick 不同点:
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
let objProto = obj.__proto__;
while(objProto) {
if (objProto === ctor.prototype) {
return true;
}
objProto = objProto.__proto__;
}
return false;
}
var Food = function() {};
var Meat = function() {};
Meat.prototype = new Food();
var meat = new Meat();
newInstanceof(meat, Meat) // true
newInstanceof(meat, Food) // true
meat instanceof Meat // true
meat instanceof Food // true
总的来说就是meat.__proto__.constructor === Meat
,由于原型链是一条链,所以循环所有__proto__
;
直到null
为止,符合Meat.prototype === meat.__proto__
即返回true
,否则返回false
。
class
是ES6
新增的关键字,为了看起来更像java
等面向对象语言,实际上是一个语法糖,等同于ES5
的function
来创建构造函数。
constructors
是 class 中默认的一个关键字,会自动调用该方法,class
中必须要有constructor
,如果没有,会被默认添加。当 new 时,constructor
会被执行返回一个 this,就是实例。class
有继承时,在constructor
内部必须执行一遍 super()函数,B 继承 A,super
相当于A.prototype.constructor.call(this, props)
,另外 super()也指向父亲 A 的原型对象, 详细解释。如果当前类和父类都有同名方法,如何区分地获取呢,获取父类:super().fun,获取当前类:fun父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount->子 mounted->父 mounted
子组件更新:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新:父 beforeUpdate -> 父 updated
销毁:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
总结带 beofre 的生命周期都是从父到子调用,带 ed 的生命周期都是从子到父
14 年前,前端路由都是用 hash 的方式实现的,地址长这个样子url#hash, 修改hash
部分不会重载这个页面,每次 hash 改变,可以用 window 的hashchange
方法来监听,通过这个方法就可以实现前端路由了
在 14 年,HTML5 标准发布,window 上新增了两个 api:pushState
和replaceState
,也 hash 一样,不刷新来实现前端路由,而且少了#更美观了,由于没#,所以刷新时候,默认会从服务器下载对应 url 资源,这个时候我们需要改下服务器配置,都加载前端 index.html 文件,来实现前端路由
// 第一个参数是state,用于描述一些特性,会被放入历史栈中,开发者使用
// 第二个参数是title,表示新页面的标题,浏览器都会忽略这个参数
// 第二个参数是url,表示新页面的相对地址
// 跳转
window.history.pushState(null, null, "/profile");
window.history.replaceState(null, null, "/profile");
和pushState的区别是不会把记录放入历史栈中;
slot
标签中<child>父给子的内容</child>
// child组件中, 父给子的内容会显示slot标签里
<slot>父给子的内容</slot>
<div slot="down"></div>的内容
会被显示到<slot name="down"></slot>里
// 父亲中
<div v-slot:data="{data}"></div>
// 组件中就可以用data了
<myslot v-slot="data">
{{ slotProps.user.firstName }}
</myslot>
keep-live 是 vue 提供的内置缓存组件,可以保存组件状态,避免重新渲染,keep-alive 有三个参数:
<!-- 组件name为a或者b的组件缓存 -->
<keep-alive include="a,b">
<component></component>
</keep-alive>
<!-- 组件name为c的组件不缓存 -->
<keep-alive exclude="c">
<component></component>
</keep-alive>
缓存组件会多出两个生命周期,activated
和deactivated
,渲染和销毁
keep-alive 可配合 router-view 来实现组件缓存
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
export default new Router({
routes: [
{
path: '/',
name: 'a',
component: a,
meta: {
keepAlive: false // 不缓存
}
},
{
path: '/b',
name: 'b',
component: b,
meta: {
keepAlive: true // 缓存
}
}
]
})
nextTick 可以获取下次最新 dom 的方法,因为 vue 的 dom 更新策略是异步的,只有一个事件循环结束后才会被应用来更新 dom,这导致数据的修改不会立即表现在 dom 上,如果要获取更新后的 dom 状态,这时候就需要用到 nextTick
一些场景:
vue mixin 是针对组件间功能共享来做的。可以对组件的任意部分进行 mixin,也就是说不同的组件,想使用同一个数组,或者方法,那么 mixin 就是来做这个事的,把公共部分提取出来作为 mixin
声明:
// 声明
const toggle = {
data() {
isshowing: false;
},
methods: {
toggleShow() {
this.isshowing = !this.isshowing;
},
},
};
// 使用,toggle的方法会自动合并到当前组件中
mixins: [toggle];
核心概念:
// 声明
import Vuex from 'vuex'
const store = new Vuex.Store({
state: {
count: 0
},
getter: {
doneTodos: (state, getters) => {
return state.todos.filter(todo => todo.done)
},
count: (state) => {
return state.count
}
},
mutations: {
increment (state, payload) {
state.count++
}
},
actions: {
addCount(context) {
// 可以包含异步操作
// context 是一个与 store 实例具有相同方法和属性的 context 对象
}
}
})
// 使用
// 调用mutation
this.$store.commit('increment', 10)
// 调用actions
this.$store.dispatch('increment', 10)
// 利用mapGetters mapGetters mapMutations mapActions便捷使用
computed: {
...mapGetters(['audio']),
...mapState({
this.count 等同于 state.count + this.localCount
count(state) {
return state.count + this.localCount
}
},
methods: {
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为`this.$store.commit('increment')`
}),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
react 的响应式是使用 diff 算法实现的。react 在 state 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分。这个过程是递归的,react 会以当前组件为根,递归比较所以子节点。为了优化性能,React 提供了 shouldComponentUpdate 生命周期方法,这个方法有两个参数,nextProps和nextState,表示上次 props 和上次 stage,返回值是布尔值,如果这个方法返回 false,react 就跳过这个组件,不做 VDOM 比较,也不更新组件。
在 react16+版本,对 diff 过程进行了优化,从原来的 diff 树结构+递归的方式优化成 fiber 双向链表+循环执行。整个的 diff 过程分为 render 阶段和 commit 阶段,render 就是对比过程,找到需要改变 dom 的那部分,commit 就是执行改变 dom 的那部分,render 阶段是可以分段的(中断,然后继续完成),类似原生的requestIdleCallback方法,在每个 16ms 帧内,会先执行用户输入,事件回调等优先级高的 js 任务。之后剩下的时间执行分段的 render 部分来进行数据更新,保证视觉上的页面流程度。
分为 强缓存 和 协商缓存,如果同时存在,强缓存 优先 协商缓存
Cache Control 其他的 value:
当 Cache Control 设置为 No-Cache 时使用
附上一张说明图:
https 是 http 的安全扩展,相当于 HTTP + SSL(TLS) = HTTPS。SSL 即安全套接字层,它在 OSI 七层网络模型中处于第五层即网络层,TLS 是 SSL 的新版本,最后一个 SSL 3.0 版本在 2015 年废弃,之后就是 TLS,现在常见的 TLS 版本是 1.2 和 1.3 版本。那么 TLS 是如何实现的呢?
http1.1 长连接:同一个域名访问同一个文件的多个请求都可以复用一个 tcp 连接(不用像 1.0 一样 每次请求都需要重新建立连接,但不是并行,只能按顺序执行。
http2 多路复用:同一个域名访问多个文件的多个请求也可以复用一个 tcp 连接,且多个请求可以被并行处理。
会自动到 package.json 找到对应的命令执行。如果没有全局命令,会自动到node_modules/.bin/
下找到对应可执行文件执行,这时npm run xxx
等同于直接调用./node_modules/.bin/xxx
dns 解析:按照这个步骤查询,找到 ip,立即中断返回给客户端(浏览器缓存 => 操作系统缓存(host) => 路由器缓存 => 本地服务器缓存 => isp dnf 服务器查询 => 根 dns 服务器)
tcp 三次握手
发送 http 请求报文:报文包括:请求行,请求头,请求体
服务端接收到请求: 进入 web 服务器,如 apache、nginx,然后进入后台程序
服务器响应报文:报文包括:响应行,响应头,响应体
浏览器渲染页面
数据传送完成,四次挥手关闭:
页面中一些大资源加载较慢,比如 iframe,img。针对这个问题可以通过懒加载解决。
拿img
举例,先设置img
的src
属性为一个默认的资源,然后把真实url
放在标签的某个属性中。并设置好图片的宽高,避免完成加载时出现页面闪动问题。
然后实时监听页面滚动,如果当前图片是可见状态,那么把图片的真实 url 属性值取出替换到默认src
上来实现加载图片资源,从而提升页面打开速度和合理使用客户端流量。
// 获取浏览设备的网页可视高度
let clientHeight = document.documentElement.clientHeight;
// 获取已被滚出的页面高度
let scrollTop = document.documentElement.scrollTop;
// 图片顶部到网页顶部的高度
let imgOffsetTop = document.querySelector("img").offsetTop;
// 判断img在可视区域内:
imgOffsetTop < clientHeight + scrollTop;
commonjs:
es6:
二分查找:也称折半查找(Binary Search),它是一种效率较高的查找方法,前提是数据结构必须先排好序,可以在数据规模的对数时间复杂度内完成查找。但是,二分查找要求线性表具有有随机访问的特点(例如数组),也要求线性表能够根据中间元素的特点推测它两侧元素的性质,以达到缩减问题规模的效果。
贪心算法:又名贪婪法,是寻找最优解问题的常用方法,这种方法模式一般将求解过程分成若干个步骤,但每个步骤都应用贪心原则,选取当前状态下最好/最优的选择(局部最有利的选择),贪图眼前局部的利益最大化。
回溯算法:实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
分治算法:分治法是构建基于多项分支递归的一种很重要的算法范式。字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
动态规划:动态规划是通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。动态规划往往用于优化递归问题,例如斐波那契数列,如果运用递归的方式来求解会重复计算很多相同的子问题,利用动态规划的思想可以减少计算量。
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。简单来说就是一种描述源代码的结构,用于被计算机识别到,并成语言引擎执行
工作原理大致可以理解成,字符串输入
=>转换
=>输出
面试题 · 2020年12月21日 · 被浏览 - 次