我遇到了一个问题,就是有时候我网页上的JavaScript获得window.pageYOffset
的值时,即使我知道用户正在查看文档的中间并且它的值应该很大,它还是会莫名其妙地为0。 ,例如650000。请注意,我有很大一部分时间获得了合理的价值。但是有时它是零,有时是一个看似随机的小值,例如当我期望650000时在6000范围内。
我不想问一堆代码,而是想问一些一般性问题,以帮助我弄清楚从哪里开始。
此页面正在iOS WKWebView
中显示(尽管此问题可以在Android应用程序的类似上下文中显示)。我的应用程序中的JavaScript方法可以通过以下几种方式之一调用:
当我的应用程序收到页面已完成加载(通过委托方法)的通知时,它将使用Objective-C代码中的evaluateJavaScript
调用JavaScript方法。
我的应用可以在其他时间调用evaluateJavaScript
,而不仅仅是在页面加载完成时。
可能会由于计时器触发而调用JavaScript函数。
滚动事件的结果可能会调用JavaScript函数。
我一直在假设页面上的JavaScript代码始终在单个线程中运行。也就是说,我不会遇到计时器触发,发生滚动事件,甚至来自Objective-C代码(使用evaluateJavaScript
的调用)都不会中断JavaScript运行时中可能发生的任何情况的情况。因此,在尝试访问window.pageYOffset
时,我不必担心会中断某些正在修改div
的系统级活动。
所以这是我的第一个问题:我是否纠正代码外的某个人在一个线程上调用我的JavaScript方法,而不是在另一个线程上胡乱使用DOM?
第二个问题与我有关:我的代码修改了DOM,添加和删除了insertAfter
元素。我一直以为这些修改是同步的-如果我用insertBefore
或top
插入一个元素,我希望子/父/兄弟指针在返回时是正确的,并且我假设可以立即访问其他元素上的left
和window.pageYOffset
值之类的东西,它们将被更新以反映插入/删除的元素。关键是,在进行更改之后和检查类似window.pageYOffset
之类的代码之前,我不必“等待” DOM“稳定”。这是正确的吗?
另一个提示:为了缓解这种情况,我很幸运,只需在函数顶部测试window.pageYOffset
的零。如果它为零,我会给自己打电话回计时器(只有1毫秒的延迟)。如果我做的时间足够长,它将最终非零。
也许在阅读了所有这些内容之后,没有一个细节是相关的,并且您知道了以下基本问题的答案:为什么有时在同一行代码中,/**
* Copyright 2015 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
require( 'dotenv' ).config( {silent: true} );
const TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
var express = require( 'express' ); // app server
var bodyParser = require( 'body-parser' ); // parser for post requests
var Watson = require( 'watson-developer-cloud/conversation/v1' ); // watson sdk
// The following requires are needed for logging purposes
var uuid = require( 'uuid' );
var vcapServices = require( 'vcap_services' );
var basicAuth = require( 'basic-auth-connect' );
// The app owner may optionally configure a cloudand db to track user input.
// This cloudand db is not required, the app will operate without it.
// If logging is enabled the app must also enable basic auth to secure logging
// endpoints
var cloudantCredentials = vcapServices.getCredentials( 'cloudantNoSQLDB' );
var cloudantUrl = null;
if ( cloudantCredentials ) {
cloudantUrl = cloudantCredentials.url;
}
cloudantUrl = cloudantUrl || process.env.CLOUDANT_URL; // || '<cloudant_url>';
var logs = null;
var app = express();
// set up routes
var token = require('./routes/token');
app.use('/token', token);
// Bootstrap application settings
app.use( express.static( './public' ) ); // load UI from public folder
app.use( bodyParser.json() );
// Create the service wrapper
var conversation = new Watson( {
// If unspecified here, the CONVERSATION_USERNAME and CONVERSATION_PASSWORD env properties will be checked
// After that, the SDK will fall back to the bluemix-provided VCAP_SERVICES environment property
// username: '<username>',
// password: '<password>',
username: process.env.ASSISTANT_USERNAME || '<username>',
password: process.env.ASSISTANT_PASSWORD || '<password>',
url: 'https://gateway.watsonplatform.net/conversation/api',
version_date: '2016-09-20',
version: 'v1'
} );
const textToSpeech = new TextToSpeechV1({
username: process.env.TEXT_TO_SPEECH_USERNAME,
password: process.env.TEXT_TO_SPEECH_PASSWORD,
version: 'v1'
// If unspecified here, the TEXT_TO_SPEECH_USERNAME and
// TEXT_TO_SPEECH_PASSWORD env properties will be checked
// After that, the SDK will fall back to the bluemix-provided VCAP_SERVICES environment property
// username: '<username>',
// password: '<password>',
});
/************************************************
* Conversation services
************************************************/
// Endpoint to be call from the client side
app.post( '/api/message', function(req, res) {
var workspace = process.env.WORKSPACE_ID || '<workspace-id>';
if ( !workspace || workspace === '<workspace-id>' ) {
return res.json( {
'output': {
'text': 'The app has not been configured with a <b>WORKSPACE_ID</b> environment variable. Please refer to the ' +
'<a href="https://github.com/watson-developer-cloud/conversation-simple">README</a> documentation on how to set this variable. <br>' +
'Once a workspace has been defined the intents may be imported from ' +
'<a href="https://github.com/watson-developer-cloud/conversation-simple/blob/master/training/car_workspace.json">here</a> in order to get a working application.'
}
} );
}
var payload = {
workspace_id: workspace,
context: {},
input: {}
};
if ( req.body ) {
if ( req.body.input ) {
payload.input = req.body.input;
}
if ( req.body.context ) {
// The client must maintain context/state
payload.context = req.body.context;
}
}
// Send the input to the conversation service
conversation.message( payload, function(err, data) {
if ( err ) {
return res.status( err.code || 500 ).json( err );
}
return res.json( updateMessage( payload, data ) );
} );
} );
/**
* Updates the response text using the intent confidence
* @param {Object} input The request to the Conversation service
* @param {Object} response The response from the Conversation service
* @return {Object} The response with the updated message
*/
function updateMessage(input, response) {
var responseText = null;
var id = null;
if ( !response.output ) {
response.output = {};
} else {
if ( logs ) {
// If the logs db is set, then we want to record all input and responses
id = uuid.v4();
logs.insert( {'_id': id, 'request': input, 'response': response, 'time': new Date()});
}
return response;
}
if ( response.intents && response.intents[0] ) {
var intent = response.intents[0];
// Depending on the confidence of the response the app can return different messages.
// The confidence will vary depending on how well the system is trained. The service will always try to assign
// a class/intent to the input. If the confidence is low, then it suggests the service is unsure of the
// user's intent . In these cases it is usually best to return a disambiguation message
// ('I did not understand your intent, please rephrase your question', etc..)
if ( intent.confidence >= 0.75 ) {
responseText = 'I understood your intent was ' + intent.intent;
} else if ( intent.confidence >= 0.5 ) {
responseText = 'I think your intent was ' + intent.intent;
} else {
responseText = 'I did not understand your intent';
}
}
response.output.text = responseText;
if ( logs ) {
// If the logs db is set, then we want to record all input and responses
id = uuid.v4();
logs.insert( {'_id': id, 'request': input, 'response': response, 'time': new Date()});
}
return response;
}
if ( cloudantUrl ) {
// If logging has been enabled (as signalled by the presence of the cloudantUrl) then the
// app developer must also specify a LOG_USER and LOG_PASS env vars.
if ( !process.env.LOG_USER || !process.env.LOG_PASS ) {
throw new Error( 'LOG_USER OR LOG_PASS not defined, both required to enable logging!' );
}
// add basic auth to the endpoints to retrieve the logs!
var auth = basicAuth( process.env.LOG_USER, process.env.LOG_PASS );
// If the cloudantUrl has been configured then we will want to set up a nano client
var nano = require( 'nano' )( cloudantUrl );
// add a new API which allows us to retrieve the logs (note this is not secure)
nano.db.get( 'car_logs', function(err) {
if ( err ) {
console.error(err);
nano.db.create( 'car_logs', function(errCreate) {
console.error(errCreate);
logs = nano.db.use( 'car_logs' );
} );
} else {
logs = nano.db.use( 'car_logs' );
}
} );
// Endpoint which allows deletion of db
app.post( '/clearDb', auth, function(req, res) {
nano.db.destroy( 'car_logs', function() {
nano.db.create( 'car_logs', function() {
logs = nano.db.use( 'car_logs' );
} );
} );
return res.json( {'message': 'Clearing db'} );
} );
// Endpoint which allows conversation logs to be fetched
app.get( '/chats', auth, function(req, res) {
logs.list( {include_docs: true, 'descending': true}, function(err, body) {
console.error(err);
// download as CSV
var csv = [];
csv.push( ['Question', 'Intent', 'Confidence', 'Entity', 'Output', 'Time'] );
body.rows.sort( function(a, b) {
if ( a && b && a.doc && b.doc ) {
var date1 = new Date( a.doc.time );
var date2 = new Date( b.doc.time );
var t1 = date1.getTime();
var t2 = date2.getTime();
var aGreaterThanB = t1 > t2;
var equal = t1 === t2;
if (aGreaterThanB) {
return 1;
}
return equal ? 0 : -1;
}
} );
body.rows.forEach( function(row) {
var question = '';
var intent = '';
var confidence = 0;
var time = '';
var entity = '';
var outputText = '';
if ( row.doc ) {
var doc = row.doc;
if ( doc.request && doc.request.input ) {
question = doc.request.input.text;
}
if ( doc.response ) {
intent = '<no intent>';
if ( doc.response.intents && doc.response.intents.length > 0 ) {
intent = doc.response.intents[0].intent;
confidence = doc.response.intents[0].confidence;
}
entity = '<no entity>';
if ( doc.response.entities && doc.response.entities.length > 0 ) {
entity = doc.response.entities[0].entity + ' : ' + doc.response.entities[0].value;
}
outputText = '<no dialog>';
if ( doc.response.output && doc.response.output.text ) {
outputText = doc.response.output.text.join( ' ' );
}
}
time = new Date( doc.time ).toLocaleString();
}
csv.push( [question, intent, confidence, entity, outputText, time] );
} );
res.csv( csv );
} );
} );
}
/************************************************
* Text-to-Speech services
************************************************/
/**
* Pipe the synthesize method
*/
app.get('/api/synthesize', (req, res, next) => {
const transcript = textToSpeech.synthesize(req.query);
transcript.on('response', (response) => {
if (req.query.download) {
if (req.query.accept && req.query.accept === 'audio/wav') {
response.headers['content-disposition'] = 'attachment; filename=transcript.wav';
} else {
response.headers['content-disposition'] = 'attachment; filename=transcript.ogg';
}
}
});
transcript.on('error', next);
transcript.pipe(res);
});
// Return the list of voices
app.get('/api/voices', (req, res, next) => {
textToSpeech.voices(null, (error, voices) => {
if (error) {
return next(error);
}
res.json(voices);
});
});
require('./config/error-handler')(app);
module.exports = app;
中我得到一个无效值(通常为0)其他时间为有效值。
答案 0 :(得分:0)
原来的问题是,从我给WKWebView
提供新的HTML字符串以呈现到告诉我完成将页面加载到现有页面之间似乎存在一段时间。仍然活跃。在这段时间内,计时器继续触发,但是某些document
和window
属性将无效。
由于调试在此环境中运行的JavaScript困难,我欺骗自己以为“ pageYOffset
最终变得有效”,而实际上我看到的是新页面最终完成了加载,这个新页面正在生成对计时器功能的有效调用。
在我的特定情况下(可能不适用于所有人),我能够在计时器函数的顶部检测到window.pageYOffset
的值,如果该值为0,请在短暂的延迟后回叫我自己。这使我能够处理由于某种原因window.pageYOffset
仍然无效(我的测试最终将通过并且我的计时器函数将照常运行)的情况,以及一切都被抛出的情况支持新页面(在这种情况下,计时器会因为页面消失而不会触发)。