Giter Site home page Giter Site logo

blog2021's People

Contributors

leungkaming avatar

Stargazers

 avatar

Watchers

 avatar  avatar

blog2021's Issues

【js】原型链

// es5
// 创建一个类
function Person () {}

Person.prototype.nickName = 'ljm' // 给类的原型上添加属性,基于这类创建的实例都能访问到这个属性

// 创建实例
var person = new Person()

1. 关于实例
person.__proto__ === Person.prototype // true

person.nickName // 'ljm',person实例会默认从最近自身的类的原型上查找,即Person.prototype

person.constructor.name === 'Person' // 实例的构造函数对应是自身的类 => person实例不具备constructor属性,这个属性是属于自身的类的原型上查找的

2. 关于类
Person.prototype.constructor.name === 'Person' // true,左侧表达式的值恒等于类本身

Person.prototype.__proto__ === Object.prototype // true, 每个类的原型都可以看成是Object类的一个实例 => 一个实例的__proto__指向它的类的原型

Object.prototype.__proto__ === null // true, 意味着 原型链 最顶级就是Object

// 特异点
1. 类,也是一个是函数
Person.constructor.name === 'Function' // true, Person既是类,也可以看成是 Function类 的一个实例 => 实例的构造函数对应是自身的类
Object.constructor.name === 'Function' // true, Object既是类,也可以看成是 Function类 的一个实例 => 实例的构造函数对应是自身的类

2. 类的原型,也是一个实例
Person.__proto__ === Function.prototype // true, 上面提到过,Person既是类,也可以看成是函数的一个实例 => 它的类自然就是Function
Function.prototype.__proto__ === Object.prototype // true, 每个类的原型都可以看成是Object类的一个实例 => 一个实例的__proto__指向它的类的原型

下图会针对上述事例代码,更加清晰:
原型链

【js】执行上下文栈

经常提到的堆内存 / 栈内存分别是什么?

一句话概括:基本数据类型(String, Number, Boolean, Null, Undefined)都是存储在栈内存;引用数据类型(Object, Array, Function)都是存储在堆内存。

【react】hook重点梳理

useState
● 每次返回的都是最新state,同时每次更新state都会带着一次渲染;
● react会确保setState不会在组件重新渲染时发生变化,这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 setState;
● setState部分使用场景,例如:

  1. 如果存在新state需要依赖旧state进行计算的场景,则将函数传递给setState
  2. 如果存在初始state需要通过复杂计算的场景,将传入函数并返回初始的state
    ● 传入的函数仅会在初始化时被调用,如果state没有变化,会跳过render阶段;

useEffect
● 在不传入依赖数组项时,每次都会随render更新而调用;
● 每次在执行下一个effect前都会清除上次effect,即意味着下一个render前就会清除上次effect;
● useEffect会在render更新后通过异步被调用,所以里面放的都是跟layout无关的处理;
● 返回一个函数,则会在组件卸载时执行,等同于componentWillUnmount;
● 第二个参数传入空数组,则会在组件首次挂载和卸载时执行,等同于componentDidmount / componentWillUnmount;
● 第二个参数传入非空数组,则会在该变量发生改变时才执行,等同于componentDidUpdate;

useLayoutEffect
● 会在render更新后通过同步被调用;
● 不要在里面放会阻塞页面渲染的逻辑;

useContext

const context = React.createContext(null)
<Context.Provider value=context>
  <Context.Consumer>
    {(value) => <div>123</div>}
  </Context.Consumer>
</Context.Provider>

● 等同于内部状态管理的消费者hook模式(class模式通过contextType );
● class:当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新;
● hook:当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染;
● 调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化(useMemo / useCallback)

useReducer

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

● 底层原理是通过内部的useState,接受外部传入的action 和 reducer组装dispatch实现的,具体dispatch如:reducer(state, action);
● 在某些场景下,useReducer 会比 useState 更适用,例如:

  1. state 逻辑较复杂且包含多个子值
  2. 下一个 state 依赖于之前的 state
  3. ...
    ● 如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

● 返回一个缓存值,在依赖参数不变的的情况返回的是上次第一次计算的值;在某个依赖项改变时才会计算返回新的缓存值;
● 起到了class组件中shouldComponentUpdate的作用,但缺点是内部引用的依赖发生变化时不会主动通知
● 传入的函数会在每次render渲染期间都会执行;

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

● useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
● 返回一个缓存函数,传入的函数只有在某个依赖项改变时才会触发缓存函数更新,同时作用域内用到的变量才会取最新值;
● 内部本身就是一个作用域,在依赖不变的情况下,作用域内用到的变量也是上次的缓存值;
● 跟useMemo一样,useCallback会在每次渲染都执行;

useRef
● useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。
● 返回的 ref 对象在组件的整个生命周期内持续存在。

function TextInputWithFocusButton() {
  const inputEl = useRef(null); // 初始化值为null
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" /> // ref只要以这种类型传入dom,则ref.current属性值为dom节点
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

● 当 ref 对象内容发生变化时,useRef 并不会通知你。即变更 .current 属性不会引发组件重新渲染。
● 如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
● 组件重新渲染后,变量会被重新赋值,可以用useRef缓存数据,这个数据改变后是不会触发组件重新渲染的,如果用useState保存数据,数据改变后会导致组件重新渲染,所以我们想悄悄保存数据,useRef是不二选择👊

// 可以充分利用好 “变更.current 属性不会引发组件重新渲染” 这个特性

// 优化前,text会一直随着输入框的内容变化而变化,如果当前组件体量大的话会造成反复渲染,影响性能
const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
  // ...
}, [text]);

return (
  <form>
    <input value={text} onChange={(e) => setText(e.target.value)} />
    <OtherForm onSubmit={handleSubmit} />
  </form>
);

// 优化后
const textRef = useRef('');
const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
  console.log(textRef.current);
  // ...
}, [textRef]); // 1)首先,ref对象内容发生变化是不会有内部通知的

return (
  <form>
    <input value={text} onChange={(e) => {
      const { value } = e.target;
      setText(value)
      textRef.current = value; // 2)其次,变更 .current 属性不会引发组件重新渲染
    }} />
    
    // 3)所以handleSubmit里的内容不会随变量text的变化而反复调用
    <OtherForm onSubmit={handleSubmit} />
  </form>
);

useImperativeHandle
● React.forwardRef 常与当前hook搭配使用。

// 子组件伪代码
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
    	console.log('自定义focus')
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

// 父组件伪代码
class Container extends React.Component {
 constructor(props){
  super(props)
 }
 
 submit () {
  this.inputRef.current.focus()
 }
 
 render () {
  <div>
    <FancyInput ref={this.inputRef} />
    <button onClick={ this.submit.bind(this) }></button>
  </div>
 }
}

● 同步React.forwardRef的两个场景:

  1. 转发refs到dom组件
// 子组件伪代码
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 父组件伪代码
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
  1. 转发refs到高阶组件
// 子组件伪代码
function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }
    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}


// 父组件伪代码
const ref = React.createRef();
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}	// 这里传入的ref会给子组件的forwardRef接收到
/>;

总结:

  1. useMemo / useCallback 纯粹得像泡菜罐一样,我不扭开盖子(命中依赖),就不会同步空气外的微生物(更新缓存值 / 函数)

【js】作用域链

定义

  • Javascript采用的是静态作用域(所谓的词法作用域),函数的作用域在函数定义的时候就决定了。
  • 而与词法作用域相对的是动态作用域,动态作用域是在函数调用的时候才决定的。

事例细说

var value = 1

function foo () {
  console.log(value)
}

function bar () {
  var value = 2
  foo()
}

bar() // 1
  1. 因为作为foo函数里面的值,从初始化的时候(经过变量提升和函数预声明)就已经决定了 作用域是全局window ,而在调用的时候虽然看上去有同名变量覆盖,但并不如此。
  2. 只要函数没调用,不管里面有多少层嵌套,只要第一层嵌套没有调用全局作用域的变量,那么里面嵌套怎么重新赋值同名变量都不会影响到全局作用域同名的变量 => 「就近原则」(事例代码见下面)
var scope = "global scope"; // 变量提升
function checkscope(){ // 函数预声明
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); // local scope

// 如何输出global scope
var scope = "global scope";
function checkscope(){
    function f(){
        return scope;
    }
    return f();
}
checkscope(); // global scope

var scope = "global scope"; // 变量提升
function checkscope(){ // 函数预声明
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()(); // local scope,分析:不管返回函数还是返回值,函数checkscope里面的作用域已经存在scope,怎么嵌套调用,最终只会认里面的local scope。

上述代码示例的() 和 ()()两种情况,其实映射了变量销毁的知识点在里面,()调用后变量scope会跟着销毁,()()中第一次()并没有销毁scope,而是留在第二次()调用后才进行销毁。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.