This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

Take a look at a picture to get a general idea of the process and what to do

Initialize the

When the new Vue is initialized, the props and data of our component are initialized

Source address: SRC/core/instance/init. Js – 15 lines

export function initMixin (Vue: Class<Component>) {
  // Add the _init method to the prototype
  Vue.prototype._init = function (options? :Object) {... vm._self = vm initLifecycle(vm)$parent, $children, $refs, $root, _watcher... Etc.
    initEvents(vm) // Initialize events: $on, $off, $emit, $once
    initRender(vm) // Initialize render: render, mixin
    callHook(vm, 'beforeCreate') // Call the lifecycle hook function
    initInjections(vm) // Initialize inject
    initState(vm) // Initialize component data: props, data, methods, watch, computed
    initProvide(vm) // initialize provide
    callHook(vm, 'created') // Call the lifecycle hook function. }}Copy the code

There are a lot of methods called for initialization, each of which is doing different things, and the main thing about responsiveness is the data props, data in the component. The content of this section is in the initState() method, so let’s go into the source code of this method

initState()

Source address: SRC/core/instance/state. The js – 49

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // Initialize props
  if (opts.props) initProps(vm, opts.props)
  // Initialize methods
  if (opts.methods) initMethods(vm, opts.methods)
  // Initialize data
  if (opts.data) {
    initData(vm)
  } else {
    // If there is no data, it defaults to an empty object and listens
    observe(vm._data = {}, true /* asRootData */)}// Initialize computed
  if (opts.computed) initComputed(vm, opts.computed)
  // Initialize watch
  if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code

Again calling a bunch of initialization methods, let’s get right to the point and take our responsive data related ones, namely initProps(), initData(), observe()

Continue to pick one by one, must understand the whole process of response

initProps()

Source address: SRC/core/instance/state. The js – 65 line

The main work here is:

  • Passes through the parent componentpropsThe list of
  • Verify each attribute’s name, type, default attribute, etc., and call it without problemdefineReactiveSet it to reactive
  • And then useproxy()Proxies properties to the current instance, as invm._props.xxbecomevm.xx, you can access
function initProps (vm: Component, propsOptions: Object) {
  // The parent component passes the props of the child component
  const propsData = vm.$options.propsData || {}
  // Final props after conversion
  const props = vm._props = {}
  // Hold the props key, and if the props value is empty, the key is still inside
  const keys = vm.$options._propKeys = []
  constisRoot = ! vm.$parent// Convert props for non-root instances
  if(! isRoot) { toggleObserving(false)}for (const key in propsOptions) {
    keys.push(key)
    // Verify props and default properties
    const value = validateProp(key, propsOptions, propsData, vm)
    // In a non-production environment
    if(process.env.NODE_ENV ! = ='production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn('hyphenatedKey' is a reserved property and cannot be used as a component prop)}// set props to responsive
      defineReactive(props, key, value, () = > {
        // Issue a warning if the user modifies props
        if(! isRoot && ! isUpdatingChildComponent) { warn(Avoid direct changes to prop)}}}else {
      // set props to responsive
      defineReactive(props, key, value)
    }
    // Delegate attributes that are not on the default VM to the instance
    // make vm._props. Xx accessible through vm.xx
    if(! (keyin vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)}Copy the code

initData()

Source address: SRC/core/instance/state. The js – 113 line

The main work here is:

  • Initialize a data and get the keys set
  • Check whether the keys set has the same name as the properties in props or the methods in methods
  • Pass without questionproxy()By proxying each property in data to the current instance, you can passthis.xxAccess to the
  • Last callobserveListen to the entire data
function initData (vm: Component) {
  // Get data for the current instance
  let data = vm.$options.data
  // Determine the type of data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if(! isPlainObject(data)) { data = {} process.env.NODE_ENV ! = ='production' && warn(The data function should return an object)}// Get the collection of data attribute names for the current instance
  const keys = Object.keys(data)
  // Get props for the current instance
  const props = vm.$options.props
  // Get the methods object of the current instance
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // Check whether methods in methods exist in props in non-production environments
    if(process.env.NODE_ENV ! = ='production') {
      if (methods && hasOwn(methods, key)) {
        warn(The 'Method' Method cannot be declared twice)}}// Check whether an attribute in data exists in props in non-production environments
    if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(Property cannot be declared twice)}else if(! isReserved(key)) {// Delegate to vm without the same name
      // vm._data.xx can be accessed through vm.xx
      proxy(vm, `_data`, key)
    }
  }
  / / to monitor data
  observe(data, true /* asRootData */)}Copy the code

observe()

Source address: SRC/core/observer/index. The js – 110 line

This method is mainly used to add listeners to data

The main work here is:

  • If it is a VNode object type or not a reference type, jump out
  • Otherwise, add an Observer, a listener, to the data that does not have one
export function observe (value: any, asRootData: ? boolean) :Observer | void {
  // If it is not an 'object' type or a vNode object type, it is returned
  if(! isObject(value) || valueinstanceof VNode) {
    return
  }
  let ob: Observer | void
  // Use the cached object
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && ! value._isVue ) {// Create listener
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
Copy the code

Observer

Source address: SRC/core/observer/index, js to 37

This is a class that turns normal data into observable data

The main work here is:

  • Mark the current value as a reactive attribute to avoid repeated operations
  • Then determine the data type
    • If it is an object, we iterate over the object and call defineReactive() to create a reactive object
    • If it is an array, traverse the array and call Observe () to listen for each element
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // Number of VMS on the root object
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // Add an __ob__ attribute to value
    // Indicates that the object has become responsive, so that the object traverses directly to avoid repeated operations
    def(value, '__ob__'.this)
    // Type judgment
    if (Array.isArray(value)) {
      // Check whether the array has __proty__
      if (hasProto) {
        // Override the array method if there is one
        protoAugment(value, arrayMethods)
      } else {
        // Define attribute values via def, object.defineProperty
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  // If it is an object type
  walk (obj: Object) {
    const keys = Object.keys(obj)
    // Add getters and setters dynamically to implement bidirectional binding
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  // Listen for arrays
  observeArray (items: Array<any>) {
    // Walk through the array and listen for each element
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
Copy the code

defineReactive()

Source address: SRC/core/observer/index. The js – 135 line

The purpose of this method is to define reactive objects

The main work here is:

  • Start by initializing a DEP instance
  • If it is an object, call observe, recursive listening, to ensure that no matter how deeply the structure is nested, it becomes a responsive object
  • Object.defineproperty () is then called to hijack the getter and getter for the Object property
  • When fetching, triggering the getter calls dep.depend() to push the observer into the dependent array subs, the dependency collection
  • If an update is triggered, the setter does the following
    • The new value pops out with no change or no setter property
    • Observe () recursive listening is called if the new value is an object
    • Dep.notify () is then called to dispatch the update
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function, shallow? : boolean) {

  // Create a DEP instance
  const dep = new Dep()
  // Get the property descriptor of the object
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // Get custom getters and setters
  const getter = property && property.get
  const setter = property && property.set
  if((! getter || setter) &&arguments.length === 2) {
    val = obj[key]
  }
  // listen recursively if val is an object
  // A recursive call to observe ensures that an object becomes a responsive object no matter how deeply its structure is nested
  letchildOb = ! shallow && observe(val)// Intercepts getters and setters for object properties
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.// Intercepts the getter, which is triggered when the value is specified
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // Do dependency collection
      // Initialize render Watcher to access objects requiring bidirectional binding, thus triggering the get function
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // Intercepts setters that fire when the value changes
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // Determine if there is a change
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter()
      }
      // Accessor property without setter
      if(getter && ! setter)return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // Recursively listen if the new value is an objectchildOb = ! shallow && observe(newVal)// Send updates
      dep.notify()
    }
  })
}
Copy the code

Dep is the core of getter dependency collection

Depend on the collection

At the heart of dependency collection is Dep, and it is inseparable from Watcher. Let’s take a look

Dep

SRC /core/observer/dep.js

This is a class, and it’s really just a management of Watcher

Initialize an array of subs to store dependencies, i.e. observers, who depend on the data, and define methods to add, remove, update, and so on

It also has a static property target, which is a global Watcher, meaning that only one global Watcher can exist at a time

let uid = 0
export default class Dep {
  statictarget: ? Watcher; id: number; subs:Array<Watcher>;
  constructor () {
    this.id = uid++
    this.subs = []
  }
  // Add an observer
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // Remove the observer
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  depend () {
    if (Dep.target) {
      // Call Watcher's addDep function
      Dep.target.addDep(this)}}// Distribute updates (described in the next section)notify () { ... }}// Only one observer is used at a time, assign observer
Dep.target = null
const targetStack = []

export function pushTarget (target: ? Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]}Copy the code

Watcher

Source address: SRC/core/observer/watcher. Js

Watcher is also a class, also called an observer (subscriber), and it does quite a bit of complicated work, including rendering and compiling

Take a look at the source code first, and then go through the whole dependency collection process

let uid = 0
export default class Watcher {...constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // An array of Dep instances held by the Watcher instance
    this.deps = []
    this.newDeps = []
    this.depIds = new Set(a)this.newDepIds = new Set(a)this.value = this.lazy
      ? undefined
      : this.get()
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
  }
  get () 
    // This function is used to cache Watcher
    // The parent component's Watcher needs to be restored if the component has nested components
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // Invoke the callback function, known as upcateComponent, to evaluate the object requiring bidirectional binding, thus triggering dependency collection
      value = this.getter.call(vm, vm)
    } catch (e) {
      ...
    } finally {
      // Deep monitor
      if (this.deep) {
        traverse(value)
      }
      / / restore Watcher
      popTarget()
      // Clean up unwanted dependencies
      this.cleanupDeps()
    }
    return value
  }
  // called when relying on collection
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // Push the current Watcher into the array
        dep.addSub(this)}}}// Clean up unwanted dependencies (see below)
  cleanupDeps () {
    ...
  }
  // called when an update is distributed (see below)
  update () {
    ...
  }
  // Execute watcher's callback
  run () {
    ...
  }
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
}
Copy the code

Supplement:

  1. Why can we automatically get the new value and the old value of watch in our own component?

It is inside watcher.run() that the callback is executed and the new and old values are passed

  1. Why initialize two ARRAYS of Dep instances

Because Vue is data-driven, every data change is re-rendered, meaning that the vm.render() method is re-executed, firing the getter again, so it is represented by two arrays, newDeps, the newly added Dep instance array, and deps, the last added instance array

Dependency collection process

There is also this logic for the first render mount

MountComponent source address: SRC/core/instance/lifecycle. The js – 141 line

export function mountComponent (.) :Component {
  // Call the lifecycle hook function
  callHook(vm, 'beforeMount')
  let updateComponent
  updateComponent = () = > {
    // Call _update to patch (Diff) the virtual DOM returned by render to the real DOM for the first time
    vm._update(vm._render(), hydrating)
  }
  // Set an observer for the current component instance to monitor the data obtained by the updateComponent function, as described below
  new Watcher(vm, updateComponent, noop, {
    // When an update is triggered, it is called before the update
    before () {
      // Check whether the DOM is mounted, that is, it will not be executed during the first rendering and unloading
      if(vm._isMounted && ! vm._isDestroyed) {// Call the lifecycle hook function
        callHook(vm, 'beforeUpdate')}}},true /* isRenderWatcher */)
  // There is no old vNode, indicating the first rendering
  if (vm.$vnode == null) {
    vm._isMounted = true
    // Call the lifecycle hook function
    callHook(vm, 'mounted')}return vm
}
Copy the code

Dependent collection:

  • A render is instantiated before mountingwatcherAnd into thewatcherIt’s executed in the constructorthis.get()methods
  • Then it will be executedpushTarget(this), is theDep.targetAssign to the current renderwatcherAnd pushed (for recovery)
  • Then performthis.getter.call(vm, vm), which is the updateComponent() function abovevm._update(vm._render(), hydrating)
  • Then performvm._render()The render will be generatedvnode, which accesses the data on the VM and triggers the getter for the data object
  • There is one getter for each object valuedepIs called when the getter is triggereddep.depend()Method is executedDep.target.addDep(this)
  • Then some judgment is made to ensure that the same data is not added more than once, and the eligible data is pushed into the subs. At this point, the collection of dependencies is done, but not done yet. If it is an object, the recursive object fires the getter for all its children and restores the DEp.target state

Remove the subscription

To remove the subscription, call the cleanupDeps() method. For example, there is a V-if in the template. We collect the dependencies in template A that meet the criteria. When conditions change, template B is displayed and template A is hidden. So you need to remove the dependency of A

The main work here is:

  • Remove the subscription to Watcher from dep.subs by iterating through the last instance array deps
  • Then swap newDepIds with depIds and newDeps with DEps
  • Then empty newDepIds and newDeps
// Clean up unwanted dependencies
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)}}let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
Copy the code

Distributed update

notify()

When the setter is triggered, dep.notify() is called to notify all subscribers to distribute updates

notify () {
    const subs = this.subs.slice()
    if(process.env.NODE_ENV ! = ='production' && !config.async) {
      // If it is not asynchronous, sort is needed to ensure that it fires correctly
      subs.sort((a, b) = > a.id - b.id)
    }
    // Walk through the array of watcher instances
    for (let i = 0, l = subs.length; i < l; i++) {
      // Trigger the update
      subs[i].update()
    }
  }
Copy the code

update()

Called when an update is triggered

  update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      // Component data updates go here
      queueWatcher(this)}}Copy the code

queueWatcher()

Source address: SRC/core/observer/scheduler. The js – 164 line

This is a queue and an optimization point for Vue when it comes to distributing updates. Instead of triggering a Watcher callback every time the data changes, the watcher is added to a queue and executed after nextTick

The logic of flushSchedulerQueue() overlaps with that of the next section, so it should be understood in conjunction

The main work is:

  • First look up the ID with the HAS object, making sure the same Watcher is only pushed once
  • Else If a new watcher is inserted during the execution of watcher, it’s going to be here, and then it’s going to go back and forth, and it’s going to find the first place where the ID to be inserted is larger than the ID in the current queue, and it’s going to insert it into the queue, and that’s going to change the length of the queue
  • Finally, waiting ensures that nextTick will only be called once
export function queueWatcher (watcher: Watcher) {
  // Get watcher's id
  const id = watcher.id
  // Check whether the watcher with the current id has been pushed
  if (has[id] == null) {
    has[id] = true
    if(! flushing) {// The initial entry will be here
      queue.push(watcher)
    } else {
      // When the flushSchedulerQueue is executed, a new watcher is inserted if a new update is dispatched, as described below
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
    // The initial entry will be here
    if(! waiting) { waiting =true
      if(process.env.NODE_ENV ! = ='production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      // Since every update sent causes a rendering, put all watcher calls into nextTick
      nextTick(flushSchedulerQueue)
    }
  }
}
Copy the code

flushSchedulerQueue()

Source address: SRC/core/observer/scheduler. The js – 71 line

The main work here is:

  • So we sort the queue, and we sort the queue for three things. Look at the comment
  • It then iterates through the queue, executing the corresponding watcher.run(). Note that the queue length is evaluated each time through the loop, because after run, a new watcher is likely to be added, and the queueWatcher will be executed again
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort by ID under the following conditions
  // 1. Components need to be updated from parent to child, because the creation process is also parent to child
  // 2. The component we wrote takes precedence over the rendered watcher
  // 3. If a component is destroyed during the parent component's Watcher run, skip the watcher
  queue.sort((a, b) = > a.id - b.id)

  // Do not cache the queue length, as the queue length may change during traversal
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      // Execute the beforeUpdate lifecycle hook function
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // Execute our own watch callback inside the component and render the component
    watcher.run()
    // Check and stop loop updates, such as re-assigning an object during Watcher, to go into an infinite loop
    if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn('Infinite loop')
        break}}}// Keep a queue backup before resetting the state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  // Call the component activated hook activated
  callActivatedHooks(activatedQueue)
  // Invokes the component's updated hook
  callUpdatedHooks(updatedQueue)
}
Copy the code

updated()

Updated life cycle hook function

This is what happens when you call callUpdatedHooks() above, so updated

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) { callHook(vm,'updated')}}}Copy the code

So far Vue2’s response principle process of the source code is basically finished analysis, the next is to introduce the shortcomings of the above process

DefineProperty defects and handling

There are some problems with implementing reactive objects using Object.defineProperty

  • For example, when you add a new property to an object, you can’t fire a setter
  • For example, an array element change cannot be detected

And these problems, Vue2 also has corresponding solutions

Vue.set()

When adding new responsive properties to objects, you can use a global API called the vue.set () method

Source address: SRC/core/observer/index. The js – 201 line

The set method takes three arguments:

  • Target: Array or plain object
  • Key: Indicates the array subscript or the key name of an object
  • Val: indicates the new value to be replaced

The main work here is:

  • If it is an array and the subscript is valid, use the overwritten splice instead
  • If it is an object and the key exists in target, replace the value
  • If there is no__ob__, indicating that the object is not a responsive object and is returned by direct assignment
  • Finally, make the new property reactive and distribute the update
export function set (target: Array<any> | Object, key: any, val: any) :any {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}// If it is an array and is a valid subscript
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // Use splice instead. Note that splice is not native, so it can be detected
    target.splice(key, 1, val)
    return val
  }
  // This is an object
  // If the key exists in the target, it can be assigned directly
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  / / to get target. __ob__
  const ob = (target: any).__ob__
  if(target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! = ='production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // As described in the Observer, if you do not have this property, it is not a reactive object
  if(! ob) { target[key] = valreturn val
  }
  // Then make the newly added attribute reactive
  defineReactive(ob.value, key, val)
  // Manually send updates
  ob.dep.notify()
  return val
}
Copy the code

Overriding array methods

Source address: SRC/core/observer/array. Js

What is done here is mainly:

  • Saves a list of methods that change the array
  • When executing a method in the list, such as push, save the original push and then do the reactive processing before executing the method
// Get the array prototype
const arrayProto = Array.prototype
// Create an object that inherits the array stereotype
export const arrayMethods = Object.create(arrayProto)
// Changes the list of methods in the original array
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
// Override the array event
methodsToPatch.forEach(function (method) {
  // Save the original event
  const original = arrayProto[method]
  // Create a responsive object
  def(arrayMethods, method, function mutator (. args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // Send updates
    ob.dep.notify()
    // After we do what we need to do, we can execute the original event
    return result
  })
})
Copy the code

Past wonderful

  • Where does the render function come from? Simple template compilation in Vue
  • Simple virtual DOM and Diff algorithms, and Vue2 and Vue3 differences
  • Vue3 7 and Vue2 12 components communication, worth collecting
  • What are the latest updates to Vue3.2
  • JavaScript advanced knowledge
  • Front-end anomaly monitoring and DISASTER recovery
  • 20 minutes to help you learn HTTP and HTTPS, and consolidate your HTTP knowledge

conclusion

If this article is of any help to you, please give it a thumbs up. Thank you