你不知道的JavaScript--Item21 漂移的this

本文详细解析了JavaScript中this关键字的不同应用场景及指向,包括普通函数、闭包、new关键字创建的对象、call与apply方法、eval函数、DOM事件等场景。

而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑。本文仅就这一问题展开讨论,阅罢本文,读者若能正确回答 JavaScript 中的 What ’s this 问题,作为作者,我就会觉得花费这么多功夫,撰写这样一篇文章是值得的。

我们要记住一句话:this永远指向函数运行时所在的对象!而不是函数被创建时所在的对象。也即:谁调用,指向谁。切记…

本文将分三种情况来分析this对象到底身处何方。

1、普通函数中的this

无论this身处何处,第一要务就是要找到函数运行时的位置。


 var name="全局";
 function getName(){
     var name="局部";
     return this.name;
 };
 alert(getName());

当this出现在全局环境的函数getName中时,此时函数getName运行时的位置在

alert(getName());

显然,函数getName所在的对象是全局对象,即window,因此this的安身之处定然在window。此时的this指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,当this不是出现在全局环境的函数中,而是出现在局部环境的函数中时,又会身陷何方呢?

 var name="全局";
 var xpg={
     name:"局部",
     getName:function(){
         return this.name;
     }
 };
 alert(xpg.getName());

其中this身处的函数getName不是在全局环境中,而是处在xpg环境中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

alert(xpg.getName());

显然,函数getName所在的对象是xpg,因此this的安身之处定然在xpg,即指向xpg对象,则getName返回的this.name其实是xpg.name,因此alert出来的是“局部”!

举个例子巩固一下:

var someone = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var other = {
    name: "Tom",
    showName: someone.showName
}

other.showName();  //Tom

this关键字虽然是在someone.showName中声明的,但运行的时候是other.showName,所以this指向other.showName函数的当前对象,即other,故最后alert出来的是other.name。

2、闭包中的this

闭包也是个不安分子,本文暂且不对其过于赘述,简而言之:所谓闭包就是在一个函数内部创建另一个函数,且内部函数访问了外部的变量。
浪子this与痞子闭包混在一起,可见将永无宁日啊!

  var name="全局";
  var xpg={
      name:"局部",
      getName:function(){
          return function(){
              return this.name;
          };
      }
  };
 alert(xpg.getName()());

此时的this明显身处困境,竟然处在getName函数中的匿名函数里面,而该匿名函数又调用了变量name,因此构成了闭包,即this身处闭包中。
无论this身处何处,一定要找到函数运行时的位置。此时不能根据函数getName运行时的位置来判断,而是根据匿名函数的运行时位置来判断。

function (){
    return this.name;
};

显然,匿名函数所在的对象是window,因此this的安身之处定然在window,则匿名函数返回的this.name其实是window.name,因此alert出来的就是“全局”!

那么,如何在闭包中使得this身处在xpg中呢?—缓存this

  var name="全局";
  var xpg={
      name:"局部",
      getName:function(){
          var that=this;
          return function(){
              return that.name;
          };
      }
 };
 alert(xpg.getName()());

在getName函数中定义that=this,此时getName函数运行时位置在

alert(xpg.getName());

则this指向xpg对象,因此that也指向xpg对象。在闭包的匿名函数中返回that.name,则此时返回的that.name其实是xpg.name,因此就可以alert出来 “局部”!

3、new关键字创建新对象

new关键字后的构造函数中的this指向用该构造函数构造出来的新对象:

function Person(__name){
    this.name = __name;        //这个this指向用该构造函数构造的新对象,这个例子是Bob对象
}
Person.prototype.show = function(){
    alert(this.name);   //this 指向Person,this.name = Person.name;
}

var Bob = new Person("Bob");
Bob.show();        //Bob

4、call与apply中的this

在JavaScript中能管的住this的估计也就非call与apply莫属了。
call与apply就像this的父母一般,让this住哪它就得住哪,不得不听话!当无参数时,当前对象为window

var name="全局";
var xpg={
    name:"局部"
};
function getName(){
    alert(this.name);
}
getName(xpg);
getName.call(xpg);
getName.call();

其中this身处函数getName中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

getName(xpg);

显然,函数getName所在的对象是window,因此this的安身之处定然在window,即指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,该call与apply登场了,因为this必须听他们的指挥!

getName.call(xpg);

其中,call指定this的安身之处就是在xpg对象,因为this被迫只能在xpg那安家,则此时this指向xpg对象, this.name其实是xpg.name,因此alert出来的是“局部”!

5、eval中的this

对于eval函数,其执行时候似乎没有指定当前对象,但实际上其this并非指向window,因为该函数执行时的作用域是当前作用域,即等同于在该行将里面的代码填进去。下面的例子说明了这个问题:

var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        eval("alert(this.name)");
    }
};

Bob.showName();    //Bob

6、没有明确的当前对象时的this

当没有明确的执行时的当前对象时,this指向全局对象window。
例如对于全局变量引用的函数上我们有:

var name = "Tom";

var Bob = {
    name: "Bob",
    show: function(){
        alert(this.name);
    }
}

var show = Bob.show;
show();  //Tom

你可能也能理解成show是window对象下的方法,所以执行时的当前对象时window。但局部变量引用的函数上,却无法这么解释:

var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var Tom = {
    name: "Tom",
    showName: function(){
        var fun = Bob.showName;
        fun();
    }
};

Tom.showName();  //window

在浏览器中setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条我们可以看成是上一条的一个特殊情况。

var name = "Bob";  
var nameObj ={  
      name : "Tom",  
      showName : function(){  
          alert(this.name);  
      },  
      waitShowName : function(){  
          setTimeout(this.showName, 1000);  
      }  
 };  

 nameObj.waitShowName();

所以在运行this.showName的时候,this指向了window,所以最后显示了window.name。

7、dom事件中的this

(1)你可以直接在dom元素中使用

<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />

分析:对于dom元素的一个onclick(或其他如onblur等)属性,它为所属的html元素所拥有,直接在它触发的函数里写this,this应该指向该html元素。

(2)给dom元素注册js函数
a、不正确的方式

<script type="text/javascript">
  function thisTest(){
  alert(this.value); // 弹出undefined, this在这里指向??
}
</script>

<input id="btnTest" type="button" value="提交" onclick="thisTest()" />

分析:onclick事件直接调用thisTest函数,程序就会弹出undefined。因为thisTest函数是在window对象中定义的,
所以thisTest的拥有者(作用域)是window,thisTest的this也是window。而window是没有value属性的,所以就报错了。
b、正确的方式

<input id="btnTest" type="button" value="提交" />

<script type="text/javascript">
  function thisTest(){
  alert(this.value); 
}
document.getElementById("btnTest").onclick=thisTest; //给button的onclick事件注册一个函数
</script>

分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById(“btnTest”).οnclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:

document.getElementById("domID").onclick=thisTest; //给button的onclick事件注册一个函数。

因为多个不同的HTML元素虽然创建了不同的函数副本,但每个副本的拥有者都是相对应的HTML元素,各自的this也都指向它们的拥有者,不会造成混乱。
为了验证上述说法,我们改进一下代码,让button直接弹出它们对应的触发函数:

<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" />
<input id="btnTest2" type="button" value="提交2" />

<script type="text/javascript">
function thisTest(){
this.value="提交中";
}
var btn=document.getElementById("btnTest1");
alert(btn.onclick); //第一个按钮函数

var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二个按钮函数
</script>

其弹出的结果是:

//第一个按钮
function onclick(){
  thisTest()
}

//第二个按钮
function thisTest(){
  this.value="提交中";
}

从上面的结果你一定理解的更透彻了。
By the way,每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。所以我们通常都这么写:

<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" />
<input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" />
<input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" />
<input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" />

<script type="text/javascript">
  function thisTest(obj){
  alert(obj.value); 
}
</script>

这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:

//js事件 添加 EventUtil.addEvent(dom元素,事件名称,事件触发的函数名) 移除EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)
var EventUtil = new eventManager();

//js事件通用管理器 dom元素 添加或者移除事件
function eventManager() {
    //添加事件
    //oDomElement:dom元素,如按钮,文本,document等; ****** oEventType:事件名称(如:click,如果是ie浏览器,自动将click转换为onclick);****** oFunc:事件触发的函数名
    this.addEvent = function(oDomElement, oEventType, oFunc) {
        //ie
        if (oDomElement.attachEvent) {
            oDomElement.attachEvent("on" + oEventType, oFunc);
        }
        //ff,opera,safari等
        else if (oDomElement.addEventListener) {
            oDomElement.addEventListener(oEventType, oFunc, false);
        }
        //其他
        else {
            oDomElement["on" + oEventType] = oFunc;
        }
    }

    this.removeEvent = function(oDomElement, oEventType, oFunc) {
        //ie
        if (oDomElement.detachEvent) {
            oDomElement.detachEvent("on" + oEventType, oFunc);
        }
        //ff,opera,safari等
        else if (oDomElement.removeEventListener) {
            oDomElement.removeEventListener(oEventType, oFunc, false);
        }
        //其他
        else {
            oDomElement["on" + oEventType] = null;
        }
    }
}

正像注释写的那样,要注册dom元素事件,用EventUtil.addEvent(dom元素,事件名称,事件触发的函数名)即可, 移除时可以这样写:EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)。这是题外话,不说了。


系列文章导航:

1、你不知道的JavaScript–Item1 严格模式

2、你不知道的JavaScript–Item2 浮点数精度

3、你不知道的JavaScript–Item3 隐式强制转换

4、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)

5、你不知道的JavaScript–Item5 全局变量

6、你不知道的JavaScript–Item6 var预解析与函数声明提升(hoist )

7、你不知道的JavaScript–Item7 函数和(命名)函数表达式

8、你不知道的JavaScript–Item8 函数,方法,构造函数调用

9、你不知道的JavaScript–Item9 call(),apply(),bind()与回调

10、你不知道的JavaScript–Item10 闭包(closure)

11、你不知道的JavaScript–Item11 arguments对象

12、你不知道的JavaScript–Item12 undefined 与 null

13、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

14、你不知道的JavaScript–Item14 使用prototype的几点注意事项

15、你不知道的JavaScript–Item15 prototype原型和原型链详解

16、你不知道的JavaScript–Item16 for 循环和for…in 循环的那点事儿

17、你不知道的JavaScript–Item17 循环与prototype最后的几点小tips

18、你不知道的JavaScript–Item18 JScript的Bug与内存管理

19、你不知道的JavaScript–Item19 执行上下文(execution context)

20、你不知道的JavaScript–Item20 作用域与作用域链(scope chain)

21、你不知道的JavaScript–Item21 漂移的this


持续更新中……………….

// 核心类型定义 export type FormattedLocation = { lat: number; lng: number; spd: number; acc: number; alt: number; }; export type LocationSuccessResult = { latitude: number; longitude: number; speed: number; accuracy: number; altitude: number; verticalAccuracy: number; horizontalAccuracy: number; }; export type UniErrorData = { errMsg?: string; }; export interface LocationError { errMsg: string; } // 全局状态变量 let isLocating = false; let locationTimer: number | null = null; let currentPosition: LocationSuccessResult | null = null; let locationHistory: FormattedLocation[] = []; const RETRY_DELAY = 5000; const LOCATE_INTERVAL = 3000; type LocationCallback = (position: LocationSuccessResult) => void; let successCallback: LocationCallback | null = null; /** * 开始持续定位 */ export function startContinuousLocation(callback: LocationCallback): void { if (isLocating) { console.log('定位已在进行中,无需重复启动'); return; } successCallback = callback; isLocating = true; console.log('开始持续定位...'); getLocation(); } /** * 停止持续定位 */ export function stopContinuousLocation(): void { const timer = locationTimer; if (timer !== null) { clearTimeout(timer); } isLocating = false; successCallback = null; locationTimer = null; console.log('已停止持续定位'); } /** * 获取当前定位信息 */ export function getCurrentPosition(): LocationSuccessResult | null { return currentPosition; } /** * 获取累计轨迹数组 */ // 添加深拷贝函数 function deepCopyLocation(loc: FormattedLocation): FormattedLocation { return { lat: loc.lat, lng: loc.lng, spd: loc.spd, acc: loc.acc, alt: loc.alt }; } // 修改 getLocationHistory 函数 export function getLocationHistory(): FormattedLocation[] { const result: FormattedLocation[] = []; for (let i = 0; i < locationHistory.length; i++) { result.push(deepCopyLocation(locationHistory[i])); } return result; } /** * 清空轨迹历史 */ export function clearLocationHistory(): void { locationHistory = []; console.log('轨迹历史已清空'); } /** * 安全获取数值函数 - 修复类型转换问题 */ function getSafeNumber(value: any, defaultValue: number = 0): number { if (value == null) return defaultValue; // 使用更安全的类型转换方式 let numValue: number; if (typeof value === 'number') { numValue = value; } else if (typeof value === 'string') { numValue = parseFloat(value); } else { return defaultValue; } if (isNaN(numValue)) { return defaultValue; } return numValue; } /** * 包装setTimeout调用 */ function scheduleNextLocation(): void { const timer = locationTimer; if (timer !== null) { clearTimeout(timer); } locationTimer = setTimeout(() => { getLocation(); }, LOCATE_INTERVAL); } /** * 核心定位函数(已修复类型转换问题) */ function getLocation(): void { if (!isLocating) return; uni.getLocation({ type: 'wgs84', geocode: false, success: (res) => { // ... (success 回调逻辑保持变) const latitude = getSafeNumber(res.latitude); const longitude = getSafeNumber(res.longitude); const speed = getSafeNumber(res.speed); const accuracy = getSafeNumber(res.accuracy); const altitude = getSafeNumber(res.altitude); const verticalAccuracy = getSafeNumber(res.verticalAccuracy); const horizontalAccuracy = getSafeNumber(res.horizontalAccuracy); const locationRes: LocationSuccessResult = { latitude: latitude, longitude: longitude, speed: speed, accuracy: accuracy, altitude: altitude, verticalAccuracy: verticalAccuracy, horizontalAccuracy: horizontalAccuracy }; currentPosition = locationRes; const formattedLoc: FormattedLocation = { lat: locationRes.latitude, lng: locationRes.longitude, spd: locationRes.speed, acc: locationRes.accuracy, alt: locationRes.altitude }; locationHistory.push(formattedLoc); console.log(`定位成功:lat=${formattedLoc.lat.toFixed(6)}, lng=${formattedLoc.lng.toFixed(6)}, 精度=${formattedLoc.acc}m`); const callback = successCallback; if (callback !== null) { callback(locationRes); } scheduleNextLocation(); }, // --- 修改点 2:使用 API 提供的 IGetLocationFail 类型 --- fail: (err: IGetLocationFail) => { // 修改这里 console.error('定位失败:', err); // IGetLocationFail 类型已经明确包含了 errMsg 属性 const errMsg = err.errMsg ?? '未知错误'; console.error('定位失败:', errMsg); if (errMsg.includes('auth deny')) { uni.showToast({ title: '请授予后台定位权限以继续巡查', icon: 'none', duration: 3000 }); stopContinuousLocation(); } else if (errMsg.includes('timeout')) { uni.showToast({ title: '定位超时,5秒后重试', icon: 'none', duration: 2000 }); } else { uni.showToast({ title: `定位失败:${errMsg}`, icon: 'none', duration: 3000 }); } scheduleNextLocation(); } }); } <template> <view class="patrol-container"> <view class="status"> <text>{{ isPatrolling ? '巡查中' : '已停止' }}</text> <text class="point-count">已记录:{{ trackPoints.length }} 个点位</text> </view> <view class="location-info"> <text>当前位置:</text> <text>{{ currentLat.toFixed(6) }}, {{ currentLng.toFixed(6) }}</text> </view> <!-- scroll-view 显示轨迹点位,高度 300rpx --> <view class="track-scroll-container"> <text class="track-title">轨迹点位记录:</text> <scroll-view class="track-scroll" scroll-y="true" :style="{ height: '300rpx' }"> <view class="track-list"> <!-- 无点位时显示提示 --> <view class="track-empty" v-if="trackPoints.length === 0"> 暂无轨迹点位,请开始巡查 </view> <!-- 遍历显示所有点位(无时间戳) --> <view class="track-item" v-for="(item, index) in trackPoints" :key="index"> <view class="track-item-index">{{ index + 1 }}</view> <view class="track-item-content"> <view class="track-item-row"> <text class="label">经纬度:</text> <text class="value">{{ item.lat.toFixed(6) }}, {{ item.lng.toFixed(6) }}</text> </view> <view class="track-item-row"> <text class="label">速度/精度:</text> <text class="value">{{ item.spd.toFixed(2) }}m/s / {{ item.acc.toFixed(1) }}m</text> </view> <view class="track-item-row"> <text class="label">高度:</text> <text class="value">{{ item.alt.toFixed(2) }}m</text> </view> </view> </view> </view> </scroll-view> </view> <button @click="startPatrol" :disabled="isPatrolling" class="start-btn"> 开始巡查 </button> <button @click="stopPatrol" :disabled="!isPatrolling" class="stop-btn"> 结束巡查 </button> <button @click="clearHistory" class="clear-btn"> 清空轨迹 </button> </view> </template> <script lang="uts"> import { startContinuousLocation, stopContinuousLocation, getLocationHistory, clearLocationHistory, type FormattedLocation, type LocationSuccessResult } from '@/utils/location'; export default { data() { return { isPatrolling: false, trackPoints: [] as FormattedLocation[], // 恢复为普通数组类型,无需 UTSArray currentLat: 0, currentLng: 0 }; }, methods: { startPatrol() { clearLocationHistory(); this.trackPoints = []; this.currentLat = 0; this.currentLng = 0; this.isPatrolling = true; startContinuousLocation((res: LocationSuccessResult) => { this.currentLat = res.latitude; this.currentLng = res.longitude; // 关键修改:直接赋值,无需 Array.from 或 UTSArray 转换 this.trackPoints = getLocationHistory(); }); }, stopPatrol() { stopContinuousLocation(); this.isPatrolling = false; // 直接获取,无需类型转换 const fullTrack = getLocationHistory(); console.log('巡查结束,轨迹点:', fullTrack); }, clearHistory() { clearLocationHistory(); this.trackPoints = []; this.currentLat = 0; this.currentLng = 0; uni.showToast({ title: '轨迹已清空', icon: 'success', duration: 1500 }); } } }; </script> <style scoped> /* 样式保持变,无需修改 */ .patrol-container { padding: 20rpx; box-sizing: border-box; } .status { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; color: #1989fa; display: flex; align-items: center; } .point-count { font-size: 24rpx; color: #666; margin-left: 20rpx; font-weight: normal; } .location-info { font-size: 28rpx; margin-bottom: 20rpx; color: #333; line-height: 40rpx; } /* 轨迹滚动容器样式 */ .track-scroll-container { margin: 20rpx 0; } .track-title { font-size: 26rpx; color: #333; margin-bottom: 10rpx; display: block; font-weight: 400; } .track-scroll { border: 1px solid #e5e5e5; border-radius: 12rpx; background-color: #fafafa; overflow: hidden; } .track-list { padding: 10rpx; } /* 无点位提示 */ .track-empty { font-size: 24rpx; color: #999; text-align: center; padding: 80rpx 0; } /* 点位项样式 */ .track-item { display: flex; padding: 12rpx 0; border-bottom: 1px solid #eee; } .track-item:last-child { border-bottom: none; } .track-item-index { width: 40rpx; height: 40rpx; background-color: #1989fa; color: white; border-radius: 50%; font-size: 22rpx; display: flex; align-items: center; justify-content: center; margin-right: 15rpx; flex-shrink: 0; } .track-item-content { flex: 1; } .track-item-row { font-size: 22rpx; color: #333; margin-bottom: 6rpx; display: flex; } .track-item-row:last-child { margin-bottom: 0; } .label { color: #666; width: 120rpx; flex-shrink: 0; } .value { flex: 1; word-break: break-all; } /* 按钮样式 */ button { margin-bottom: 20rpx; height: 80rpx; font-size: 28rpx; border-radius: 16rpx; } .start-btn { background-color: #1989fa; color: white; } .stop-btn { background-color: #ff4d4f; color: white; } .clear-btn { background-color: #f5f5f5; color: #333; border: 1px solid #eee; } </style>上方代码startContinuousLocation手机黑屏后调用,打开页面又报错定位失败: ‍[⁠uts.sdk.modules.DCloudUniLocation.GetLocationFailImpl⁠]‍ {cause: null, data: null, errCode: 1505600, errMsg: "location fail: timeout", errSubject: "uni-location", ⁠...⁠} at utils/location.uts:189 14:02:29.777 定位失败: location fail: timeou,过一会重新调用,uniapp X开发引擎,uts语言
11-25
function autoUnitPriceFormat(...items) { items.forEach(item => { const inputId = item[0]; const maxLength = item[1]; // 最大长度(包含小数点) const inputElement = document.getElementById(inputId); if (!inputElement) return; // 用于保存输入前的原始值(带千分位) let originalRawValue = ''; // 焦点进入时:保存原始值并移除千位符 inputElement.addEventListener('focus', function() { originalRawValue = this.value.replace(/,/g, ''); this.value = originalRawValue; }); // 输入时:限制格式和长度 inputElement.addEventListener('input', function() { let value = this.value.replace(/,/g, ''); // 限制只能输入数字和小数点 value = value.replace(/[^\d.]/g, ''); // 处理多个小数点的情况 const decimalCount = value.split('.').length - 1; if (decimalCount > 1) { const parts = value.split('.'); value = parts[0] + '.' + parts.slice(1).join(''); } // 限制小数点后最多两位 if (value.includes('.')) { const [integer, decimal] = value.split('.'); value = integer + '.' + decimal.substring(0, 2); } // 如果长度已满15位,允许再输入 if (value.length > maxLength) { // 恢复为输入前的原始值 this.value = originalRawValue || '0.00'; return; } // 更新当前值 originalRawValue = value; this.value = value; }); // 焦点离开时:格式化千位符并更新原始值 inputElement.addEventListener('blur', function() { let value = this.value.replace(/,/g, ''); if (value === '' || value === '.') { this.value = '0.00'; originalRawValue = '0.00'; return; } // 处理以小数点结尾的情况 if (value.endsWith('.')) { value = value.slice(0, -1); } // 转换为数字 let numberValue = parseFloat(value); if (isNaN(numberValue)) { this.value = '0.00'; originalRawValue = '0.00'; return; } // 处理最大值 const maxValue = 999999999999.99; if (numberValue > maxValue) { numberValue = maxValue; } // 格式化为千位符并固定两位小数 this.value = numberValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); // 更新原始值为格式化后的值(用于下次输入) originalRawValue = this.value.replace(/,/g, ''); }); }); } 位置计算可能有误,满位后,每次输入数字,虽然文本框内的数值没有变化,但是光标都会向右侧串一位,请确认代码
最新发布
12-04
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值