检测变量何时在javascript中发生更改 [英] Detecting when a variable changes in javascript

查看:36
本文介绍了检测变量何时在javascript中发生更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个chrome应用程序,我想在分辨率更改时正确调整大小(尺寸与屏幕宽度成比例).我编写了一个函数,可以用正确的尺寸重绘应用程序,现在我只想在需要时执行它.

分辨率的改变会导致screen.width发生变化,但是(由于它们与不同的事物有关,所以可能就不足为奇了)调整大小"事件并未触发,据我所知,没有触发任何事件.

我了解代理对象( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy ),所以我写了一些代码来检测何时设置了变量并执行了我的回调,这似乎可行,但是在分辨率更改的情况下不会.

所以我在线搜索并尝试了 https://gist.github.com/eligrey/384583 (本质上,这是在类似主题的几个stackoverflow问题中提供的答案,实际上是我最初生成的内容,尽管缺少新的Proxy对象提供的抽象概念.)

这似乎也可行(从某种意义上说,如果我说完成screen.watch("width",()=> console.log("hello"))之后手动设置screen.width);然后再进行屏幕显示.width = 100;我的回调已执行).但不是在分辨率更改的情况下(实际上,也许最重要的是,分配此监视程序似乎阻止了screen.width的分配).

我有三个问题

1)当我分配使事情混乱的观察者/代理时,会发生什么情况.

2)我如何才能了解浏览器在这一详细级别上的功能(是发送触发来更改screen.width的触发器?我猜是操作系统,这是什么样子的?)

3)是否有更好的方法来实现我最初的目标(调整chrome应用程序的大小).

大多数情况下,我对问题1)感兴趣,并且不再关心3).

要复制我的问题,

编辑-正如Bertrand回答后所揭示的,resize事件实际上可能会在分辨率更改时触发,但这似乎是对分辨率更改的响应,如果窗口很小,则迫使窗口的边界变小.screen.width可以更改,而无需触发调整大小事件.

当我分配一个混乱的观察者/代理时发生了什么.

问题在于, width 属性实际上是 Screen.prototype 上的 getter .它不是 window.screen :

上的普通值.

  console.log(window.screen.width);console.log(window.screen.hasOwnProperty('width'));//该属性改为存在于原型上,并且是一个吸气剂:const描述符= Object.getOwnPropertyDescriptor(Screen.prototype,'width');console.log(descriptor);  

如果 width 属性是 ordinary 属性,该属性仅由一个普通值组成,该值是浏览器通过 eval ing

  window.screen.width = 1500 

然后可以使用代理或 Object.watch polyfill,因为您可以拦截对 .width 属性的分配.这就是为什么在将自己的设置器分配给 window.screen.width :

之后看到的原因

执行 screen.width = 100; 并观察到已记录了问候并且screen.width设置为100

它向您显示 hello -您正在调用先前分配给 window screen 属性的设置器.相反,由于本机内置属性不是浏览器分配给它的普通值,因此当屏幕变化时,您的 hello 不会被记录.这种情况是 bit ,类似于本示例代码片段中的操作:

  const obj =(()=> {让privateVal ='propOriginal';//privateVal在500毫秒后更改:setTimeout(()=> {console.log('将privateVal更改为propChanged');privateVal ='propChanged';},500);//返回一个具有getter的对象,该对象将返回privateProp:返回 {获取privateProp(){返回privateVal;}};})();//在这一点上,如果只有一个对obj的引用,//没有真正的方法来监视对privateVal的更改:console.log(obj.privateProp);//将自定义设置器分配给obj.privateProp//仅会导致观察对obj.privateProp的尝试分配//,但将无法观察到对privateVal的更改:Object.defineProperty(obj,'privateProp',{set(newVal){console.log('已观察到的更改:新值为'+ newVal);}});setTimeout(()=> {obj.privateProp = 1000;},2500);  

如您所见,添加到外部作用域中的 obj 的setter函数无法捕获对 privateVal 的更改.这与 window.screen 并不是完全一样,但是类似.您无权访问底层代码结构,该底层代码结构会导致通过调用内置getter返回的值发生更改.


屏幕上的内置getter函数由本机代码组成-这意味着它不是普通的Javascript函数.由本机代码组成的功能始终是浏览器提供的功能(或运行代码的任何环境);您自己编写的任何Javascript函数通常都无法模仿它们.例如, only window.history 对象可以执行历史记录操作,例如 history.back();如果 window.history 被覆盖,并且您没有保存的引用或任何其他 history 对象,则无法编写自己的函数来执行 history.back(),因为 .back()会调用特权本机代码,该代码需要在可见Javascript和浏览器引擎之间建立接口.

类似地,内置的 window.screen 吸气剂在被调用时会返回纯Javascript无法直接观察的值.如果不进行轮询,监视更改的唯一其他方法是监听 resize 事件,如其他答案所述.(此 resize 事件类似于 window.screen 返回的基础值,由浏览器内部部件管理,否则无法观察到.)

如果未响应分辨率的更改而触发 resize 事件-例如,如果浏览器窗口足够小,则较小的分辨率不需要进行更改-分辨率更改不会导致触发任何事件,这意味着,除了轮询之外,没有其他方法可以监听它.(您可以自己尝试,它看起来与其他任何产品都不一样.分辨率更改触发事件)

一个人可能会编写(或调整)一个浏览器引擎,该监听器会侦听来自操作系统的分辨率变化并在发现时调度Javascript事件,但是这样做需要的知识远远超出Javascript-随时浏览 Chromium的源代码复杂,可能对没有使用过的语言的人来说是不透明的.

I have a chrome app that I want to correctly resize (dimensions proportional to the screen width) on resolution change. I have written a function that redraws the app with the correct dimensions, now I want to execute it only when it needs to.

A resolution change causes screen.width to change, but (probably unsurprisingly since they relate to different things) the "resize" event is not fired, and as far as I can tell no event is fired.

I know about the Proxy object (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) so I wrote some code which detects when a variable is set and executes my callback, this seemed to work but not in the instance of resolution change.

So I searched online and tried this https://gist.github.com/eligrey/384583 (which is essentially the answer provided in several stackoverflow questions on a similar topic, and indeed what I initially produced, albeit lacking the abstraction that the newish Proxy object offers).

This also seems to work (in the sense if I say manually set screen.width after having done screen.watch("width", () => console.log("hello")); and then do screen.width = 100; my callback is executed). But not in the instance of resolution change (in fact, perhaps most importantly, assigning this watcher seems to prevent screen.width getting assigned).

I have three questions

1) What is going on when I assign a watcher/proxy that's messing things up.

2) How could I find out what the browser is doing at this level of detail (what sends the trigger to change screen.width? I guess it is the OS, what does this look like)

3) Is there a better way to achieve what I was initially going for (the chrome app resizing).

Mostly I am interested in question 1) and don't care much about 3) any more.

To replicate my issue,

  • open a new tab in firefox or chrome
  • go to the developer console.
  • check screen.width, change resolution, observe that screen.width changes
  • Copy and paste the code from https://gist.github.com/eligrey/384583
  • Do screen.watch("width", (id, oldval, newval) => {console.log("hello"); return newval;});
  • Do screen.width = 100; and observe that hello is logged and screen.width is set to 100
  • Change resolution, observe that screen.width is not set.

Edit - As revealed after Bertrand's answer, the resize event may actually fire on resolution change, but this seems to be as a response to the resolution change forcing the boundary of the the window to get smaller, if the window is small then screen.width can change without firing a resize event.

解决方案

What is going on when I assign a watcher/proxy that's messing things up.

The issue is that the width property is actually a getter, on Screen.prototype. It's not an ordinary value on window.screen:

console.log(window.screen.width);
console.log(window.screen.hasOwnProperty('width'));

// The property exists on the prototype instead, and is a getter:
const descriptor = Object.getOwnPropertyDescriptor(Screen.prototype, 'width');
console.log(descriptor);

If the width property were an ordinary property, composed of just a plain value, which was set by the browser via evaling

window.screen.width = 1500

then a proxy or the Object.watch polyfill would work, because you would be able to intercept the assignment to the .width property. This is why you see that, after assigning your own setter to window.screen.width:

Do screen.width = 100; and observe that hello is logged and screen.width is set to 100

It shows you hello - you're invoking the setter that you previously assigned to the screen property of window. In contrast, because the native built-in property is not a plain value which gets assigned to by the browser, your hello does not get logged when the screen changes. The situation is a bit similar to what's going on in this example snippet:

const obj = (() => {
  let privateVal = 'propOriginal';
  // privateVal changes after 500ms:
  setTimeout(() => {
    console.log('Changing privateVal to propChanged');
    privateVal = 'propChanged';
  }, 500);
  
  // Return an object which has a getter, which returns privateProp:
  return {
    get privateProp() {
      return privateVal;
    }
  };
})();

// At this point, if one only has a reference to `obj`,
// there is no real way to watch for changes to privateVal:
console.log(obj.privateProp);

// Assigning a custom setter to obj.privateProp
// will only result in observing attempted assignments to obj.privateProp
// but will not be able to observe the change to privateVal:

Object.defineProperty(obj, 'privateProp', { set(newVal) {
  console.log('Observed change: new value is ' + newVal);
}});

setTimeout(() => {
  obj.privateProp = 1000;
}, 2500);

As you can see, the setter function added to obj in the outer scope cannot capture the change to privateVal. This isn't exactly what's happening with window.screen, but it's similar; you do not have access to the underlying code structure that results in changes to the value returned by calling the built-in getter.


The built-in getter function on screen is composed of native code - this means that it's not an ordinary Javascript function. Functions composed of native code are always functions provided by the browser (or whatever environment the code is running in); they often cannot be emulated by any Javascript functions you write yourself. For example, only the window.history object can perform history actions like history.back(); if window.history gets overwritten, and you don't have a saved reference to it or any other history objects, there's no way to write your own function that can do history.back(), because .back() invokes privileged native code that requires an interface between the visible Javascript and the browser engine.

Similarly, the built-in window.screen getter, when called, returns a value not directly observable by plain Javascript. Without polling, the only other way to watch for a change is by listening for the resize event, as covered in the other answer. (This resize event, similar to the underlying value returned by window.screen, is managed by browser internals not observable otherwise.)

If the resize event does not get fired in response to a change in resolution - for example, if the browser window is small enough already that a change is not required for the smaller resolution - then the resolution change does not result in any event being fired, which means there's unfortunately no way to listen for it, aside from polling. (You can try it for yourself, it doesn't look like any other events are triggered on resolution change)

One could probably write (or tweak) a browser engine which listens for a resolution change from the OS and dispatches a Javascript event when found, but doing so would require knowledge far beyond Javascript - feel free to browse Chromium's source code for details, but it's pretty complicated, and probably opaque to one without experience in the languages used.

这篇关于检测变量何时在javascript中发生更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆