2023年最全前端React18面试题考点

Posted 参宿7

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023年最全前端React18面试题考点相关的知识,希望对你有一定的参考价值。

目录

React18

特点

声明式编码

单向数据流 

组件化

虚拟DOM(Virtual Dom)(同Vue)

Diff算法(同Vue)

组件属性

props

state

refs

总结

受控组件和非受控组件

事件event

事件处理的几种方法

事件中this的处理

事件传参处理

鼠标事件 mouseenter与mouseover区别

跨组件通信

生命周期

状态提升

复用组件

Render Props模式

HOC高阶组件模式

Hooks

useState

useReducer

useEffect

useRef

useCallback和useMemo

自定义Hook

StrictMode严格模式

Router

路由模式(同Vue)

基础路由搭建

重定向路由

自定义全局守卫

动态路由

Redux状态管理库

应用

React脚手架

Angular,Vue,React对比

Angular

React和Vue

MVC、MVP、MVVM模式

MVC (Model View Controller)

MVP(Model View Presenter)

MVVM (Model View View Model)


React18

React是用来构造用户界面的JS库 

在React18中,需要使用两个文件来初始化框架:

  • react.development.js 或 react模块 -> 生成虚拟DOM

  • react-dom.development.js 或 react-dom/client模块 -> Diff算法 + 处理真实DOM

下面就是初始化React程序的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
    
  <script src="../react.development.js"></script>
  <script src="../react-dom.development.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>
    // React对象 -> react.development.js(产生虚拟DOM)
    // ReactDOM对象 -> react-dom.development.js(渲染成真实DOM)
    //原生DOM获取
    let app = document.querySelector('#app');
    // root根对象,渲染react的DOM,React18
    let root = ReactDOM.createRoot(app); 
    // React.createElement() -> 创建虚拟DOM 
    //createElement(标签,属性,内容)
    let element = React.createElement('h2', title: 'hi', 'hello world');
    root.render(element);
  </script>
</body>
</html>

特点

虚拟DOM,组件化设计模式,声明式代码,单向数据流,使用jsx描述信息

声明式编码

  • 命令式编程:流程(每一步指令怎么去做)
  • 声明式编码:目标,而非流程

因没有指令的概念,所以条件渲染和列表渲染都要通过命令式编程来实现(即JS本身的能力)

map()方法
let app = document.querySelector('#app');
let root = ReactDOM.createRoot(app); 
let data = [
     id: 1, text: 'aaa' ,
     id: 2, text: 'bbb' ,
     id: 3, text: 'ccc' 
];
let element = (
    <ul>
        
            data.map(v=><li key=v.id>v.text</li>)
        
    </ul>
);
root.render(element);

单向数据流 

数据主要从父节点传到子节点(通过props),如果父级的某个props改变了,React会重新渲染所有子节点

组件化

可复用的代码可以抽成组件共同使用(UI,方法等) 

每个UI块都是一个组件,每个组件都有一个状态。

虚拟DOM(Virtual Dom)(同Vue)

虚拟 DOM,根据模板生成一个js对象(使用createElement,方法),取代真实的 DOM 。

当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来

Vue和React框架都会自动控制DOM的更新,而直接操作真实DOM是非常耗性能的,所以才有了虚拟DOM的概念

React遵循可观察的模式,并监听状态变化。当组件的状态改变时,React更新虚拟DOM树。

缺点首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
 

Diff算法(同Vue)

通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较

总的来说就是减少DOM,重绘回流


react生成的新虚拟DOM和旧虚拟DOM的比较规则:

  • 如果旧的虚拟DOM中找到了与新虚拟DOM相同的key:

如果内容没有变化,就直接只用之前旧的真实DOM
如果内容发生了变化,就生成新的真实DOM

  • 如果旧的虚拟DOM中没有找到与新虚拟DOM相同的key:

根据数据创建新的真实的DOM,随后渲染到页面上

组件属性

每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。

props

对外公开属性,只读

传递数据

<body>
    <div id = "div">
 
    </div>
 
</body>
<script type="text/babel">
// 函数组件
function Welcome(props)
    return (
        <div>hello world, props.msg</div>
    );  

let element = (
    <Welcome msg="hi react" />
);
 
// 类组件
    class Person extends React.Component
        render()
            return (
                <ul>
                    //接受数据并显示
                    <li>this.props.name</li>
                    <li>this.props.age</li>
                    <li>this.props.sex</li>
                </ul>
            )
        
    
   //等同
   const p = name:"张三",age:"18",sex:"女"
   ReactDOM.render(<Person ...p/>,document.getElementById("div"));
    //传递数据
    ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>

传递函数

// 子组件
class Head extends React.Component 
    render()
        this.props.getData('子组件的问候~~~')
        return (
            <div>Head Component</div>
        );
    

// 父组件
class Welcome extends React.Component 
    getData = (data) => 
        console.log(data)
    
    render()
        return (
            <div>
                hello world, this.props.msg
                <br />
                <Head getData=this.getData />
            </div>
        );
    

构造函数获取props

class Foo 
    constructor(props)
        this.props = props;
    

class Bar extends Foo 
    constructor(props)
        super(props);
        console.log(this.props);
    
    render()
        console.log(this.props);
        return '';
    

let props = 
    msg: 'hello world'
;
let b = new Bar(props);
b.props = props;
b.render();

多属性传递props

class Welcome extends React.Component 
    render()
        //解构
        let  msg, username, age  = this.props;
        console.log( isChecked );
        return (
            <div>hello world, msg, username, age</div>
        );
    

let info = 
    msg: 'hi react',
    username: 'xiaoming',
    age: 20
;
let element = (
    <Welcome ...info />
);

设置props初始值和类型

import PropTypes from 'prop-types'
class Welcome extends React.Component 
    static defaultProps = 
        age: 0
    
    static propTypes = 
        age: PropTypes.number
    
    ...

state

组件的私有属性,值是对象(可以包含多个key-value的组合)

通过state的变化设置响应式视图受控于当前组件

class Welcome extends React.Component 
    state = 
        msg: 'hello',
        count: 0
    
    handleClick = () =>    
        this.setState(
            msg: 'hi'
        );
    
    render()
        console.log('render');
        return (
            <div>
                <button onClick=this.handleClick>点击</button>
                this.state.msg, this.state.count
            </div>
        );
    

let element = (
    <Welcome />
);

refs

React操作原生DOM跟Vue框架是类似的,都是通过ref属性来完成的

class Welcome extends React.Component 
    myRef = React.createRef()
    handleClick = () =>    
        //console.log(this.myRef.current);  // 原生DOM input
        this.myRef.current.focus();//获取焦点
    
    render()
        return (
            <div>
                <button onClick=this.handleClick>点击</button>
                <input type="text" ref=this.myRef />
            </div>
        );
    

总结

props


公开,单向数据流值,父子组件间的唯一通信不可改

1.每个组件对象都会有props(properties的简写)属性

2.组件标签的所有属性都保存在props中

3.内部读取某个属性值:this.props.propertyName

state


私有(通过Ajax获取回来的数据,一般都是私有数据),

React 把组件看成是一个状态机(State Machines),

只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。


refs


当需要获取某一个真实的DOM元素来交互,比如文本框的聚焦、触发强制动画等

当需要操作的元素和获取的元素是同一个时,无需ref

受控组件和非受控组件

非受控组件
现用现取,官方建议尽量少用ref,用多了有一定的效率影响

handleSubmit = (event) => 
            event.preventDefault() //阻止表单提交
            const username, password = this//拿到的是form下的username, password结点
            alert(`你输入的用户名是:$username.value,你输入的密码是:$password.value`)
        
 
render() 
return (
<form onSubmit=this.handleSubmit>
用户名:<input ref=c => this.username = c type="text" name="username"/>
密码:<input ref=c => this.password = c type="password" name="password"/>
<button>登录</button>


受控组件
将输入维护到state,等需要时再从state取出来

    class Login extends React.Component 
 
//state最好要初始化状态
        state = 
            username: '', //用户名
            password: '' //密码
        
 
//保存用户名到状态中
        saveUsername = (event) => 
this.setState(username: event.target.value)
        
 
//保存密码到状态中
        savePassword = (event) => 
this.setState(password: event.target.value)
        
 
//表单提交的回调
        handleSubmit = (event) => 
          event.preventDefault() //阻止表单提交
          const username, password = this.state//获得的是state下的username,password值
          alert(`你输入的用户名是:$username,你输入的密码是:$password`)
        

事件event

React中的事件都是采用事件委托的形式,所有的事件都挂载组件容器上,其次event对象是合成处理过的

事件处理的几种方法

import React from 'react'
class Test extends React.Component
    
    handleClick2()
        console.log('click2')
    
 
    hangleClick4 = () =>
        console.log('click4')
    
    render()
        return(
            <button onClick= console.log('click1')>click1</button>
            <button onClick= this.handleClick2.bind(this)>click2</button>
            <button onClick= () => console.log('click3')>click3</button>
            <button onClick= this.hangleClick4 >click3</button>
        )
    

export default Test

事件中this的处理

class Welcome extends React.Component 
    handleClick = (ev) =>   //推荐 public class fields语法,箭头函数不会创建自己的                                      this,它只会从自己的作用域链的上一层继承 this。
        console.log(this);   //对象
        console.log(ev);
    
    handleClick()           //不推荐 要注意修正指向
        console.log(this);   //按钮 
    
    render()
        return (
            <div>
                <button onClick=this.handleClick>点击</button>
                hello world
            </div>
        );
    

let element = (
    <Welcome />
);

事件传参处理


推荐采用函数的高阶方式,具体代码如下:

class Welcome extends React.Component 
    handleClick = (num) =>    // 高阶函数
        return (ev) => 
            console.log(num);
        
    
    render()
        return (
            <div>
                <button onClick=this.handleClick(123)>点击</button>
                hello world
            </div>
        );
    

let element = (
    <Welcome />
);

鼠标事件 mouseenter与mouseover区别

mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发

mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。

mouseenter 不支持事件冒泡 mouseover 会冒泡

跨组件通信

Welcome传递给Title:

let MyContext = React.createContext();
class Welcome extends React.Component 
    state = 
        msg: 'welcome组件的数据'
    
    render()
        return (
            <div>
                Hello Welcome
                <MyContext.Provider value=this.state.msg>
                    <Head />
                </MyContext.Provider>
            </div>
        );
    

class Head extends React.Component 
    render()
        return (
            <div>
                Hello Head
                <Title />
            </div>
        );
    

class Title extends React.Component 
    static contextType = MyContext
    componentDidMount = () => 
        console.log( this.context );
    
    render()
        return (
            <div>
                Hello Title <MyContext.Consumer> value => value </MyContext.Consumer>
            </div>
        );
    

let element = (
    <Welcome />
);

通过<MyContext.Provider>组件携带value属性进行向下传递的,

那么接收的语法是通过<MyContext.Consumer>组件。

也可以定义一个静态方法static contextType = MyContext,这样就可以在逻辑中通过this.context来拿到同样的值。

生命周期

生命周期钩子函数就是回调函数

挂载

  • constructor():在 React 组件挂载之前,会调用它的构造函数。(注:如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。)
  • render(): class 组件中唯一必须实现的方法。
  • componentDidMount():在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。

更新

  • render(): class 组件中唯一必须实现的方法。
  • componentDidUpdate():在更新后会被立即调用。首次渲染不会执行此方法。

卸载

  • componentWillUnmount():在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
     

可以看到挂载时和更新时都有render这个方法。这就是为什么state改变的时候,会触发render重渲染操作

class Welcome extends React.Component 
    state = 
        msg: 'hello world'
    
    constructor(props)
        super(props);
        console.log('constructor');
    
    componentDidMount = () => 
        // react中发起ajax请求的初始操作,在这个钩子中完成
        console.log('componentDidMount');
    
    componentDidUpdate = () => 
        // 等DOM更新后触发的钩子
        console.log('componentDidUpdate');
    
    componentWillUnmount = () => 
        console.log('componentWillUnmount');
    
    handleClick = () =>   
        /* this.setState(
          msg: 'hi react'
        ); */
        //this.forceUpdate();
        root.unmount();   // 触发卸载组件
    
    render()
        console.log('render');
        return (
            <div>
                <button onClick=this.handleClick>点击</button>
                 this.state.msg 
            </div>
        );
    

状态提升

多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。

复用组件

Render Props模式

组件之间使用一个值为函数prop 共享代码的简单技术。

class MouseXY extends React.Component 
    state = 
        x: 0,
        y: 0
    
    componentDidMount = () => 
        document.addEventListener('mousemove', this.move)
    
    componentWillUnmount = () => 
        document.removeEventListener('mousemove', this.move)
    
    move = (ev) => 
        this.setState(
            x: ev.pageX,
            y: ev.pageY
        );
    
    render()
        return (
            <React.Fragment>
                 this.props.render(this.state.x, this.state.y) 
            </React.Fragment>
        );
    

class Welcome extends React.Component 
    render()
        return (
            <MouseXY render=(x, y)=> 
               <div>
                  hello world, x, y
               </div>
             />
        );
    

let element = (
    <Welcome />
);

render属性后面的值是一个回调函数,通过这个函数的形参可以得到组件中的数据,从而实现功能的复用。

HOC高阶组件模式

参数组件返回值新组件函数

function withMouseXY(WithComponent)
    return class extends React.Component 
        state = 
            x: 0,
            y: 0
        
        componentDidMount = () => 
            document.addEventListener('mousemove', this.move)
        
        componentWillUnmount = () => 
            document.removeEventListener('mousemove', this.move)
        
        move = (ev) => 
            this.setState(
                x: ev.pageX,
                y: ev.pageY 
            )
        
        render()
            return <WithComponent ...this.state />
        
    

class Welcome extends React.Component 
    render()
        return (
            <div>
                hello world,  this.props.x ,  this.props.y 
            </div>
        );
    

const MouseWelcome = withMouseXY(Welcome)
let element = (
    <MouseWelcome />
);

Hooks

Hook 是 React 16.8 的新增特性,是一个特殊的函数,它可以在不写 class(不用extends React.Component) 的情况下“钩入” React 特性组件化模块的特性

useState

等同组件中的setState()

let  useState  = React;//只能在最顶层使用Hook
let Welcome = (props) => //只能在函数组件中使用Hook
    //count的初始值0,设置count的函数
    const [count, setCount] = useState(0);
    const handleClick = () => 
        setCount(count + 1)       
    
    return (
        <div>
            <button onClick=handleClick>点击</button>
            <div>hello world,  count </div>
        </div>
    );

与组件中的state一样自动批处理(即合并修改,每次set只渲染一次),可用flushSync方法消除

(flush synchronization清洗同步)

setCount((count)=> count+1)
setCount((count)=> count+1)
setCount((count)=> count+1)

<div> count </div>   // 渲染 3

let  useState  = React;
let  flushSync  = ReactDOM;
let Welcome = (props) => 
    const [count, setCount] = useState(0);
    const [msg, setMsg] = useState('hello');
    const handleClick = () => 
        flushSync(()=>
          setCount(count + 1)
        )
        flushSync(()=>
          setMsg('hi')
        )       
    
    return (
        <div>
            <button onClick=handleClick>点击</button>
            <div>hello world,  count ,  msg </div>
        </div>
    );

useState中的值在修改的时候,并不会进行原值的合并处理,所以使用的时候要注意。可利用扩展运算符的形式来解决合并的问题。

const [info, setInfo] = useState(
    username: 'xiaoming',
    age: 20
)
setInfo(
    ...info,
    username: 'xiaoqiang'
)

如果遇到初始值需要大量运算才能获取的话,可采用惰性初始state,useState()添加回调函数的形式来实现。

const initCount = () => 
    console.log('initCount');
    return 2*2*2;

const [count, setCount] = useState(()=>
    return initCount();
);

这样初始只会计算一次,并不会每次都重新进行计算

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

let  useReducer  = React;
let loginState = //体现整体关联性与统一性**
    isLogin: true,
    isLogout: false

let loginReducer = (state, action) => 
    switch(action.type)
        case 'login':
            return  isLogin: true, isLogout:

金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)下

金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)下

引言

元旦匆匆而过,2020年的春节又接踵而来,大家除了忙的提着裤子加班、年底冲冲冲外,还有着对于明年的迷茫和期待!2019年有多少苦涩心酸,2020年就有更多幸福美好,加油,奥利给!怀着一颗积极向上的心,来面对未来每一天的挑战!

所谓“兵马未动,粮草先行”,我们打响明天的战役也需要精神食粮来做后勤保障才是。在此我整理了多位从业者和我在2019年底至2020年初的一厂面试精选题,希望对磨砺锋芒、奋发向上的小伙伴有所帮助,祝你早日剑指大厂,扬帆起航,奥利给!

前端的性能优化和安全

1.常见的网站漏洞有哪些?(拼多多一面 2019.11)

有跨站脚本攻击(XSS)、跨站请求伪造(CSRF)、点击劫持、SQL注入、DDOS攻击、DNS劫持

2.简要介绍一下SQL注入?(拼多多一面 2019.11)

  • 所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.
  • 防范方法:
  1. 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双"-"进行转换等。

  2. 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。

  3. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。

  4. 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。

  5. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装

  6. sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。

3. 讲讲CSRF和XSS区别 (腾讯 一面 2019.09)

xss:跨站点攻击。xss攻击的主要目的是想办法获取目标攻击网站的cookie,因为有了cookie相当于有了session,有了这些信息就可以在任意能接进互联网的PC登陆该网站,并以其他人的身份登陆做破坏。预防措施防止下发界面显示html标签,把</>等符号转义。

csrf:跨站点伪装请求。csrf攻击的主要目的是让用户在不知情的情况下攻击自己已登录的一个系统,类似于钓鱼。如用户当前已经登陆了邮箱或bbs,同时用户又在使用另外一个,已经被你控制的网站,我们姑且叫它钓鱼网站。这个网站上面可能因为某个图片吸引你,你去点击一下,此时可能就会触发一个js的点击事件,构造一个bbs发帖的请求,去往你的bbs发帖,由于当前你的浏览器状态已经是登陆状态,所以session登陆cookie信息都会跟正常的请求一样,纯天然的利用当前的登陆状态,让用户在不知情的情况下,帮你发帖或干其他事情。预防措施,请求加入随机数,让钓鱼网站无法正常伪造请求。

4. DNS劫持与DNS污染 (快手一面 2019.08)

  • DNS劫持 DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。

  • DNS污染 DNS污染,指的是用户访问一个地址,国内的服务器(非DNS)监控到用户访问的已经被标记地址时,服务器伪装成DNS服务器向用户发回错误的地址的行为。范例,访问Youtube、Facebook之类网站等出现的状况。

5.在浏览器输入网址,知道页面出现发生了啥?( 腾讯二面 2019.06)

? 1.DNS解析

? 2.发送tcp连接

? 3.发送http请求

? 4.服务器处理请求并返回http报文

? 5.浏览器解析渲染界面

? 6.连接结束

参考地址:https://www.cnblogs.com/yuanzhiguo/p/8119470.html

6.计算浏览器的白屏时间( 腾讯二面 2019.06)

我们通常认为浏览器开始渲染 标签或者解析完 标签的时刻就是页面白屏结束的时间点。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>白屏</title>
  <script type="text/javascript">
    // 不兼容performance.timing 的浏览器,如IE8
    window.pageStartTime = Date.now();
  </script>
  <!-- 页面 CSS 资源 -->
  <link rel="stylesheet" href="common.css">
  <link rel="stylesheet" href="page.css">
  <script type="text/javascript">
    // 白屏时间结束点
    window.firstPaint = Date.now();
  </script>
</head>
<body>
  <!-- 页面内容 -->
</body>
</html>

因此白屏时间则可以这样计算出:

可使用Performance API时

白屏时间 = firstPaint - performance.timing.navigationStart;

不可使用Performance API时

白屏时间 = firstPaint - pageStartTime

7. 加载一个很长的列表,怎么优化性能?( 腾讯二面 2019.06)

参考地址: https://segmentfault.com/p/1210000011237223/read

8.xss和csrf有哪些防御方式 (英语流利说一面 2019.03)

什么是xss攻击:
攻击者通过篡改网页,嵌入恶意js代码,当用户访问网页时,被嵌入的js代码会被执行,从而达到恶意攻击用户的一种方式;

如何防范 XSS 攻击:
1、对输入的字符串做长度限制;
2、对用户的输入进行过滤,如对& < > " ‘ /等进行转义;
3、获取用户的输入,不用innerHtml,用innerText.

CSRF攻击的原理:
跨站请求伪造,攻击者构造某个网站后台接口的请求地址,诱导用户去点击或者用特殊的方法让 该请求地址自动加载,用户在登陆的情况下,这个请求被服务器端接收后误以为是用户合法的操作,
对于get形式的接口跨域轻易被攻击,对于psot形式的接口也不是100%安全,攻击者可诱导用户带from表单可用post形式提交参数的页面。

如何防范CSRF攻击:
1、验证 HTTP Referer 字段;
2、在请求地址中添加 token 并验证;
3、在 HTTP 头中自定义属性并验证。

9.前端需要注意哪些seo(搜索引擎优化)?(字节跳动一面 2019.08)

  • 合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可

  • 语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页

  • 重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
    重要内容不要用js输出:爬虫不会执行js获取内容

  • 少用iframe:搜索引擎不会抓取iframe中的内容

  • 非装饰性图片必须加alt

  • 提高网站速度:网站速度是搜索引擎排序的一个重要指标

10.webpack中loader和plugins的区别(搜狐一面 2019.10 )

  • Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中。
  • Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。

混合式开发 (hybrid webview等)

1、 混合开发概述

Hybrid App主要以JS+Native两者相互调用为主,从开发层面实现“一次开发,多处运行”的机制,成为真正适合跨平台的开发。Hybrid App兼具了Native App良好用户体验的优势,也兼具了Web App使用HTML5跨平台开发低成本的优势。

目前已经有众多Hybrid App开发成功应用,比如美团、爱奇艺、微信等知名移动应用,都是采用Hybrid App开发模式。

2、移动应用开发的三种方式比较

移动应用开发的方式,目前主要有三种:

  • Native App: 本地应用程序(原生App),一般依托于操作系统,有很强的交互,是一个完整的App,可拓展性强,需要用户下载安装使用。(简单来说,原生应用是特别为某种操作系统开发的,比如iOS、Android、黑莓等等,它们是在各自的移动设备上运行的)

    该模式通常是由“云服务器数据+APP应用客户端”两部份构成,APP应用所有的UI元素、数据内容、逻辑框架均安装在手机终端上。

    原生应用程序是某一个移动平台(比如iOS或安卓)所特有的,使用相应平台支持的开发工具和语言(比如iOS平台支持Xcode和Objective-C,安卓平台支持Eclipse和Java)。原生应用程序看起来(外观)和运行起来(性能)是最佳的。

  • Web App:网页应用程序(移动web)指采用Html5语言写出的App,不需要下载安装。类似于现在所说的轻应用。生存在浏览器中的应用,基本上可以说是触屏版的网页应用。(Web应用本质上是为移动浏览器设计的基于Web的应用,它们是用普通Web开发语言开发的,可以在各种智能手机浏览器上运行)

    Web App开发即是一种框架型APP开发模式(HTML5 APP 框架开发模式),该开发具有跨平台的优势,该模式通常由“HTML5云网站+APP应用客户端”两部份构成,APP应用客户端只需安装应用的框架部份,而应用的数据则是每次打开APP的时候,去云端取数据呈现给手机用户。

    HTML5应用程序使用标准的Web技术,通常是HTML5、JavaScript和CSS。这种只编写一次、可到处运行的移动开发方法构建的跨平台移动应用程序可以在多个设备上运行。虽然开发人员单单使用HTML5和JavaScript就能构建功能复杂的应用程序,但仍然存在一些重大的局限性,具体包括会话管理、安全离线存储以及访问原生设备功能(摄像头、日历和地理位置等)。

  • Hybrid App:混合应用程序(混合App)指的是半原生半Web的混合类App。需要下载安装,看上去类似Native App,但只有很少的UI Web View,访问的内容是 Web 。

    混合应用程序让开发人员可以把HTML5应用程序嵌入到一个细薄的原生容器里面,集原生应用程序和HTML5应用程序的优点(及缺点)于一体。

    混合应用大家都知道是原生应用和Web应用的结合体,采用了原生应用的一部分、Web应用的一部分,所以必须在部分在设备上运行、部分在Web上运行。不过混合应用中比例很自由,比如Web 占90%,原生占10%;或者各占50%。

3、如何实现移动端适配? (Bigo 网易雷火)

? 1、meta:viewport 防止浏览器自动缩放

<meta name="viewport" content="width=device-width , user-scalable=no ,  initial-scale=1.0  , maximum-scale=1.0 , minimum-scale=1.0">

? 2、响应式布局(responsive)

响应式布局可使网站在不同的设备上浏览时对应不同分辨率皆有适合的呈现
其布局通过媒体查询@media实现,
新闻及门户网站可以用响应式布局,但大型网站媒体查询有其局限性

实际上除了响应式,网站通常会采用:1.提供两套html由后端根据用户设备来切换 2.提供两个完全不同的url由后端根据用户设备来跳转

? 3、通过动态rem实现

css常见的单位有: px,em,rem ,vh ,vw

  • rem:root em 即根元素的字体大小
  • em:一个字的宽度(一般指字母m),它的大小与其font-size一样
  • vh: 100vh === 视口高度
  • vw: 100vw === 适口宽度

需要注意:

  • 网页字体默认大小16px,em、rem默认情况下都是16px
  • chrome可以在浏览器设置中自行设置最小字号,无论控制样式多小都不会小于这个值,这个值默认12px
<script>
document.write(` <style>{html{font-size:${window.innerWidth}px}}</style>`)
</script>

参考地址:https://www.jianshu.com/p/c6d82db7ad62

git的使用

1、git版本管理问题,如何回滚

? Git回滚代码到某个commit

? 回退命令:
? git reset --hard HEAD^ 回退到上个版本

? git reset --hard HEAD~3 回退到前3次提交之前,以此类推,回退到n次提交之前

? git reset --hard commit_id 退到/进到,指定commit的哈希码(这次提交之前或之后的提交都会回滚)

2、git工作流程,常用的git命令有哪些,rebase的原理?

参考地址 :https://www.jianshu.com/p/57f0626a1432

设计模式学习笔记

设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

单例模式

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
  • 使用场景:一个单一对象,比如弹窗,无论点击多少次,弹窗只应该被创建一次。
class CreateUser {
    constructor(name) {
        this.name = name;
        this.getName();
    }
    getName() {
         return this.name;
    }
}
// 代理实现单例模式
var ProxyMode = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new CreateUser(name);
        }
        return instance;
    }
})();
// 测试单体模式的实例
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
// 因为单体模式是只实例化一次,所以下面的实例是相等的
console.log(a === b);    //true

策略模式

  • 定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。
  • 一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context(不变),Context接受客户的请求,随后将请求委托给某一个策略类。
/*策略类*/
var levelOBJ = {
    "A": function(money) {
        return money * 4;
    },
    "B" : function(money) {
        return money * 3;
    },
    "C" : function(money) {
        return money * 2;
    } 
};
/*环境类*/
var calculateBouns =function(level,money) {
    return levelOBJ[level](money);
};
console.log(calculateBouns('A',10000)); // 40000

代理模式

  • 为一个对象提供一个代用品或占位符,以便控制对它的访问。
  • 常用的虚拟代理形式:某一个花销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建(例:使用虚拟代理实现图片懒加载)
  • 图片懒加载的方式:先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。
var imgFunc = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
var proxyImage = (function() {
    var img = new Image();
    img.onload = function() {
        imgFunc.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            imgFunc.setSrc('./loading,gif');
            img.src = src;
        }
    }
})();
proxyImage.setSrc('./pic.png');

中介者模式

  • 通过一个中介者对象,其他所有的相关对象都通过该中介者对象来通信,而不是相互引用,当其中的一个对象发生改变时,只需要通知中介者对象即可。通过中介者模式可以解除对象与对象之间的紧耦合关系。
  • 中介者模式适用的场景:例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。

装饰者模式

  • 在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。
  • 装饰者模式适用的场景:原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。

算法

1、算法题: 实现LRU算法 (微软)

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

参考: https://www.cnblogs.com/bopo/p/9255654.html

2、求解最长递增子序列(微软)

给定一个长度为N的数组,找出一个最长的单调递增子序列,子序列不一定连续,但初始顺序不能乱。

例如:给定一个长度为6的数组A{4, 5, 7, 1,3, 9},则其最长的单调递增子序列为{4,5,7,9},长度为4。

//找出最长子序列的长度

int LargestListFind()
{
    int vec[6] = {4,5,7,1,3,9};
    int d[6] = {1};
    for(unsigned int i = 0; i < 6; i++)
    {
        d[i] = 1;
        for(unsigned int j = 0; j < i; j++)
        {
            if(vec[j] < vec[i] && d[j] >= d[i])
                d[i] = d[j] + 1;
        }
    }
    int Max = -1;
    for(unsigned int i = 0; i < 6; i++)
        if(Max < d[i])
            Max = d[i];
    return Max;
}

参考 : https://blog.csdn.net/love20165104027/article/details/79618367

3、 判断一棵树是否平衡二叉树

我们先来整理一下什么是平衡二叉树?
满足以下两点的就是平衡二叉树:
1.左右子树的高度差不能超过1
2.左右子树也是平衡二叉树

int IsBalance(BNode *root,int *pHeight)
{
    if (root == NULL)
    {
        *pHeight = 0;
        return 1;
    }
    int leftHeight;
    int rightHeight;
    int leftBalance = IsBalance(root->left, &leftHeight);
    int rightBalance = IsBalance(root->right, &leftHeight);
    *pHeight = MAX(leftHeight, rightHeight) + 1;
    if (leftBalance == 0 || rightBalance == 0)
    {
        return 0;
    }
    int diff = leftHeight - rightHeight;
    if (diff < -1 || diff>1)
    {
        return 0;
    }
    else {
        return 1;
    }
}

参考 :https://blog.csdn.net/WYH19951220/article/details/88891672

4、给定一个字符串,找出不含有重复字符的 最长子串 的长度。 (图森未来)

示例:

给定 “abcabcbb” ,没有重复字符的最长子串是 “abc” ,那么长度就是3。

给定 “bbbbb” ,最长的子串就是 “b” ,长度是1。

给定 “pwwkew” ,最长子串是 “wke” ,长度是3。请注意答案必须是一个子串,“pwke” 是 子序列 而不是子串。

public int LengthOfLongestSubstring(string str)
{
    int resLength = 0;
    int strLength = str.Length;
    int i = 0, j = 0;
    HashSet<string> hashSet = new HashSet<string>(); 
    while   (i < strLength && j < strLength)
    {
            string  oneStrJ = str.Substring(j,1);
            if  (!hashSet.Contains(oneStrJ))
            {
                    hashSet.Add(oneStrJ);
                    j++;
                    resLength = Math.Max(resLength,j-i);
            } else {
                    string  oneStrI = str.Substring(i, 1);
                    hashSet.Remove(oneStrI);
                    i++;
            }
    }
    return  resLength;
}

参考:https://blog.csdn.net/xc121566/article/details/80827129

5、数学:如何判断一条直线与一个三角形相交?简化: 如何判断一条直线与一个线段相交?(图森未来)

判断是否存在一条直线,让所有线段在直线上的投影存在一个公共点。这个问题可以转化为这样的问题:
是否存在一条直线可以穿过所有线段,进而转化成经过某两个线段端点的直线能否穿过所有线段。(具体证明可以自行百度)
现在问题简化了,只需要枚举两个端点(包括同一条线段的)判断是否相交就行了。
坑:枚举的两个端点可能重合

#include <cstdio>
#include <iostream>
#include <cmath>
const double eps=1e-8;
using namespace std;

struct Point {
    double x, y;
    Point(double x=0, double y=0) : x(x),y(y) {}
};

int n,T;
Point p[1005][2];

typedef Point Vector;

Vector operator + (Vector A,Vector B) {return Vector(A.x+B.x,A.y+B.y);}
Vector operator - (Vector A,Vector B) {return Vector(A.x-B.x,A.y-B.y);}
Vector operator * (Vector A,double p) {return Vector(A.x*p,A.y*p);}
Vector operator / (Vector A,double p) {return Vector(A.x/p,A.y/p);}

bool operator < (const Point& a,const Point& b) {
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}

int dcmp(double x) {
    if (fabs(x) < eps) return 0;
    else return x<0?-1:1;
}

bool operator == (const Point &a,const Point &b) {
    return dcmp(a.x-b.x) == 0 && dcmp(a.y-b.y) == 0;
}

double Cross(Vector A,Vector B) {
    return A.x*B.y - A.y*B.x;
}

// 判断直线与线段是否相交
bool SegLineInt(Point a1,Point a2,Point b1,Point b2) {
    double c1 = Cross(a2-a1,b1-a1), c2 = Cross(a2-a1,b2-a1);
    return dcmp(c1)*dcmp(c2)<=0;
}

// 判断一条直线是否满足条件
bool judge(Point A,Point B)
{
    // 一个坑,选择的两个点不能重合
    if (A==B) return false;
    for (int i=1;i<=n;i++)
        if (!SegLineInt(A,B,p[i][0],p[i][1]))
            return false;
    return true;
}

int main()
{
    cin >> T;
    while(T--)
    {
        cin >> n;
        for (int i=1;i<=n;i++)
            scanf("%lf%lf%lf%lf",&p[i][0].x,&p[i][0].y,&p[i][1].x,&p[i][1].y);
        if (n==1) {printf("Yes!
"); continue;}
        bool flag=0;
        // 枚举两个端点
        for (int i=1;i<=n&&!flag;i++)
        {
            for (int j=i+1;j<=n;j++)
            {
                flag|=judge(p[i][0],p[j][0]);
                flag|=judge(p[i][0],p[j][1]);
                flag|=judge(p[i][1],p[j][0]);
                flag|=judge(p[i][1],p[j][1]);
                if (flag) break;
            }
        }
        if (flag) printf("Yes!
");
        else printf("No!
");
    }
    return 0;
}

参考:https://blog.csdn.net/radium_1209/article/details/89520666

6、一个xy坐标系中的无数个点,两两连线,求斜率最大的点?(图森未来)

参考:https://blog.csdn.net/baidu_38621657/article/details/88369398

7、说说快排和冒泡的算法原理?(VIVO)

参考:https://blog.csdn.net/ValDC_Morning/article/details/76615752

? https://www.cnblogs.com/clarke157/p/6808951.html

8、排序算法有哪些?其中哪些是稳定的?复杂度多少?(猿辅导 2019.08 阿里巴巴)

插入排序算法是基于某序列已经有序排列的情况下,通过一次插入一个元素的方式按照原有排序方式增加元素。这种比较是从该有序序列的最末端开始执行,即要插入序列中的元素最先和有序序列中最大的元素比较,若其大于该最大元素,则可直接插入最大元素的后面即可,否则再向前一位比较查找直至找到应该插入的位置为止。插入排序的基本思想是,每次将1个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,寻找最适当的位置,直至全部记录插入完毕。执行过程中,若遇到和插入元素相等的位置,则将要插人的元素放在该相等元素的后面,因此插入该元素后并未改变原序列的前后顺序。我们认为插入排序也是一种稳定的排序方法。插入排序分直接插入排序、折半插入排序和希尔排序3类。

冒泡排序算法是把较小的元素往前调或者把较大的元素往后调。这种方法主要是通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。冒泡排序的基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,重复进行上述计算,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程进行第2次、第3次排序,直至整个序列有序为止。排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。

选择排序算法的基本思路是为每一个位置选择当前最小的元素。选择排序的基本思想是,基于直接选择排序和堆排序这两种基本的简单排序方法。首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可;以此类推,重复进行“最小元素”的选择,直至完成第(n-1)个位置的元素选择,则第n个位置就只剩唯一的最大元素,此时不需再进行选择。使用这种排序时,要注意其中一个不同于冒泡法的细节。举例说明:序列58539.我们知道第一遍选择第1个元素“5”会和元素“3”交换,那么原序列中的两个相同元素“5”之间的前后相对顺序就发生了改变。因此,我们说选择排序不是稳定的排序算法,它在计算过程中会破坏稳定性。

快速排序的基本思想是:通过一趟排序算法把所需要排序的序列的元素分割成两大块,其中,一部分的元素都要小于或等于另外一部分的序列元素,然后仍根据该种方法对划分后的这两块序列的元素分别再次实行快速排序算法,排序实现的整个过程可以是递归的来进行调用,最终能够实现将所需排序的无序序列元素变为一个有序的序列。

归并排序算法就是把序列递归划分成为一个个短序列,以其中只有1个元素的直接序列或者只有2个元素的序列作为短序列的递归出口,再将全部有序的短序列按照一定的规则进行排序为长序列。归并排序融合了分治策略,即将含有n个记录的初始序列中的每个记录均视为长度为1的子序列,再将这n个子序列两两合并得到n/2个长度为2(当凡为奇数时会出现长度为l的情况)的有序子序列;将上述步骤重复操作,直至得到1个长度为n的有序长序列。需要注意的是,在进行元素比较和交换时,若两个元素大小相等则不必刻意交换位置,因此该算法不会破坏序列的稳定性,即归并排序也是稳定的排序算法。

参考: https://blog.csdn.net/hanxiaoran906/article/details/81488232

9、 搜索一个二叉树,是否有和为某个值的路径,返回true或false ( 字节跳动)

写一个程序创建一棵二叉树,并按照一定规则,输出二叉树根节点到叶子节点的路径。

规则如下:
1、从最顶端的根结点,到最下面的叶子节点,计算路径通过的所有节点的和,如果与设置的某一值的相同,那么输出这条路径上的所有节点。

2、从根节点遍历树时,请请按照左到右遍历,即优先访问左子树的节点。

二叉树创建规则:从上到下一层一层的,按照从左到右的顺序进行构造

import java.util.Scanner;
 
public class Main {
    
    public static int counter = 0;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        int N  = Integer.valueOf(sc.nextLine());
        String line =  sc.nextLine();
        
        compute(N, line);
    }
    
    public static void compute(int N, String line){
        
        String[] arr = line.split(",");
        int len = arr.length;
        
        //从index=1开始存数据
        Node[] nodeArr = new Node[len + 1];
        
        for(int i = 0; i < len; i++){
            int val = Integer.valueOf(arr[i]);
            nodeArr[i + 1] = new Node(val);
        }
        
        //构建二叉树
        Node root = nodeArr[1];
        for(int i = 1; i < len + 1; i++){
            if(i * 2 < len + 1){
                nodeArr[i].left = nodeArr[2 * i];
            }
            
            if(i * 2 + 1 < len + 1){
                nodeArr[i].right = nodeArr[2 * i + 1];
            }
        }
        
//      printTree(root);
        
        printPaths(root, len, N);
    }
    
    
    public static void printTree(Node root){
        if(root == null){
            return;
        }
        
        System.out.println(root.val);
        
        if(root.left != null){
            printTree(root.left);
        }
        
        if(root.right != null){
            printTree(root.right);
        }
    }
    
    public static void printPaths(Node root, int n, int N) {  
        int[] path = new int[n];  
        printPaths(root, path, 0, N);
        
        if(counter == 0){
            System.out.println("error");
        }
        
    }  
 
    
    public static void printPaths(Node root, int[] path, int pathLen, int N) {
        if (root == null) return;  
            path[pathLen++] = root.val;  
        if (root.left == null && root.right == null) {  
            printArray(path, pathLen, N);
        }  
        else {  
            printPaths(root.left, path, pathLen, N);  
            printPaths(root.right, path, pathLen, N);  
        }  
    }  
  
    public static void printArray(int[] ints, int len, int N) {
        int total = 0;
        StringBuilder sb = new StringBuilder();
 
        for (int i = 0; i < len; i++) {
            sb.append(ints[i] + ",");
            total += ints[i];
        }
        
        if(total == N){
            System.out.println(sb.toString().substring(0, sb.toString().length() - 1));
            counter++;
        }
    }  
    
    
}
 
class Node{
    
    public int val;
    public Node left;
    public Node right;
    
    public Node(int val){
        this.val = val;
    }
}

10、给定一个链表和一个值k,每隔k进行反转 ( 字节跳动)

给定一个链表,要求每隔k个元素反转 即链表为1->2->3->4->5->6->7->8

当k=2时,链表为2->1->4->3->6->5->8->7

当k=5时,链表5->4->3->2->1->6->7->8

class Node<T> {
    public  T data;
    public  Node<T> next;
 
    Node(T dataPortion) {
        data = dataPortion;
        next = null;
 
    }
 
    Node(T dataPortion, Node<T> nextNode) {
        data = dataPortion;
        next = nextNode;
    }
    
}
 
 
public class ListKReverse {
    
    public static void main(String[] args) {
        ListKReverse s = new ListKReverse();
        
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        Node n4 = new Node(4);
        Node n5 = new Node(5);
        Node n6 = new Node(6);
        Node n7 = new Node(7);
        Node n8 = new Node(8);
        
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;
        n5.next = n6;
        n6.next = n7;
        n7.next = n8;
    
        
        Node head = s.ReverseInGroups(n1, 4);
        while (head != null) {
            System.out.print(head.data+"  ");
            head = head.next;
        }
        System.out.println();
    }
    
    public Node ReverseInGroups(Node current, int k) {
        if (current == null || current.next == null ) return current;
        int n=0;
        Node oldHead=current;
        while(current!=null)
        {
            current=current.next;
            n++;
        }
        System.out.println(n);
        int reverseNum=n/k;
        current=oldHead;
        Node newHead = current;
        Node previousGroupTail = null;
        int count = 1; 
        int num=0;
        while (current != null&&num<reverseNum) {   
            Node groupTail = current;
            Node prev = null;
            Node next = null;
            for (int i = 1; i <= k && current != null; i++) {
                next = current.next;
                current.next = prev;
                prev = current;
                current = next;
            }
            if (count == 1) {
                newHead = prev;
                count++;
            }
            if (previousGroupTail != null) {
                previousGroupTail.next = prev;
            }
            previousGroupTail = groupTail;
            num++;
        }
        if(current!=null)
            if (previousGroupTail != null) 
                previousGroupTail.next = current;
            
        return newHead;
    }
}

11、斐波那契数列编程题实现,复杂度多少,有哪些缺点,如何改进。(快手)

递归算法:

f(0) = 0 f(1) = 1 f(2) = 2 其次,当n=3时,青蛙的到达第三阶的前第位置有两种情况,阶一,阶二,所以递推f(3)=f(2)+f(1).即n>=3时,f(n)=f(n-1)+f(n-2)。

long facinabo(int n)
{
    if(n==0)
    {
        return 0;
    }
    if(n==1)
    {
        return 1;
    }
    if(n==2)
    {
        return 2;
    }
    else
    {
        return facinabo(n-1)+facinabo(n-2);
    }
}

复杂度:O(n*n)。

非递归算法

long facinabo(int n)
{
    int sum=0;
    int q1=1;
    int q2=2;
  if(n==0) {
        return 0;
    }
    if(n==1)    {
        return 1;
    }
    if(n==2) {
        return 2;
    }
    for(int i=3;i<=n;i++){
        sum = q1+q2;
        q1=q2;
        q2=sum;
  }
    return sum;
}

时间复杂度:O(n)。

12、查找数组中元素和等于给定数的子数组 (腾讯)

var ans,res,len;
var dfs=function(index,sum,candidates,target){
    if(sum===target){
        var tmp=res.map(function(item){
            return item;
        })
        ans.push(tmp);
        // console.log(res,ans);
        return ;
    }
    for(var i=index;i<len;i++){
        if(sum+candidates[i]>target)
            continue;
        res.push(candidates[i]);
        dfs(i,sum+candidates[i],candidates,target);
        res.pop();
    }
}
var combinationSum = function(candidates, target) {
    ans=[];
    len=candidates.length;
    candidates.sort((a,b)=>a-b);
    for(var i=0;i<len;i++){
        res=[candidates[i]];
        dfs(i,candidates[i],candidates,target);
    }
    return ans;
};

13、js实现数组千分位

方法一:正则实现 

//正则实现
function format (num) {  
    var reg=/d{1,3}(?=(d{3})+$)/g;   
    return num.toString().replace(reg, '$&,');  
}
//基础
function format(num){
    num+='';
    var str="";
    for(var i=num.length-1,j=1;i>=0;i--,j++){
        if(j%3===0 & i!=0){
            str+=num[i]+',';
        }else{
            str+=num[i];
        }
    }
    return str.split('').reverse().join('');
}

方法二:for循环正常思维算法

function format(num){  
  num=num+'';//数字转字符串  
  var str="";//字符串累加  
  for(var i=num.length- 1,j=1;i>=0;i--,j++){  
      if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况  
          str+=num[i]+",";//加千分位逗号  
          continue;  
      }  
      str+=num[i];//倒着累加数字
  }  
  return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串  
}

方法三:slice+while循环

function format(num) {
  var arr = [],
      str = num + '',
      count = str.length;

  while (count >= 3) {
    arr.unshift(str.slice(count - 3, count));
    count -= 3;
  }

  // 如果是不是3的倍数就另外追加到上去
  str.length % 3 && arr.unshift(str.slice(0, str.length % 3));

  return arr.toString();

}

方法四:reduce版

function format(num) {
  var str = num+'';
  // ["8", "7", "6", "5", "4", "3", "2", "1"]
  return str.split("").reverse().reduce((prev, next, index) => {
    return ((index % 3) ? next : (next + ',')) + prev;
  })
}
console.log(format(12345678));

14、 变色龙问题,给定10、15、17只三种颜色的变色龙,每两只一碰就会变成第三种颜色,最后如何变成同一种颜色,总结规律。(字节跳动三面 2019.09)

设三只变色龙分别为x y z当任意两只相遇边恒另外一只时,其变换成课简化为:(x-n)(y-n) (z+zn) n= 0,1,2,...

其中n =0 代表没有穿绳变化的情况。那么只要变化后满足下述三种情形之一,则代表可以变成同一种颜色:

  • x-n = y-n

  • x-n = z+2n

  • y-n = z+2n

(即,有两种颜色的变色龙数量相等时,可全部变为第三种颜色)

解答方程可得:

  • x-y = 0 = 3*0
  • x-z = 3n
  • y-z = 3n

上述三种情况实质上可以用一种情况代表:
x-y = 3n (n =0,1,2,....)
即,三种变色龙中,任意两种的数量相乘为3的倍数时,即可变成为同一种颜色的变色龙。

参考地址:https://blog.csdn.net/qq_32657025/article/details/79599954

HR面试

1.字节跳动HR面

对前端未来有什么看法
简单介绍下自己
讲讲项目,遇见比较有趣的点,遇到的困难 (快手)
你还面过哪些公司呢?有offer吗?
用三句话简单评价一下你自己
如何看待加班
未来职业规划
在与后台沟通过程中,遇见过什么困难?如何联调?

2.腾讯HR面

自我介绍

印象最深刻的一个项目,为什么,这个项目给你的最大的收获是啥?

近期遇到的最大的困难是啥?如何解决?期间是如何努力的?

最近印象最深刻的事情是啥?

学习能力强的原因是啥?

有什么业余爱好?

最近去哪些地方玩过?

3.招行信用卡中心HR面

如果老板提一个很难实现的需求,如何解决?
银行和互联网的区别,为啥想要到银行工作?
如果银行和互联网同时给你offer,怎么选?

为什么不留原公司,原公司有哪些地方让你想吐槽?如何解决这些问题?会找领导主动反馈吗?
觉得原公司有啥不合理的

4.其他HR面

有别的offer吗? 如果面临多个offer选择,你会怎么考虑?(腾讯)
讲讲项目,遇见比较有趣的点,遇到的困难(快手)

此处没有标准答案,因为每个人的情况不同,大家多面几次这些问题都应该不是问题了。

结语

还有2件事拜托大家
一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容
二:欢迎添加我的个人微信
备注“资料”, 300多篇原创技术文章,海量的视频资料即可获得
备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您做个潜水鱼也会学到很多东西

技术图片

相关链接

金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上
金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)中
金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)下

参考资料

11道浏览器原理面试题

这儿有20道大厂面试题等你查收

2020 前端面试 | “HTML + CSS + JS”专题

图解浏览器的工作原理(史上最全)

BAT前端经典面试问题:史上最最最详细的手写Promise教程

以上是关于2023年最全前端React18面试题考点的主要内容,如果未能解决你的问题,请参考以下文章

前端面试题整理——React考点和回答

面试秘籍《2023年Android中高级最全面试真题答案解析》原题命中率超高

面试秘籍《2023年Android中高级最全面试真题答案解析》原题命中率超高

最全Java面试题及答案整理(2023最新版)

2023最全Java面试八股(涵盖所有Java核心面试知识点),立刻收藏

2023年JAVA面试宝典(全网最全未来十年可用)