前言
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第24期,链接:第24期 【vue2 工具函数】初学者也能看懂的 Vue2 源码中那些实用的基础工具函数
其实对于Vue2源码系列以及有了一篇[源码阅读]vue2源码系列(中) - 掘金 (juejin.cn)因为Vue2的源码比较多,所以花了几天时间给自己理解一下🤦♀️
源码分析
polyfillBind()
定义:其实就是为了兼容以前的老版本,所以实现了一个bind方法
function polyfillBind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundFn._length = fn.length;
return boundFn
}
function nativeBind (fn, ctx) {
return fn.bind(ctx)
}
var bind = Function.prototype.bind
? nativeBind
: polyfillBind;
首先我们先复习一下bind,apply,call
function.prototype.call()
定义:使用指定的this的值和单独给出来的参数来调用函数
它的返回值是this的值和调用函数的返回值
我的理解是:实现属性或者方法继承和调用函数
- 实现继承:
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
console.log(cheese);
console.log(fun)
Food {name: 'feta', price: 5, category: 'food'}
Toy {name: 'robot', price: 40, category: 'toy'}
这里其实就可以理解为一种继承,Food和Toy都继承了Product的name和price属性
- 调用函数
function greet() {
var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
console.log(reply);
}
var obj = {
animal: 'cats', sleepDuration: '12 and 16 hours'
};
greet.call(obj); // cats typically sleep between 12 and 16 hours
这里面我们可以理解为:greet函数执行时,this其实是指向obj的,也就是我现在可以在这个函数里访问这个obj的一些属性
注意:
call的第一个参数表示的是this
- 如果在非严格模式下,我们将
this的指向设置为null或者undefined,这时候我们的this就会自动指向window - 如果在严格模式,你只要写了,无论是什么,都是
this
具体可以阅读:面试官问:能否模拟实现JS的call和apply方法 - 掘金 (juejin.cn)
function.prototype.apply()
其实他的用法和上面介绍的call非常类似,区别就在于:call可以接受很多参数,但是apply只可以接受最多两个参数
- 第一个参数:是用来指定this的指向
- 第二个参数:是一个数组或者类数组,用来装所有的参数
使用:当我们可以确定参数是多少时,我们就可以使用call,没法确定就可以使用apply
function.prototype.bind()
定义:bind()方法会创建一个新的函数,然后当bind()被调用,第一个参数作为this的指向,其余参数作为函数的新参数
使用:创建一个函数,目的为了不改变this
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();//9,因为函数在全局调用
var boundGetX = retrieveX.bind(module);//this绑定到了module身上
boundGetX(); // 81
偏函数:
大概意思就是,你里面的参数(除了第一个)都会作为函数的参数,后续你再次调用绑定函数时,你传入的参数也依然会作为函数的参数,只是他们会被插在原先那个函数参数的后面
function list() {
return [...arguments];
}
function addArguments(arg1, arg2) {
return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);//bind会创建一个函数
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);//bind会创建一个函数
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
var result2 = addThirtySeven(5);
// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);//因为我之前已经固定只可以有两个参数,现在两个参数已经满了,所以10就上不去
// 37 + 5 = 42 ,第二个参数被忽略
代码参考链接:Function.prototype.bind() - JavaScript | MDN (mozilla.org)
所以:你会发现其实bind他是会创建一个函数,如果你不去调用,那么什么用也没有
好了现在我们开始调试代码
注意:bind你可以没有参数,只要你有参数,那么你第一个参数一定是this的指向
function list() {
console.log("arguments",arguments);
return [...arguments]
}
let bindFun = bind(list,null)//this是null,在非严格模式下,指向window
console.log("bindFun",bindFun(1,2,3));
arguments Arguments(3) [1, 2, 3, callee: <accessor>,Symbol(Symbol.iterator): ƒ]
bindFun (3) [1, 2, 3]
理解:
-
bind(),其实就是在调用那个bind,但是bind的返回值是一个函数 -
下面就直接调用,调用的时候,我们会执行那个
fn,这边就是那个list,但是刚刚我们之传入了两个参数- 一个是函数
fn,就是调用的函数 - 一个是
bind(thisArgs)的thisArgs
也就是我们还没有传入其他参数
- 一个是函数
-
传入参数
bindFun(1,2,3)
toArray()
定义:就是将类数组转为数组,并且支持定义start位置
function toArray(list, start) {
start = start || 0;
var i = list.length - start;
// 确定array的大小
var ret = new Array(i);
while (i--) {
// 将数放进去
ret[i] = list[i + start];
}
return ret
}
但是前提条件就是:类数组,因为类数组具有索引
extend()
定义:将对象进行合并
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
let obj1 = {}
let obj2 = {name:'summer',age:13,hobby:["music","coding"]}
console.log(extend(obj1, obj2));
其实我感觉它就是一种浅拷贝:复制只能复制一层,如果后续是引用类型的,那么复制的值改变了,原来的值也会改变
let obj2 = { name: 'summer', age: 13, hobby: ["music", 'coding'] }
let obj3 = extend({},obj2)
console.log(obj3);
obj3.name = "alex"
console.log(obj3);
obj3.hobby = ["sing", 'basketball']
console.log(obj3);
console.log("obj2",obj2);
{name: 'summer', age: 13, hobby: Array(2)}
{name: 'alex', age: 13, hobby: Array(2)}
{name: 'alex', age: 13, hobby: Array(2)}
obj2 {name: 'summer', age: 13, hobby: Array(2)}
toObject()
定义:将二维数组转化为对象,也可以将多维数组进行转化,但是问题就是,他会进行覆盖,因为我每次每次去调用extend()函数,key每次都是从0开始
function toObject(arr) {
var res = {};
for (var i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res
}
console.log("toObject",toObject([[1,2]]));
toObject {0: 1, 1: 2}
假如操作的是多维数组,例如[[1,2],[3,4]]
console.log("toObject",toObject([[1,2],[3,4]]));//toObject {0: 3, 1: 4}
如果操作的是一维数组,那么直接就是空对象,因为在extend()你就没法进行遍历for..in要求你必须是可迭代的
收获
第一次看Vue的源码,感觉这次收获满满吧
- 了解到了
bind,apply,callapply和call其实功能类似bind的特点就是,它会返回一个新的函数- 用
apply和call来实现一个bind
参考链接: