乐趣区

关于javascript:滴滴前端一面常考手写面试题合集

应用 Promise 封装 AJAX 申请

// promise 封装实现:function getJSON(url) {
  // 创立一个 promise 对象
  let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
    // 新建一个 http 申请
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
      // 当申请胜利或失败时,扭转 promise 的状态
      if (this.status === 200) {resolve(this.response);
      } else {reject(new Error(this.statusText));
      }
    };
    // 设置谬误监听函数
    xhr.onerror = function() {reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置申请头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 申请
    xhr.send(null);
  });
  return promise;
}

树形构造转成列表(解决菜单)

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]
转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

实现代码如下:

function treeToList(data) {let res = [];
  const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
        delete item.children;
      }
      res.push(item);
    });
  };
  dfs(data);
  return res;
}

实现 prototype 继承

所谓的原型链继承就是让新实例的原型等于父类的实例:

// 父办法
function SupperFunction(flag1){this.flag1 = flag1;}

// 子办法
function SubFunction(flag2){this.flag2 = flag2;}

// 父实例
var superInstance = new SupperFunction(true);

// 子继承父
SubFunction.prototype = superInstance;

// 子实例
var subInstance = new SubFunction(false);
// 子调用本人和父的属性
subInstance.flag1;   // true
subInstance.flag2;   // false

将 VirtualDom 转化为实在 DOM 构造

这是以后 SPA 利用的外围概念之一

// vnode 构造:// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === 'number') {vnode = String(vnode);
  }
  // 字符串类型间接就是文本节点
  if (typeof vnode === 'string') {return document.createTextNode(vnode);
  }
  // 一般 DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    })
  }
  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

Promise

// 模仿实现 Promise
// Promise 利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定
// 2. 返回值穿透
// 3. 谬误冒泡

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已胜利
const REJECTED = 'REJECTED';    // 已失败

class Promise {constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将胜利、失败后果放在 this 上,便于 then、catch 拜访
    this.value = undefined;
    this.reason = undefined;
    // 胜利态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态能力更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 胜利态函数顺次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态能力更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数顺次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立刻执行 executor
      // 把外部的 resolve 和 reject 传入 executor,用户可调用 resolve 和 reject
      exector(resolve, reject);
    } catch(e) {
      // executor 执行出错,将谬误内容 reject 抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected :
      reason => {throw new Error(reason instanceof Error ? reason.message : reason) }
    // 保留 this
    const self = this;
    return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {
          // try 捕捉谬误
          try {
            // 模仿微工作
            setTimeout(() => {const result = onFulfilled(self.value);
              // 分两种状况:// 1. 回调函数返回值是 Promise,执行 then 操作
              // 2. 如果不是 Promise,调用新 Promise 的 resolve 函数
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {setTimeout(() => {const result = onRejected(self.reason);
              // 不同点:此时是 reject
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {setTimeout(() => {const result = onFulfilled(self.value);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          });
        } catch(e) {reject(e);
        }
      } else if (self.status === REJECTED) {
        try {setTimeout(() => {const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {reject(e);
        }
      }
    });
  }
  catch(onRejected) {return this.then(null, onRejected);
  }
  static resolve(value) {if (value instanceof Promise) {
      // 如果是 Promise 实例,间接返回
      return value;
    } else {
      // 如果不是 Promise 实例,返回一个新的 Promise 对象,状态为 FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {return new Promise((resolve, reject) => {reject(reason);
    })
  }
  static all(promiseArr) {
    const len = promiseArr.length;
    const values = new Array(len);
    // 记录曾经胜利执行的 promise 个数
    let count = 0;
    return new Promise((resolve, reject) => {for (let i = 0; i < len; i++) {// Promise.resolve() 解决,确保每一个都是 promise 实例
        Promise.resolve(promiseArr[i]).then(
          val => {values[i] = val;
            count++;
            // 如果全副执行完,返回 promise 的状态就能够扭转了
            if (count === len) resolve(values);
          },
          err => reject(err),
        );
      }
    })
  }
  static race(promiseArr) {return new Promise((resolve, reject) => {
      promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}

封装异步的 fetch,应用 async await 形式来应用

(async () => {
    class HttpRequestUtil {async get(url) {const res = await fetch(url);
            const data = await res.json();
            return data;
        }
        async post(url, data) {
            const res = await fetch(url, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async put(url, data) {
            const res = await fetch(url, {
                method: 'PUT',
                headers: {'Content-Type': 'application/json'},
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async delete(url, data) {
            const res = await fetch(url, {
                method: 'DELETE',
                headers: {'Content-Type': 'application/json'},
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
    }
    const httpRequestUtil = new HttpRequestUtil();
    const res = await httpRequestUtil.get('http://golderbrother.cn/');
    console.log(res);
})();

参考 前端进阶面试题具体解答

深拷贝

递归的残缺版本(思考到了 Symbol 属性):

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数解决
  if (typeof target !== 'object' || target === null) {return target;}
  // 哈希表中存在间接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对 Symbol 属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

实现一个函数判断数据类型

function getType(obj) {if (obj === null) return String(obj);
   return typeof obj === 'object' 
   ? Object.prototype.toString.call(obj).replace('[object', '').replace(']','').toLowerCase()
   : typeof obj;
}

// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callbackfn + 'is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为 undefined 的状况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}
    // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
    if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

二叉树档次遍历

// 二叉树档次遍历

class Node {constructor(element, parent) {
    this.parent = parent // 父节点 
    this.element = element // 以后存储内容
    this.left = null // 左子树
    this.right = null // 右子树
  }
}

class BST {constructor(compare) {
    this.root = null // 树根
    this.size = 0 // 树中的节点个数

    this.compare = compare || this.compare
  }
  compare(a,b) {return a - b}
  add(element) {if(this.root === null) {this.root = new Node(element, null)
      this.size++
      return
    }
    // 获取根节点 用以后增加的进行判断 放右边还是放左边
    let currentNode = this.root 
    let compare
    let parent = null 
    while (currentNode) {compare = this.compare(element, currentNode.element)
      parent = currentNode // 先将父亲保存起来
      // currentNode 要不停的变动
      if(compare > 0) {currentNode = currentNode.right} else if(compare < 0) {currentNode = currentNode.left} else {currentNode.element = element // 相等时 先笼罩后续解决}
    }

    let newNode = new Node(element, parent)
    if(compare > 0) {parent.right = newNode} else if(compare < 0) {parent.left = newNode}

    this.size++
  }
  // 档次遍历 队列
  levelOrderTraversal(visitor) {if(this.root == null) {return}
    let stack = [this.root]
    let index = 0 // 指针 指向 0
    let currentNode 
    while (currentNode = stack[index++]) {
      // 反转二叉树
      let tmp = currentNode.left
      currentNode.left = currentNode.right
      currentNode.right = tmp
      visitor.visit(currentNode.element)
      if(currentNode.left) {stack.push(currentNode.left)
      }
      if(currentNode.right) {stack.push(currentNode.right)
      }
    }
  }
}
// 测试
var bst = new BST((a,b)=>a.age-b.age) // 模仿 sort 办法

// ![](http://img-repo.poetries.top/images/20210522203619.png)
// ![](http://img-repo.poetries.top/images/20210522211809.png)
bst.add({age: 10})
bst.add({age: 8})
bst.add({age:19})
bst.add({age:6})
bst.add({age: 15})
bst.add({age: 22})
bst.add({age: 20})

// 应用访问者模式
class Visitor {constructor() {this.visit = function (elem) {elem.age = elem.age*2}
  }
}

// ![](http://img-repo.poetries.top/images/20210523095515.png)
console.log(bst.levelOrderTraversal(new Visitor()))

应用 setTimeout 实现 setInterval

setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。

针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果

function mySetInterval(fn, timeout) {
  // 控制器,管制定时器是否继续执行
  var timer = {flag: true};
  // 设置递归函数,模仿定时器执行。function interval() {if (timer.flag) {fn();
      setTimeout(interval, timeout);
    }
  }
  // 启动定时器
  setTimeout(interval, timeout);
  // 返回控制器
  return timer;
}

Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
  }
  const O = Object(this);
  const len = O.length >>> 0;
  let k = 0;
  while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
    }
    k++;
  }
}

实现防抖函数(debounce)

防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则从新计时。

那么与节流函数的区别间接看这个动画实现即可。

手写简化版:

// 防抖函数
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {clearTimeout(timer);
    timer = setTimeout(() => {fn.apply(this, args);
    }, delay);
  };
};

实用场景:

  • 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
  • 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似

生存环境请用 lodash.debounce

实现字符串的 repeat 办法

输出字符串 s,以及其反复的次数,输入反复的后果,例如输出 abc,2,输入 abcabc。

function repeat(s, n) {return (new Array(n + 1)).join(s);
}

递归:

function repeat(s, n) {return (n > 0) ? s.concat(repeat(s, --n)) : "";
}

对象数组列表转成树形构造(解决菜单)

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:

function listToTree(data) {let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
  }
  for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
      temp[temp[i].parentId].children.push(temp[i]);
    } else {treeData.push(temp[i]);
    }
  }
  return treeData;
}

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");
  }
  // 保留 this 的值
  var self = this;

  return function F() {
    // 思考 new 的状况
    if(this instanceof F) {return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}

实现一个链表构造

链表构造

看图了解 next 层级

// 链表 从头尾删除、减少 性能比拟好
// 分为很多类 罕用单向链表、双向链表

// js 模仿链表构造:增删改查

// node 节点
class Node {constructor(element,next) {
    this.element = element
    this.next = next
  } 
}

class LinkedList {constructor() {
   this.head = null // 默认应该指向第一个节点
   this.size = 0 // 通过这个长度能够遍历这个链表
 }
 // 减少 O(n)
 add(index,element) {if(arguments.length === 1) {
     // 向开端增加
     element = index // 以后元素等于传递的第一项
     index = this.size // 索引指向最初一个元素
   }
  if(index < 0 || index > this.size) {throw new Error('增加的索引不失常')
  }
  if(index === 0) {
    // 间接找到头部 把头部改掉 性能更好
    let head = this.head
    this.head = new Node(element,head)
  } else {
    // 获取以后头指针
    let current = this.head
    // 不停遍历 直到找到最初一项 增加的索引是 1 就找到第 0 个的 next 赋值
    for (let i = 0; i < index-1; i++) { // 找到它的前一个
      current = current.next
    }
    // 让创立的元素指向上一个元素的下一个
    // 看图了解 next 层级
    current.next = new Node(element,current.next) // 让以后元素指向下一个元素的 next
  }

  this.size++;
 }
 // 删除 O(n)
 remove(index) {if(index < 0 || index >= this.size) {throw new Error('删除的索引不失常')
  }
  this.size--
  if(index === 0) {
    let head = this.head
    this.head = this.head.next // 挪动指针地位

    return head // 返回删除的元素
  }else {
    let current = this.head
    for (let i = 0; i < index-1; i++) { // index- 1 找到它的前一个
      current = current.next
    }
    let returnVal = current.next // 返回删除的元素
    // 找到待删除的指针的上一个 current.next.next 
    // 如删除 200,100=>200=>300 找到 200 的上一个 100 的 next 的 next 为 300,把 300 赋值给 100 的 next 即可
    current.next = current.next.next 

    return returnVal
  }
 }
 // 查找 O(n)
 get(index) {if(index < 0 || index >= this.size) {throw new Error('查找的索引不失常')
  }
  let current = this.head
  for (let i = 0; i < index; i++) {current = current.next}
  return current
 }
}


var ll = new LinkedList()

ll.add(0,100) // Node {ellement: 100, next: null}
ll.add(0,200) // Node {element: 200, next: Node { element: 100, next: null} }
ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null} } }
ll.add(300)
ll.remove(0)

console.log(ll.get(2),'get')
console.log(ll.head)

module.exports = LinkedList

替换 a,b 的值,不能用长期变量

奇妙的利用两个数的和、差:

a = a + b
b = a - b
a = a - b

实现 instanceOf

// 模仿 instanceof
function instance_of(L, R) {
  //L 示意左表达式,R 示意右表达式
  var O = R.prototype; // 取 R 的显示原型
  L = L.__proto__; // 取 L 的隐式原型
  while (true) {if (L === null) return false;
    if (O === L)
      // 这里重点:当 O 严格等于 L 时,返回 true
      return true;
    L = L.__proto__;
  }
}

实现一个迷你版的 vue

入口

// js/vue.js
class Vue {constructor (options) {
    // 1. 通过属性保留选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2. 把 data 中的成员转换成 getter 和 setter,注入到 vue 实例中
    this._proxyData(this.$data)
    // 3. 调用 observer 对象,监听数据的变动
    new Observer(this.$data)
    // 4. 调用 compiler 对象,解析指令和差值表达式
    new Compiler(this)
  }
  _proxyData (data) {
    // 遍历 data 中的所有属性
    Object.keys(data).forEach(key => {
      // 把 data 的属性注入到 vue 实例中
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get () {return data[key]
        },
        set (newValue) {if (newValue === data[key]) {return}
          data[key] = newValue
        }
      })
    })
  }
}

实现 Dep

class Dep {constructor () {
    // 存储所有的观察者
    this.subs = []}
  // 增加观察者
  addSub (sub) {if (sub && sub.update) {this.subs.push(sub)
    }
  }
  // 发送告诉
  notify () {
    this.subs.forEach(sub => {sub.update()
    })
  }
}

实现 watcher

class Watcher {constructor (vm, key, cb) {
    this.vm = vm
    // data 中的属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb

    // 把 watcher 对象记录到 Dep 类的动态属性 target
    Dep.target = this
    // 触发 get 办法,在 get 办法中会调用 addSub
    this.oldValue = vm[key]
    Dep.target = null
  }
  // 当数据发生变化的时候更新视图
  update () {let newValue = this.vm[this.key]
    if (this.oldValue === newValue) {return}
    this.cb(newValue)
  }
}

实现 compiler

class Compiler {constructor (vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // 编译模板,解决文本节点和元素节点
  compile (el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 解决文本节点
      if (this.isTextNode(node)) {this.compileText(node)
      } else if (this.isElementNode(node)) {
        // 解决元素节点
        this.compileElement(node)
      }

      // 判断 node 节点,是否有子节点,如果有子节点,要递归调用 compile
      if (node.childNodes && node.childNodes.length) {this.compile(node)
      }
    })
  }
  // 编译元素节点,解决指令
  compileElement (node) {// console.log(node.attributes)
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // v-text --> text
        attrName = attrName.substr(2)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }

  update (node, key, attrName) {let updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }

  // 解决 v-text 指令
  textUpdater (node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
  }
  // v-model
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {node.value = newValue})
    // 双向绑定
    node.addEventListener('input', () => {this.vm[key] = node.value
    })
  }

  // 编译文本节点,解决差值表达式
  compileText (node) {// console.dir(node)
    // {{msg}}
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if (reg.test(value)) {let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])

      // 创立 watcher 对象,当数据扭转更新视图
      new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {return node.nodeType === 3}
  // 判断节点是否是元素节点
  isElementNode (node) {return node.nodeType === 1}
}

实现 Observer

class Observer {constructor (data) {this.walk(data)
  }
  walk (data) {
    // 1. 判断 data 是否是对象
    if (!data || typeof data !== 'object') {return}
    // 2. 遍历 data 对象的所有属性
    Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])
    })
  }
  defineReactive (obj, key, val) {
    let that = this
    // 负责收集依赖,并发送告诉
    let dep = new Dep()
    // 如果 val 是对象,把 val 外部的属性转换成响应式数据
    this.walk(val)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
      },
      set (newValue) {if (newValue === val) {return}
        val = newValue
        that.walk(newValue)
        // 发送告诉
        dep.notify()}
    })
  }
}

应用

<!DOCTYPE html>
<html lang="cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Mini Vue</title>
</head>
<body>
  <div id="app">
    <h1> 差值表达式 </h1>
    <h3>{{msg}}</h3>
    <h3>{{count}}</h3>
    <h1>v-text</h1>
    <div v-text="msg"></div>
    <h1>v-model</h1>
    <input type="text" v-model="msg">
    <input type="text" v-model="count">
  </div>
  <script src="./js/dep.js"></script>
  <script src="./js/watcher.js"></script>
  <script src="./js/compiler.js"></script>
  <script src="./js/observer.js"></script>
  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue',
        count: 100,
        person: {name: 'zs'}
      }
    })
    console.log(vm.msg)
    // vm.msg = {test: 'Hello'}
    vm.test = 'abc'
  </script>
</body>
</html>
退出移动版