getCursorLeft(e) {
const event = e.changedTouches ? e.changedTouches[0] : e;
//这行代码首先检查e.changedTouches是否存在,这是为了区分触摸事件(如移动设备)与鼠标事件(如桌面设备)。如果e.changedTouches存在,表示当前是一个触摸事件,因此event被赋予e.changedTouches[0],即首个触摸点。若不存在,直接使用e作为事件对象。
const { left, width } = this.container.getBoundingClientRect();
//使用getBoundingClientRect方法获取当前组件容器的left位置和width宽度。left代表容器左边缘相对于视口的水平位置,width是容器的宽度。
const sliderButtonWidthPercent = (this.slider.getBoundingClientRect().width + 4) * 100 / width;
//获取滑块按钮的宽度,并且稍微增加了一个固定值4(可能是为了考虑边缘的留白、边框或其他视觉效果),然后计算它相对于容器宽度的百分比占比。
const min = sliderButtonWidthPercent / 2;
const max = 100 - min;
//基于滑块按钮的宽度比例,计算出滑块可以移动的最小和最大百分比值。这确保滑块不会超出容器的边界。
const delta = event.pageX - left;
let percent = (delta / width) * 100;
//这里,event.pageX获取光标相对于文档(页面)左侧的位置。从这个值中减去容器的left值,可以得到光标在容器内的相对位置delta。再将这个相对位置转换为容器宽度的百分比(percent)。
if (percent < min) percent = min;
if (percent > max) percent = max;
return percent
}
}
Monthly Archives: 5 月 2024
不同函数写法导致的this行为差异
在JavaScript中,函数内的this关键字的行为非常依赖于函数是如何被调用的。在user, user2, 和 user3的例子中,sayHi函数被定义了三种不同的方式,这导致this的行为也存在差异。
user对象
这里使用了一个名为sayHi的普通函数表达式:
let user = {
name: "Tom",
sayHi: function () {
console.log('Hi, I am ' + this.name);
}
}
在这个例子中,sayHi是一个普通的函数属性。当它作为方法(即通过对象引用调用时,例如user.sayHi())被调用时,this指的是调用它的对象,即user。
user2对象
这里使用了一个ES6中的方法简写:
let user2 = {
name: "Jack",
sayHi() {
console.log('Hi, I am ' + this.name);
}
}
user2例子中的sayHi定义和user的很相似,唯一的区别是它使用了ES6的简化语法来创建方法。这并不改变this的行为:作为user2对象的方法被调用时,this同样指向user2。
user3对象
这里使用了箭头函数:
let user3 = {
name: "Ben",
sayHi: () => {
console.log('Hi, I am ' + this.name);
}
}
user3.sayHi使用了一个箭头函数,箭头函数不绑定this,相反,它们捕获定义时上下文的this值。在全局代码中定义的箭头函数中的this指向全局对象,在浏览器中通常是window。因此,无论你何时何地调用user3.sayHi,它打印出来的this.name总会引用定义时捕获的this的name属性,而不是user3的name属性。如果user3.sayHi在全局范围内定义,如在浏览器中使用<script>
直接定义,则this.name很可能是undefined。
关于setTimeout
当你直接传递方法给setTimeout,如setTimeout(user.sayHi, 1000),你传递的是函数本身,而不是函数的调用。结果是,当setTimeout执行这个函数时,它没有了对象上下文,因此this不再引用原始的对象,除非像user和user2示例中一样使用.bind()方法明确地绑定this。
总结下三者间的区别:
user和user2展示了在对象内部定义方法的两种方式,都可以使用sayHi作为方法调用,其中this指向调用的对象。
user3的箭头函数导致this在定义时就固定下来,对箭头函数使用bind是无效的,因为箭头函数不绑定this。
使用.bind()可以修正setTimeout调用中this的指向,确保当方法在将来某个时间点执行时,this指向正确的对象。
call,apply和bind
call,apply和bind都是JavaScript中的函数方法,它们都可以改变函数调用时this的指向。不过它们之间各有其特定的使用场景和差异。
call: call()方法接受一个参数列表,这个方法无需改变函数的调用时机,可立即执行函数。常常用来借用别的对象的方法。
func.call(thisArg, arg1, arg2, ...)
apply: apply()方法接受一个包含多个参数的数组,与call()类似,apply()也会立即执行函数。当你不知道要传递给函数多少个参数,或者参数是作为一个数组存在时,apply()非常有用。
func.apply(thisArg, [argsArray])
bind: bind()方法返回一个新的函数,使得这个新函数将this设置为bind()的第一个参数。bind()不会立即执行函数,而是返回一个绑定了正确this值的新函数,这点是bind()和call()、apply()的主要区别。bind()非常适合用在事件监听上。
newFunc = func.bind(thisArg[, arg1[, arg2[, ...]]])
总结一下:
call和apply都是为了解决不同对象之间方法的借用,包括他们的参数列表形式不同之处。
bind是将函数体内this对象绑定为指定对象,返回一个新函数,供日后调用。
apply和call都会立即执行函数,而bind不会。
案例:
// // 数组连接(concatenate): 使用apply将一个数组添加到另一个数组的末尾。
let array1 = [1, 2, 3]
let array2 = [4, 5, 6]
Array.prototype.push.apply(array1, array2)
console.log(array1) // 输出: [1, 2, 3, 4, 5, 6]
// // 这个例子中,apply方法改变了push方法的上下文并执行它,新的上下文是array1,参数是array2。
// // // 找到数组中的最大值: 使用apply或者call找到数组中的最大值
let array = [1, 2, 3, 4, 5]
console.log(Math.max.apply(null, array)); //5
// // 在这个例子中,apply方法更改了Math.max的上下文并执行它,通过传递数组作为参数。同样,call也可帮助我们达到同样的效果:
console.log(Math.max.call(null, ...array)); //5
// // 这里...array是ES6的语法特性,它将数组转为参数序列。
// 函数柯里化: bind经常用于函数柯里化。它允许你部分地应用函数,将其余的参数留到函数被实际调用时指定。
function add(x) {
return function (y) {
return x + y
}
}
let add5 = add(5).bind(this);
console.log(add5(3));
// 在这个例子中,bind方法创建了一个新的add5函数,它是add(5)的一个固定版本,现在它总是将5作为第一个参数加到其余的参数上。
bing 配合setTimeOut
// 配合 setTimeout()
// 在默认情况下,在 setTimeout() 内部,this 关键字将被设置为 globalThis,在浏览器中它是 window 对象。当处理需要将 this 引用类实例的类方法时,你可以显式地将 this 绑定到回调函数,以便保持实例的引用。
class LateBloomer {
constructor() {
this.petalCount = Math.floor(Math.random() * 12) + 1;
}
bloom() {
// 延迟一秒后宣布开花
// setTimeout(this.declare.bind(this), 1000);
//你还可以使用箭头函数来实现此目的。
setTimeout(() => this.declare(), 1000)
}
declare() {
console.log(`l am a beautiful flower with ${this.petalCount} petals!`);
}
}
const flower = new LateBloomer();
flower.bloom();
偏函数
function list(...args) {
return args;
}
function addArguments(arg1, arg2) {
return arg1 + arg2;
}
console.log(list(1, 2, 3)); // [1, 2, 3]
console.log(addArguments(1, 2)); // 3
// 创建一个带有预设前导参数的函数
const leadingThirtySevenList = list.bind(null, 37);
// 创建一个带有预设第一个参数的函数。
const addThirtySeven = addArguments.bind(null, 37);
console.log(leadingThirtySevenList()); // [37]
console.log(leadingThirtySevenList(1, 2, 3)); // [37, 1, 2, 3]
console.log(addThirtySeven(5)); // 42
console.log(addThirtySeven(5, 10)); // 42
//(最后一个参数 10 被忽略)
为何(最后一个参数 10 被忽略)
当你使用 .bind() 方法绑定一些参数到一个函数时,你实际上是在创建一个新的函数,它具有预设的初始参数。这些预设的参数在绑定时已经确定,它们将在新函数被调用时作为第一批参数传入,后续调用时传入的参数将按顺序排在这些预设参数之后。
在 addThirtySeven 函数的示例中,.bind(null, 37) 创建了一个新函数,其中第一个参数(arg1)被预设为 37。当 addThirtySeven(5) 被调用时,数字 5 被作为第二个参数(arg2),所以函数 addArguments 接收的参数为 37(arg1)和 5(arg2),结果是这两个数相加,即 42。
继续这个例子,当你调用 addThirtySeven(5, 10) 时,尽管你传入了两个参数(5 和 10),由于 addArguments 函数只期望和处理两个参数(arg1和arg2),在绑定时已经预设了第一个参数(arg1)为 37,所以传入的第一个参数(5)实际上会被作为第二个参数(arg2)。第二个参数(10)由于在函数 addArguments 中没有对应的第三个参数接收它,所以会被函数忽略,因此没有起作用。
总结来说,addThirtySeven 函数不会处理超过两个参数的情况,因为它是基于 addArguments 函数的,而 addArguments 只处理两个参数。绑定操作将预设的参数固定在参数列表的前面,超出原函数参数数量的额外参数会被忽略。
创建绑定函数
/* bind() 最简单的用法是创建一个函数,无论如何调用,它都会使用特定的 this 值进行调用。
JavaScript 新手经常犯的一个错误是将一个方法从对象中提取出来,然后再调用,并期望方法中的 this 是原来的对象(比如在回调中传入这个方法)。
然而,如果不做特殊处理的话,通常会丢失原始对象。使用这个函数加上原始的对象来创建一个绑定函数,巧妙地解决了这个问题:*/
// 顶级的“this”绑定到“globalThis”。
this.x = 9;
const module = {
x: 81,
getX() {
return this.x;
},
};
// 'getX'的‘this’参数绑定到"module"
console.log(module.getX()); //81
const retrieveX = module.getX;
// “retrieveX”的“this”参数在非严格模式下绑定到“globalThis”
console.log(retrieveX());//9
// 创建一个新函数“boundGetX”,并将“this”参数绑定到“module”。
const boundGetX = retrieveX.bind(module);
console.log(boundGetX());//81
剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function sum(...theArgs) {
let total = 0;
for (const arg of theArgs) {
total += arg;
}
return total;
}
console.log(sum(1, 2, 3));
// Expected output: 6
console.log(sum(1, 2, 3, 4));
// Expected output: 10
改进版本的isMobile函数
function isMobile() {
// 检查屏幕宽度,假定平板或更大尺寸的设备宽度大于 756px
if (window.innerWidth > 756) {
return false;
}
// 用户代理关键字列表,可以根据需要增减
var mobileKeywords = ["Android", "webOS", "iPhone", "iPad", "iPod", "BlackBerry", "IEMobile", "Opera Mini"];
// 检查用户代理字符串是否包含上述任一关键字
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
for(var i = 0; i < mobileKeywords.length; i++) {
if (userAgent.indexOf(mobileKeywords[i]) > -1) {
return true;
}
}
// 特别针对Windows平板的检测,因为它们可能在用户代理中不包含上述关键字
if (/windows phone/i.test(userAgent)) {
return true;
}
// 最后,额外考虑小尺寸的设备可能是手机
return window.innerWidth <= 756;
}
这个版本的isMobile函数先通过屏幕宽度简单排除大部分非移动设备,然后使用用户代理字符串中特定的关键字来进一步确认。这样,即使是小屏幕的平板或对折设备,在用户代理中没有出现对应的移动设备关键字的情况下,也不会被误判为手机。
由于设备和浏览器环境的不断变化,这种方法仍然不是完全准确的,但它比仅仅基于屏幕尺寸的判断更加灵活和准确些。在实际应用中,可能还需要根据具体情况调整关键字列表或判断逻辑。