现代 JavaScript 语法:对表达式语言进行了改进
表达式可使用来自 ECMAScript 2018 的 JavaScript 语法
JavaScript 语言中加入了大量 ECMAScript 3 之后新增的内容。提供了更加精简和可读的方式以使用字符串、数组和对象。提供了新的方式用于声明变量和函数,以及默认参数、扩展运算符等等。由于这些变化通常属于 JavaScript 语言的范围,本文档并不涉及这些变化。
从源文本链接到其他属性时不再需要 .value
从源文本资源属性引用另一个属性值时,旧版 ExtendScript 引擎要求在属性的结尾添加 .value。除非明确使用其他属性(例如 .propertyIndex 或 .name),否则 JavaScript 引擎默认显示属性值。
例如:
thisComp.layer("Solid 1").transform.position // 在旧版引擎中,当应用于“源文本”属性时,此计算结果为“[object Property]”。// 在 JavaScript 引擎中,当应用于“源文本”属性时,计算结果为图层“"Solid 1”的“位置”属性值。
使用 posterizeTime(0) 冻结属性值
在 After Effects 16.0 中,使用 posterizeTime(0) 可冻结合成中位于时间 0 的属性值。此功能同时适用于 JavaScript 和旧版 ExtendScript 引擎。
注意:
此功能并不反向兼容,用于 After Effects 16.0 之前的旧版本可能导致意外的结果。
不兼容的旧版语法
旧版 ExtendScript 表达式引擎的绝大部分表达式语法都与 JavaScript 表达式引擎正向兼容。然而,仍有部分旧版语法与 JavaScript 表达式引擎不兼容。有时这是因为现代 JavaScript 语法变化所导致的结果。在其他情况下,则是因为废弃或过时的语法已经被删除。下方提供了一些无效语法和有效语法的示例。
绝大多数此类语法差别,都可通过在应用程序脚本编写中重写表达式来修正。
if...else 语法差别
一般来说,写 if...else 语句时,建议务必根据《MDN 指导原则》进行换行和使用括号。旧版 ExtendScript 引擎对于 if...else 语句的语法比较宽松,容错能力较高,而 JavaScript 则非常严格。使用 JavaScript 引擎时,无法对错误的 if...else 语法求值。
不允许出现表达式末尾只有 IF 而没有 ELSE 的情况
如果某个表达式的末尾有 if 语句而没有 else 语句,则 JavaScript 无法求该表达式的值,且会显示错误消息“表达式中使用了未定义的值(可能是数组下标超出范围?)”在旧版 ExtendScript 引擎的以下表达式中,如果时间大于 1 秒,则求值结果为 100,否则求值结果为 50:
var x = 50;if ( time > 1 ) { x = 100}// 此处隐含“else”,但未明确表述。
而在 JavaScript 引擎中,表达式中的最后一个语句需要通过语句中的 else 部分来明确表述:
var x = 50;if ( time > 1 ) { x = 100;} else { x;}
此例在 JavaScript 引擎和旧版 ExtendScript 引擎中都可正确求值。
不使用括号的情况下,if 和 else 不能在同一行
if...else 在同一行且不加括号的语句在旧版 ExtendScript 引擎中可以求值,但在 JavaScript 引擎中无法求值,且会显示错误信息“语法错误:意外的 else 标记”或“表达式中使用了未定义的值(可能是数组下标超出范围?)”。根据上下文和属性类型的不同,显示的错误也不同。
在旧版 ExtendScript 引擎的以下表达式中,如果时间大于 1 秒,则求值结果为 100,否则求值结果为 50:
if ( time > 1 ) 100 else 50;
JavaScript 引擎要求 if...else 语句正确换行或使用括号才能求值简单 Case 可使用三元运算符替换。以下任一语法都可用于 JavaScript 引擎:
// 解决方案 A:在“else”之前添加换行符,可在两个引擎中正确求值。if ( time > 1 ) 100else 50;// 解决方案 B:添加正确的括号,可在两个引擎中正确求值。if ( time > 1 ) { 100 } else { 50 };// 解决方案 C:使用三元运算符代替 if ...else 语句,也可在两个引擎中正确求值。time > 1 ?100 : 50;
所有上述解法,都可在 JavaScript 引擎和旧版 ExtendScript 中正确求值。
表达式结尾不能使用函数声明
如果表达式结尾使用函数声明,则 JavaScript 引擎无法对函数求值,且会显示错误信息“发现对象的类型应该是数字、数组或属性”。在 JavaScript 引擎中,求值的最后一项必须返回一个值,而不是声明一个值。
以下示例在旧版引擎中有效,但在 JavaScript 引擎中无效:
timesTen( value ); // 即使下面声明了该函数,旧版引擎也会计算此行。function timesTen ( val ) {return val * 10}
当调用某个函数(而不是声明)作为最后一行时,表达式在两个引擎中都可正确求值:
function timesTen ( val ) {return val * 10}timesTen( value ); // JavaScript 引擎需要在声明下方调用函数,才能返回正确的值。
不允许使用 this() 简写语法;使用 thisLayer() 代替
在旧版 ExtendScript 引擎中,允许使用 this 作为 thisLayer 的简写。而在 JavaScript 引擎中,this 代表全局对象且必须使用 thisLayer 来代替。在 JavaScript 引擎中使用 this 通常会导致“这不是一个函数”错误。
在以下旧版 ExtendScript 示例中,this 被用于创建从源文本属性到文本层位置属性的简洁链接:
this(5)(2).value;
在 JavaScript 引擎中,必须使用 thisLayer 代替 this:
thisLayer(5)(2).value;
使用 thisLayer 的情况下,两种表达式引擎都可兼容。
源文本属性数组索引访问字符需要 .value
在旧版 ExtendScript 表达式引擎中,文本属性的字符可通过括号来访问,例如数组:
text.sourceText[0] // 返回“源文本”属性的文本值的第一个字符。
在 JavaScript 引擎中,必须添加 .value 才能访问字符:
text.sourceText[0] // 返回“源文本”属性的文本值的第一个字符。
此语法两个引擎均兼容。
不允许使用蛇形命名法属性和方法
JavaScript 引擎不支持已经被弃用的蛇形命名法属性和方法(用下划线而不是驼峰命名法编写)。应使用两个引擎都兼容的驼峰命名法版本。下面提供了已弃用的蛇形命名法及对应驼峰命名法的清单。
蛇形命名法属性 | 驼峰命名法属性 | 蛇形命名法方法 | 驼峰命名法方法 |
---|---|---|---|
this_comp
this_layer this_property color_depth has_parent in_point out_point start_time has_video has_audio audio_active anchor_point audio_levels time_remap casts_shadows light_transmission accepts_shadows accepts_lights frame_duration shutter_angle shutter_phase num_layers pixel_aspect point_of_interest depth_of_field focus_distance blur_level cone_angle cone_feather shadow_darkness shadow_diffusion active_camera |
thisComp
thisLayer thisProperty colorDepth hasParent inPoint outPoint startTime hasVideo hasAudio audioActive anchorPoint audioLevels timeRemap castsShadows lightTransmission acceptsShadows acceptsLights frameDuration shutterAngle shutterPhase numLayers pixelAspect pointOfInterest depthOfField focusDistance blurLevel coneAngle coneFeather shadowDarkness shadowDiffusion activeCamera |
value_at_time()
velocity_at_time() speed_at_time() nearest_key() posterize_time() look_at() seed_random() gauss_random() ease_in() ease_out() rgb_to_hsl() hsl_to_rgb() degrees_to_radians() radians_to_degrees() from_comp_to_surface() to_comp_vec() from_comp_vec() to_world_vec() from_world_vec() to_comp() from_comp() to_world() from_world() temporal_wiggle() loop_in_duration() loop_out_duration() loop_in() loop_out() |
valueAtTime()
velocityAtTime() speedAtTime() nearestKey() posterizeTime() lookAt() seedRandom() gaussRandom() easeIn() easeOut() rgbToHsl() hslToRgb() degreesToRadians() radiansToDegrees() fromCompToSurface() toCompVec() fromCompVec() toWorldVec() fromWorldVec() toComp() fromComp() toWorld() fromWorld() temporalWiggle() loopInDuration() loopOutDuration() loopIn() loopOut() |
使用 eval() 和二进制编码 (.jsxbin) 表达式
JavaScript 引擎不支持使用 ExtendScript 二进制格式编码的表达式(使用 ExtendScript ToolKit CC 保存的二进制 .jsxbin 文件)。
如要混淆某个表达式,可使用旧版 ExtendScript 引擎或使用与 ECMAScript 2018 兼容的其他混淆方法。部分混淆方法可能无法与两个表达式引擎同时兼容。
对 $. (Dollar) 对象的支持有限
$.(Dollar) 对象方法和属性属于 ExtendScript 专用,很可能不受 JavaScript 引擎支持。此表列出了 $.(Dollar) 对象不受支持和受支持的用法:
不支持的 $. | 支持的 $. |
---|---|
$.fileName
$.hiResTimes $.stack $.evalFile() $.list() $.setenv() $.getenv() $.appEncoding $.buildDate $.decimalPoint $.dictionary $.error $.flags $.includePath $.level $.line $.locale $.localize $.memCache $.os $.screens $.strict $.version |
$.build
$.engineName(Legacy ExtendScript 引擎不支持此项) $.global |
不支持 ...reflect.properties、...reflect.methods 和 toSource()
JavaScript 引擎不支持 reflect.properties 和 reflect.methods,这两项是 ExtendScript 的特有方法,JavaScript 中没有直接对应的方法。
toSource() 已经被 JavaScript 弃用,且已被所有标准跟踪删除。
要查看任何给定 After Effects 属性的可用属性和方法列表,可使用与上方类似的方法,在源文本中使用以下表达式,并链接到所需属性,例如,在第 1 行 thisProperty 的位置使用关联器:
let obj = thisProperty; // 将“thisProperty”替换为您所需属性的属性链接。let props = [];do { Object.getOwnPropertyNames(obj).forEach(prop => {if (props.indexOf(prop) === -1) { props.push(prop);}});} while (obj = Object.getPrototypeOf(obj));props.join("\n"); // 返回列出可用属性和方法的字符串数组。
上方的表达式与旧版 ExtendScript 不兼容。其中使用了 ECMAScript 3 中不可用的语法和方法。
JavaScript 引擎对于 .jsx 表达式库和 eval() 的语法要求
在 .jsx 表达式函数库中使用表达式时或在 eval() 中调用表达式时,需要对某些语法进行修改:
没有显式调用到层或属性上的任何原生方法或属性,都需要添加显式 thisLayer. 或 thisProperty. 前缀。此前缀用于告知 JavaScript 引擎所调用的方法或属性来自哪个对象。
数组值(例如“坐标”)的数学运算需要使用矢量运算来计算,也可通过使用循环函数对应数组中的每个项来计算。重载数学运算符(例如:position + [100,100])将不会被求值。
使用 JavaScript 引擎时,会先对表达式进行预处理,然后再进行求值,这样新引擎才能解读部分旧版 ExtendScript 表达式语法。但是,对 .jsx 表达式函数库求值时或在 eval() 中调用表达式时,不会执行这些预处理任务。出现此类情况时,必须对上述语法进行手动修改。所有这些语法修改都反向兼容旧版 ExtendScript 引擎,因此为 JavaScript 引擎应用而编写的 .jsx 表达式库,也可用于旧版 ExtendScript。
性能提示:与直接从属性调用复杂表达式相比,通过 JavaScript 使用此语法从 .jsx 调用同样的表达式性能表现更好,因为后者不会进行预处理。
使用 thisLayer. 或 thisProperty. 为原生方法和属性添加显式前缀
下表列出了需要前缀的方法和属性。例如,time 必须写作 thisLayer.time,而 wiggle() 必须写作 thisProperty.wiggle()。
只有没有显式调用其他层或属性上的属性或方法时,才需要这些前缀。例如,调用 thisComp.layer(1).hasParent 时,无需添加 thisLayer.,因为 layer(1)已经显式调用了 .hasParent。
需要 thisLayer. 的方法 | 需要 thisLayer. 的属性 | 需要 thisProperty. 的方法 | 需要 thisProperty. 的属性 |
---|---|---|---|
comp()footage()posterizeTime()add()sub()mul()div()clamp()length()dot()normalize()cross()lookAt()timeToFrames()framesToTime()timeToTimecode()timeToFeetAndFrames()timeToNTSCTimecode()timeToCurrentFormat()seedRandom()random()gaussRandom()noise()degreesToRadians()radiansToDegrees()linear()ease()easeIn()easeOut()rgbToHsl()hslToRgb()hexToRgb()mask()sourceRectAtTime()sourceTime()sampleImage()toComp()fromComp()toWorld()fromWorld()toCompVec()fromCompVec()toWorldVec()fromWorldVec()fromCompToSurface() | timesourcethisProjectcolorDepthtransformanchorPointpositionscalerotationopacityorientationrotationXrotationYrotationZlightTransmissioncastsShadowsacceptsShadowsacceptsLightsambientdiffusespecularspecularIntensityshininessspecularShininessmetalaudioLevelstimeRemapmarkernamewidthheightindexparenthasParentinPointoutPointstartTimehasVideohasAudioactiveenabledaudioActivecameraOptionpointOfInterestzoomdepthOfFieldfocusDistanceaperatureblurLevelirisShapeirisRotationirisRoundnessirisAspectRatioirisDiffractionFringehighlightGainhighlightThresholdhighlightSaturationlightOptionintensitycolorconeAngleconeFeathershadowDarknessshadowDiffusion | valueAtTime()velocityAtTime()speedAtTime()wiggle()temporalWiggle()smooth()loopIn()loopOut()loopInDuration()loopOutDuration()key()nearestKey()propertyGroup()points()inTangents()outTangents()isClosed()pointsOnPath()tangentOnPath()normalOnPath()createPath() | velocityspeednumKeyspropertyIndex |
使用矢量运算函数代替数学运算符
JavaScript 和旧版 ExtendScript 引擎都允许对数组使用重载数学运算符(所用语法类似于 position + [100,100]),但此功能对于 .jsx 表达式函数库或 eval() 中的表达式无效。
要对类似于“坐标”、“比例”之类的数组属性执行数学运算,应使用等价矢量进行加减乘除运算。矢量运算函数也可用于正则数,因此有可能会被调用到其中任何一种数据类型属性上的函数,都应该使用矢量运算函数。
注意:
thisLayer. 前缀必须搭配矢量运算函数使用。
- 加:thisLayer.add(vec1, vec2)
- 减:thisLayer.sub(vec1, vec2)
- 乘:thisLayer.mul(vec, amount)
- 除:thisLayer.div(vec, amount)
下方提供了一些使用标准运算法和更新后矢量运算法的表达式示例。在需要时,矢量运算表达式也会使用相应的 thisLayer. 或 thisProperty. 前缀。
用于求 wiggle() 和 Position 属性值之间的差值:
// 标准运算法:wiggle() - value;// 矢量运算法:thisLayer.sub( thisProperty.wiggle(), value );
用于在两个值之间插补(类似于 linear()),但范围超过规定的下限和上限:
// 标准运算法:value1 + ( ( t - tMin ) / ( tMax - tMin ) ) * ( value2 - value1 );// 矢量运算法:thisLayer.add( value1, thisLayer.mul( thisLayer.div( thisLayer.sub( t, tMin ), thisLayer.sub( tMax, tMin ) ), thisLayer.sub( value2, value1 ) ) );
用于从 Position 属性向前或向后循环:
// 标准运算法:loopIn( "cycle" ) + loopOut( "cycle" ) - value;// 矢量运算法:thisLayer.sub( thisLayer.add( thisProperty.loopIn( "cycle" ), thisProperty.loopOut( "cycle" ) ), value );
上次更新日期: 2023年5月25日
原文链接:https://helpx.adobe.com/cn/after-effects/using/legacy-and-extend-script-engine.html