一、什么是函数劫持?
顾名思义,即在一个函数运行之前把它劫持下来,添加我们想要的功能。当这个函数实际运行的时候,它已经不是原本的函数了,而是带上了被我们添加上去的功能。这也是我们常见的【钩子函数】的原理之一。
二、常见的劫持方法
1、系统内置功能的重写:就是将系统内置函数功能修改为自己想要实现的功能;也就是系统内置函数的改写
var _log = console.log
console.log = function(str) {
_log(str + " XX学苑")
}
console.log("hello")
2、this关键字的引用劫持:就是通过函数的一些特殊方法来自己指定函数的调用者
(1)this的劫持1 —— call()方法
var obj = {
name: "gang",
fun: function(n1, n2) {
var age = n1
var height = n2
console.log(this.name)
console.log(age)
console.log(height)
}
}
var obj2 = {
name: "shanghai"
}
obj.fun.call(obj2, 21, 180)
就是通过函数的call方法将对象obj中fun方法的调用者改写为对象obj2,也就是将fun方法中的this指向改为obj2,所以最终打印出来的this.name就是obj2的name: shanghai。
(2)this的劫持2 —— apply() 方法
var obj = {
name: "gang",
fun: function(n1, n2) {
var age = n1
var height = n2
console.log(this.name)
console.log(age)
console.log(height)
}
}
var obj2 = {
name: "shanghai"
}
obj.fun.apply(obj2, [21, 180])
apply()方法和call()方法的作用和用法基本一致,唯一的区别是call方法的参数是直接放进去,用逗号隔开,而apply方法的所有参数必须放在一个数组中再传入。
这种方法也可以用来比较纯数字数组中的元素的最大值和最小值:
var arr = [10,230,40,6,2,5,3]
var maxnum = Math.max.apply(arr,arr) //相当于arr.max(10,230,40,6,2,5,3)
var minnum = Math.min.apply(arr,arr)//相当于arr.min(10,230,40,6,2,5,3)
console.log("max number = " + maxnum)
console.log("min number = " + minnum)
(3)this的劫持3 —— bind()方法
var obj = {
name: "shanghai"
}
var obj2 = {
name: "gang",
fun: function(n1, n2) {
var age = n1
var height = n2
console.log(this.name)
console.log(age)
console.log(height)
}.bind(obj)
}
obj2.fun(21, 180)
bind()方法就是在函数设计的时候就指定函数的调用者,也就是指定函数中this的指向;其参数传入形式和call方法一样,是直接传入参数。
三、JavaScript数据劫持的两个API
(1)Object.defineProperty(target, key, desc)(vue2使用)
参数target: 目标对象
参数key: 将要操作的对象中的属性或名称
参数desc: 对象的描述
Object.defineProperty()用来访问一个对象的设置,允许精确地添加或修改对象的属性
劫持即是通过Object.defineProperty()对对象属性的set和get操作与检测
var data = {
name:'xiaoqiao'
}
Object.keys(data).forEach(function(key){
Object.defineProperty(data,key,{
//enumerable: false,可否被枚举/遍历
//writable: false,读写控制
//configurable: false,可否重新定义属性,可否被删除
get:function(){
console.log('get');
},
set:function(){
console.log('监听到数据发生了变化');
}
})
});
data.name //控制台会打印出 “get”
data.name = '上海' //控制台会打印出 "监听到数据发生了变化"
(2)Proxy(target, handler)(vue3使用)
参数target: 目标对象
参数handler: 对象处理器
相比Object.defineProperty(),速度更快,更重要的是,vue3因为它可以响应数组变化了
const data = {
bao: {
price: 5000,
name: "LV"
},
list: [1, 2]
};
const handler = {
get: function(target, key) {
if (target[key] > 5000) {
console.log("太贵了,不买了");
return 1;
} else {
console.log("可以接受,买买买");
return 3;
}
}
};
const handler2 = {
get: function(target, key) {
console.log("读到的下标", key);
return target[key];
}
};
// 读取对象
const p = new Proxy(data.bao, handler);
const value = p.price; // 此操作触发get,控制台打印"符合客户心理预期,买买买"
console.log(value); // 此时的 value 应是 3,因为get拦截了取值操作,并返回了新值
//读取数组
const p2 = new Proxy(data.list, handler2);
const value2 = p2[0] // 此操作触发get 控制台打印出:读到的下标, 0
console.log(value2) // 此时的 value2 应是 1