刷面试题
刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
前端字节跳动真题解析
-
【269页】前端大厂面试题宝典
最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。
} // 这里,some_integer 移出作用域。不会有特殊操作
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 移给 s1
let s2 = String::from(“hello”); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 移出作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 将返回值移动给
// 调用它的函数
let some_string = String::from(“hello”); // some_string 进入作用域.
some_string // 返回 some_string 并移出给调用的函数
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?这里就需要引用和借用(可以理解为c里面的指针)。
fn main() {
let s1 = String::from(“hello”);
let len = calculate_length(&s1);
println!(“The length of ‘{}’ is {}.”, s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
// 尝试修改,那指定是不行的
fn main() {
let s = String::from(“hello”);
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(“, world”);
}
函数调用了引用类型的引用,在函数体中使用该变量被称之为借用,那么又有一个问题了,你不让我改,我就是想改,诶,就是玩!那么这时候需要引入一个新的概念:可变引用
fn main() {
let mut s = String::from(“hello”);
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(“, world”);
}
不过可变引用有一个很大的限制:在特定作用域中的特定数据只能有一个可变引用,并且可变引用和不可变引用不应该同时存在(这两是互斥的关系)。
// 会报错
let mut s = String::from(“hello”);
let r1 = &mut s;
let r2 = &mut s;
println!(“{}, {}”, r1, r2);
// 会报错 too
let mut s = String::from(“hello”);
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!(“{}, {}, and {}”, r1, r2, r3);
这个限制的好处是 Rust 可以在编译时就避免数据竞争,可以理解为类似于分布式锁的玩意儿~ 数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码 注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用在声明可变引用之前,所以如下代码是可以编译的:
let mut s = String::from(“hello”);
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!(“{} and {}”, r1, r2);// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!(“{}”, r3);
但是虽然可以编译,这样书写是绕不过静态类型检查的!!!!!!
相信大家发现了上面的string类型有些特殊,不是说string是"值类型"吗?为什么他又可以用引用类型来表示呢?string使用了没有所有权的特殊的引用类型slice,slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。
let s = String::from(“hello”);
let slice = &s[0…2];
let slice = &s[…2];
对于"值类型"的string
let s = “Hello, world!”;
这里 s 的类型是 &str:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str 是一个不可变引用。
悬垂引用 悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态 例如以下代码:
fn main() {
let reference_to_nothing = dangle();
}
// wrong
fn dangle() -> &String {
let s = String::from(“hello”);
&s
}
//safe
fn safe() -> String {
let s = String::from(“hello”);
s
}
因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String,这可不对!Rust 不会允许我们这么做 总结两条规则
-
在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
-
引用必须总是有效的。
到这里和JavaScript有联系的,并且基础的就分享的差不多了,隐约记得鲁迅说过,如果你对一门语言,了解了其基本的语法,能够编写对应的简单的代码来实现简单的功能,那么你就入门了。后续的包括以下部分,就先按下不表
-
Cargo : Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。类似于JS使用的npm/pnpm/yarn
-
常见集合:Hashmap(类似于js中的map),Vector(类似于js中的数组),String
-
错误处理:panic(Throw Error 完全阻塞了程序执行) Result(类似于warning 可以报错但是不影响程序的执行)
-
…
最后总结一下rust我认为最令人称道的两点
-
丰富而强大的类型系统
-
可信赖的所有权模型
Rust and WebAssembly
====================
上面讲了半天rust,他只是我们今天的猪脚之一,那么今天的猪脚还有哪位呢?没错,就是 WebAssembly。那么WebAssembly到底是什么呢?在说这个之前先康康JavaScript的是怎么进行编译的 这就不得不说到两种编译方式了
- AOT: Ahead-of-Time compilation
必须是强类型语言,编译在执行之前,编译直接生成CPU能够执行的二进制文件,执行时CPU不需要做任何编译操作,直接执行,性能最佳,比如C/C++,Rust
- JIT: Just-in-Time compilation
没有编译环节。执行时根据上下文生成二进制汇编代码,灌入CPU执行。JIT执行时,可以根据代码编译进行优化,代码运行时,不需要每次都翻译成二进制汇编代码,V8就是这样优化JavaScript性能的。
举个例子,如果使用var来声明一个变量,不使用Typescript等类型系统来限定,一个变量,在多次编译的时候得到的变量的类型可能会不一样,这就导致了每一次JavaScript在执行的时候可能都会被重新编译,这就是类型系统的重要性,不仅能减少bug的发生也可以让我们的代码跑得更快
详细的说一下这个过程也就是
-
代码文件会被下载下来。
-
然后进入Parser,Parser会把代码转化成AST(抽象语法树).
-
然后根据抽象语法树,Bytecode Compiler字节码编译器会生成引擎能够直接阅读、执行的字节码。
-
字节码进入翻译器,将字节码一行一行的翻译成效率十分高的Machine Code.
有同学可能会问:JavaScript不是可以使用Typescript进行静态类型检查吗?为什么不能在编译时编译成可执行的二进制文件呢?盲生,你发现了华点!Typescript说白了也只是给JavaScript打上了补丁,但是JavaScript还是那个JavaScript,说不定在有生之年可以看见JavaScript的整个内核被重写呢?Wasm:那我走?
回到正题,既然JavaScript的内核变化的几率不大,那我们该如何进行优化呢?一个思路就是可以直接把 C、C++、Rust等语言编译成 WebAssembly 并能在浏览器中运行,但是有一点需要注意,使用wasm并不是完全舍弃掉了JavaScript,这两者实际上是相辅相成的关系,在实际的应用场景中Rust和JavaScript往往是互相调用包来开发一个web应用。
WebAssembly是一份字节码标准,以字节码的形式依赖虚拟机在浏览器中运行。万维网联盟(W3C)2019年12月5日宣布,WebAssembly 核心规范 现在是一种正式的 Web 标准,它为 Web 发布了一种功能强大的新语言。WebAssembly 是一种安全、可移植的低级格式,能够在现代处理器(包括 Web 浏览器)中高效执行并紧凑地表示代码。它也被设计为可以与JavaScript共存,允许两者一起工作。这样说大家可能云里雾里的,那么换个方法 我们每天都在接触各种业务,那大家有没有想过从我们写下JavaScript代码开始,到底发生了什么?就只看JavaScript大致是这样一个过程:
业务代码 -> v8 解析 -> 得到编译结果(字节码) -> 线程通信 -> 通知GPU绘制 -> 渲染
那如果我们使用了WebAssembly,那又是一个什么过程呢?
业务代码 -> 编译 -> 字节码 -> 线程通信 -> 通知GPU绘制 -> 渲染
可以看出,这两个链路最大的区别就是,在第二种链路中,浏览器(V8)所得到的东西,已经是一份可以执行的字节码了,他只需要执行就完事了,而不需要使用大量的CPU来对可能很复杂的源代码来进行编译。(当然也可以使用worker 这里就不做讨论了) 但是纯纯的字节码指定是不行的,C/C++,Rust可能都有自己的一套规范,所以这就需要一套规范来整合一下,让大家都可以愉快的在浏览器中玩耍,这可以说就是WebAssembly,由他的标准可以生成后缀名为.wasm的文件,可以直接交给浏览器执行 目前主流的浏览器都已经支持了WebAssembly。除此之外 ,依照wasm的特性,个人认为或者wasm未来在多端也能有一定的用处
实战
–
俗话说的好,纸上得来终觉浅,绝知此事要躬行,上面简单学习了rust+wasm,那如果不实践一下那不是浪费了吗,那到底怎么实践rust+wasm呢?自己看着wasm的文档写?那指定是不行的。那怎么办呢?不要慌,今天的第三位猪脚出现了:Yew 文档在此yew中文文档简介如下Yew 是一个设计先进的 Rust 框架,目的是使用 WebAssembly 来创建多线程的前端 web 应用。
-
基于组件的框架,可以轻松的创建交互式 UI。拥有 React 或 Elm 等框架经验的开发人员在使用 Yew 时会感到得心应手。
-
高性能 ,前端开发者可以轻易的将工作分流至后端来减少 DOM API 的调用,从而达到异常出色的性能。
-
支持与 JavaScript 交互 ,允许开发者使用 NPM 包,并与现有的 JavaScript 应用程序结合。
让一个yew应用跑起来分三步(确信)
- 创建一个二进制项目
cargo new --bin yew-app && cd yew-app
-
编写代码,注意要编写index.html
-
启动
cargo install trunk wasm-bindgen-cli
rustup target add wasm32-unknown-unknown
trunk serve
一张图简述一下wasm-bindgen的作用
组件化
页面展示的代码
use yew::prelude:😗;
enum Msg {
AddOne,
}
struct Model {
link: ComponentLink,
value: i64,
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink) -> Self {
Self {
link,
value: 0,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::AddOne => {
self.value += 1;
true
}
}
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
{ "tandake is a Vegetable Chicken" }
{ self.value }
}
}
}
fn main() {
yew::start_app::();
}
效果演示
可以看出,这里的渲染的文件来源已经是wasm了 眼尖的同学可能已经发现了上面的create,update ,change几个函数,那么他们是用来干嘛的呢?简单说一下yew中组件的生命周期:Component 特质定义了六个生命周期函数。
-
create 是一个构造函数,接收道具和ComponentLink
-
view 渲染该组件
-
update 当一个Message 被发送到该组件时被调用,实现消息传递的逻辑
-
change 重新渲染变化,优化渲染速度
-
rendered 在view 之后但在浏览器更新之前被调用一次,以区分第一次渲染和连续渲染。
-
destroy ,当一个组件被卸载并需要进行清理操作时被调用。
如果把他类比成react的类组件,那么create就是constructor构造函数,update就是相当于注册在组件内部的一些静态方法,change相当于shouldcomponentupdate,其他的生命周期也可同比
父子组件中通信
前文说到yew是基于组件的,那么父子组件该怎么进行最简单的数据通信呢?声明父组件
#[derive(Clone, PartialEq, Properties, Default)]
struct Properties {
name: String,
}
enum Message {
ChangeName(String),
}
struct Model {
link: ComponentLink,
props: Properties,
}
impl Model {
fn change_name(&mut self, name: String) {
self.props.name = name;
}
}
impl Component for Model {
type Message = Message;
type Properties = Properties;
fn create(_props: Self::Properties, link: ComponentLink) -> Self {
Self {
link,
props: Properties {
name: “tandake”.to_string(),
},
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Message::ChangeName(name) => {
self.change_name(name);
}
};
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
{ "大家好,我是练习时长两天半的rust实习生,谭达科" }
{"hello "}{self.props.name.clone()}
}
}
}
声明子组件
#[derive(Clone, PartialEq, Properties, Default)]
struct ButtonProperties {
onclick: Callback,
}
enum ButtonMessage {
ChangName,
}
struct Button {
props: ButtonProperties,
link: ComponentLink,
}
impl Button {
fn change_name(&mut self) {
self.props.onclick.emit(“is a vegetableChicken”.to_string());
}
}
impl Component for Button {
type Message = ButtonMessage;
type Properties = ButtonProperties;
fn create(props: Self::Properties, link: ComponentLink) -> Self {
Self { props, link }
}
fn update(&mut self, msg: Self::Message) -> bool {
match msg {
ButtonMessage::ChangName => {
self.change_name();
}
};
true
}
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
}
}
}
演示效果
声明一个组件需要一些什么东西呢?从上面这个简单的demo可以看出一个大概
- 定义属性结构
#[derive(Clone, PartialEq, Properties, Default)]
- 把属性附加到状态
struct Button {
props: ButtonProperties,
link: ComponentLink,
}
- 初始化组件的状态
fn create(props: Self::Properties, link: ComponentLink) -> Self {
Self { props, link }
}
- 初始化生命周期,在update中接受事件,在change中重新渲染
fn update(&mut self, msg: Self::Message) -> bool {
match msg {
ButtonMessage::ChangName => {
self.change_name();
}
};
true
}
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
}
}
- 需要交互注册自定义事件
impl Button {
fn change_name(&mut self) {
self.props.onclick.emit(“is a vegetableChicken”.to_string());
}
}
这种写法与现在我们使用的react的写法是比较类似的(当然也可以使用vue的emit的方式)
函数式组件
上诉yew的组件多多少少和类组件比较像,那么yew可不可以使用一种类似函数式组件的方法?甚至使用hooks呢?当然可以 下面我们来实现一个简单的点击计数器(效果和第一个类似,就不再赘述了)
#[derive(Properties, Clone, PartialEq)]
pub struct RenderedAtProps {
pub time: String,
}
#[function_component(App)]
fn app() -> Html {
文末
如果30岁以前,可以还不知道自己想去做什么的话,那30岁之后,真的觉得时间非常的宝贵,不能再浪费时间在一些碎片化的事情上,比如说看综艺,电视剧。一个人的黄金时间也就二,三十年,不能过得浑浑噩噩。所以花了基本上休息的时间,去不断的完善自己的知识体系,希望可以成为一个领域内的TOP。
同样是干到30岁,普通人写业务代码划水,榜样们深度学习拓宽视野晋升管理。
这也是为什么大家都说30岁是程序员的门槛,很多人迈不过去,其实各行各业都是这样都会有个坎,公司永远都缺的高级人才,只用这样才能在大风大浪过后,依然闪耀不被公司淘汰不被社会淘汰。
269页《前端大厂面试宝典》
包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
前端面试题汇总
JavaScript