vue源码中值得学习的方法

vue源码中值得学习的方法

    • 1. 数据类型判断
    • 2. 利用闭包构造map缓存数据
    • 3. 二维数组扁平化
    • 4. 方法拦截
    • 5.继承的实现
    • 6. 执行一次
    • 7. 递归判断一个对象是否和另个一个对象完全相同

1. 数据类型判断

Object.prototype.toString.call()返回的数据格式为 [object Object]类型,然后用slice截取第8位~倒数一位,得到结果为 Object

var _toString = Object.prototype.toString;
function toRawType (val) {
     
	return _toString.call(val).slice(8, -1)
}
_toString .call({
     }) // "[object Object]"
toRawType({
     }) // "Object"

_toString .call([]) // "[object Array]"
toRawType([])  // "Array"    

_toString .call(true) // "[object Boolean]"
toRawType(true) // "Boolean"

_toString .call(undefined) // "[object Undefined]" 
toRawType(undefined) // Undefined

_toString .call(null) // "[object Null]" 
toRawType(null) // "Null"

_toString .call(function(){
     }) // "[object Function]"
toRawType(function(){
     }) // "Function"

2. 利用闭包构造map缓存数据

vue判断我们写的组件名是不是html内置标签的时候,如果用数组类遍历那么将要循环很多次获取结果,如果把数组转为对象,把标签名设置为对象的key,那么不用依次遍历查找,只需要查找一次就能获取结果,提高了查找效率

function makeMap (str, expectsLowerCase) {
     
	// 构建闭包集合map
	var map = Object.create(null)
	var list =str.split(',')
	for (var i = 0; i < list.length; i++) {
     
		map[list[i]] = true
	}
	return expectsLowerCase 
		? function (val) {
     return map[val.toLowerCase()]}
		: function (val) {
      return map[val];}
}

// 利用闭包,每次判断是否是内置标签只需调用isHTMLTag
var isHTMLTag = makeMap('html,body,base,head,link,meta,style,title')
console.log('res', isHTMLTag('body')) // true

这里还没看出来哪里好用,先占个坑,后面再补补

3. 二维数组扁平化

vue_createElement格式化传入的children的时候用到了simpleNormalizeChildren函数,原来是为了拍平数组,使二维数组扁平化(只减少一级嵌套),类似lodash中的flatten方法。

想将多维array降维成一维数组,可以用lodash中的flattenDeep方法。

// 先看lodash中的flatten 作用 减少一级array嵌套深度。  
let arr = [1, [2, [3, [4]], 5], [20,30]]
_.flatten(arr)
// 得到结果为  [1, 2, [3, [4]], 5, 20, 30]

// vue中 减少一级array嵌套深度
function simpleNormalizeChildren (children) {
     
  for (var i = 0; i < children.length; i++) {
     
    if (Array.isArray(children[i])) {
     
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}

simpleNormalizeChildren (arr) // [1, 2, [3, [4]], 5, 20, 30]

// 降维成一维数组
function simpleNormalizeChildren (children) {
     
  for (var i = 0; i < children.length; i++) {
     
    if (Array.isArray(children[i])) {
     
      children = Array.prototype.concat.apply([], children)
    }
  }
  return children
}

simpleNormalizeChildren (arr) // [1, 2, 3, 4, 5, 20, 30]

// es6中 等价于  
// 降一维
function simpleNormalizeChildren (children) {
     
   return [].concat(...children)
}
simpleNormalizeChildren(arr) // [1, 2, [3, [4]], 5, 20, 30]

// 降维成一维数组
function simpleNormalizeChildren (children) {
     
	let result = []
	for (var i = 0; i < children.length; i++) {
     
		if (Array.isArray(children[i])){
     
			result = [].concat(...children)
			children = result
		}
	}
	return result
}
simpleNormalizeChildren (arr) // [1, 2, 3, 4, 5, 20, 30]

4. 方法拦截

vue中利用Object.defineProperty收集依赖,从而触发更新视图,但是数组却无法监测到数据的变化,但是为什么数组在使用push pop等方法的时候可以触发页面更新呢,那是因为vue内部拦截了这些方法。

// 重写push等方法,然后再把原型指回原方法
var ARRAY_METHOD = ['push', 'pop', 'shift', 'unshift', 'reverse',  'sort', 'splice'];
var array_methods = Object.create(Array.prototype);
ARRAY_METHOD.forEach(method => {
     
	array_methods[method] = function () {
     
		// 拦截方法
		console.log('调用的是拦截的 ' + method + ' 方法,进行依赖收集');
		return Array.prototype[method].apply(this, arguments)
	}
})

var arr = [1, 2, 3]
arr.push(4) // 4   没有打印文字
arr.__proto__ =  array_methods // 改变arr的原型
arr.push(4)  // 5 调用的是拦截的 unshift 方法,进行依赖收集

5.继承的实现

vue中调用Vue.extend实例化组件,Vue.extend就是VueComponent构造函数,而VueComponent利用Object.create继承Vue,所以在平常开发中VueVue.extend区别不是很大。这边主要学习用es5原生方法实现继承,当然,es6中 class类直接用extends继承。

// 继承方法
function inheritPrototype(Son, Father) {
     
	var prototype = Object.create(Father.prototype)
	// 下面这一行 我去掉后 也不影响继承 作用是什么 
	prototype.constructor = Son
	// 把Fater.prototype复制给Son.prototype
	Son.prototype = prototype
}

function Father(name) {
     
	this.name = name
	this.arr = [1, 2, 3]
}

Father.prototype.getName = function() {
     
	console.log(this.name)
}

function Son(name, age) {
     
	// 继承了 除Father.prototype外的内容
	Father.call(this, name)
	this.age = age
}

inheritPrototype(Son, Father)
Son.prototype.getAge = function() {
     
    console.log(this.age)
}

// 测试
var son1 = new Son("hello", 25)
son1.getName()            // hello
son1.getAge()             // 25
son1.arr.push(4)          
console.log(son1.arr)     // [1, 2, 3, 4]

var son2 = new Son("world", 35)
son2.getName()            // world
son2.getAge()             // 35
console.log(son2.arr)     // [1, 2, 3]

es6实现继承

// 父类
class People {
     
    constructor(name) {
     
        this.name = name
    }
    eat() {
     
        console.log(`${
       this.name} eat something`)
    }
}

// 子类
class Student extends People {
     
    constructor(name, number) {
     
        super(name)
        this.number = number
    }
    sayHi() {
     
        console.log(`姓名 ${
       this.name} 学号 ${
       this.number}`)
    }
}

// 子类
class Teacher extends People {
     
    constructor(name, major) {
     
        super(name)
        this.major = major
    }
    teach() {
     
        console.log(`${
       this.name} 教授 ${
       this.major}`)
    }
}

// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 学号 100
xialuo.eat() // 夏洛 eat something

// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name) // 王老师
console.log(wanglaoshi.major) // 语文
wanglaoshi.teach() // 王老师 教授 语文
wanglaoshi.eat() // 王老师 eat something

原型链图
vue源码中值得学习的方法_第1张图片

6. 执行一次

once 方法相对比较简单,直接利用闭包实现

  • 传入要执行一次的函数 fn
  • 设置标识为 false
  • 返回一个函数
function once(fn) {
     
	var called = false;
	return function() {
     
		if (!called) {
     
			called = true;
			fn.apply(this, arguments)
		} else {
     
			console.log('函数已执行过一次,不会再执行')
		}
	}
}

function test() {
     
	console.log('我只会执行一次')
}

var test2 = once(test)
test2() // 我只会执行一次
test2() // 函数已执行过一次,不会再执行
test2() // 函数已执行过一次,不会再执行
  • 调用 once 函数后,会返回一个函数,赋值给 test2
  • 第一次调用 test2 后,在函数内部,called 初次为 false, 所以可以执行函数 test,然后把标识 called 设置为true,就类似关闭了大门,下次不再执行。
  • 之后在调用 test2 , test 将不再执行

7. 递归判断一个对象是否和另个一个对象完全相同

判断两个对象是否相同,主要是判断两个对象包含的值都是一样的,如果包含的值依然是个对象,则继续递归调用判断是否相同。

function isObject(obj) {
     
  return obj !== null && typeof obj === 'object'
}

function looseEqual(a, b) {
     
	// 如果是同一个对象 则相同
	if (a === b) return true
	// 判断是否是对象
	const isObjectA = isObject(a)
	const isObjectB = isObject(b)
	// 两者都是对象
	if (isObjectA  && isObjectB) {
     
		try {
     
      // 判断是否是数组
      const isArrayA = Array.isArray(a)
      const isArrayB = Array.isArray(b)
      // 两者都是数组
      if (isArrayA && isArrayB) {
     
        // 长度得一样 同时每一项要相同 递归调用
        return a.length === b.length && a.every((e, i) => {
     
          return looseEqual(e, b[i])
        })
      } else if (a instanceof Date && b instanceof Date) {
     
        // 如果都是时间对象 则需要保证时间戳相同
        return a.getTime() === b.getTime()
      } else if (!isArrayA && !isArrayB) {
     
        // 两者都不是数组 则为对象
        // 拿到两者的key值 存入数组
        const keysA = Object.keys(a)
        const keysB = Object.keys(b)
        // 对象属性的个数要一样 递归判断每一个值是否相同
        return keysA.length === keysB.length && keysA.every(key => {
     
          return looseEqual(a[key], b[key])
        })
      } else {
     
        return false
      }
		} catch(e) {
     
      return false
    }
	} else if (!isObjectA && !isObjectB) {
     
    // 两者都不是对象
    // 转成字符串 看值是否一致
    return String(a) === String(b)
  } else {
     
    return false
  }
}

// 例子
let a1 = [1,2,3,{
     a:1,b:2,c:[1,2,3]}];
let b1 = [1,2,3,{
     a:1,b:2,c:[1,2,3]}];
console.log(looseEqual(a1,b1)); // true

let a2 = [1,2,3,{
     a:1,b:2,c:[1,2,3,4]}];
let b2 = [1,2,3,{
     a:1,b:2,c:[1,2,3]}];
console.log(looseEqual(a2,b2)); // false
  • 判断两个值是否相同,无论是原始类型还是对象类型,如果相同,则直接返回true。
  • 如果两个都是对象,则分为两种情况,数组和对象。
    • 都是数组,则保证长度一致,同时调用 every 函数递归调用函数,保证每一项都一样
    • 是时间对象,则保证时间戳相同
    • 是对象,则先取出 key 组成的数组,两者 key 的个数要相同;再递归调用比较 value 值是否相同
  • 以上都不满足,直接返回false
  • 如果两者都不是对象,转成字符串后进行比较。
  • 以上都不满足,直接返回false

谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~

感谢
vue源码中值得学习的方法
盘点Vue源码中用到的工具函数

你可能感兴趣的:(javascript,Vue,vue源码,数组降维,方法拦截,闭包,数据类型判断)