After Effects 16.0 中的 JavaScript 与旧版 ExtendScript 表达式引擎之间的语法差别。

优质
小牛编辑
128浏览
2023-12-01
本文将说明 After Effects 16.0 中使用的 JavaScript 与旧版 ExtendScript 表达式引擎之间的表达式语言语法差别。

请参见本文档了解如何通过 JavaScript 表达式引擎改进您的表达式,或当 JavaScript 表达式引擎对旧版 After Effects 所用表达式求值发生错误时,如何修复错误。

After Effects 中的表达式语言基于 JavaScript(ECMAScript 的一种实现)。After Effects 16.0 中的 JavaScript 表达式引擎基于 ECMAScript 2018。旧版 ExtendScript 表达式引擎基于 ECMAScript 3 (1999)。(Adobe ExtendScript 也是 After Effects 和其他 Adobe 应用程序所用的脚本编写语言。)

您可根据下方提供的示例和指导,了解如何编写能够同时用于 JavaScript 和旧版 ExtendScript 表达式引擎的表达式。

JavaScript 与旧版 ExtendScript 表达式引擎之间的核心差别如下:

  • 现代 JavaScript 语法:从 ECMAScript 5 到 ECMAScript 2018,JavaScript 添加了很多内容,使用 JavaScript 表达式引擎时可使用新的表达式语法和方法。另外,使用 JavaScript 表达式引擎时的表达式语法也进行了一些小小的改进。以下是部分核心差别:
    • 表达式可使用来自 ECMAScript 2018 的 JavaScript 语法
    • 从源文本链接到其他属性时不再需要 .value
    • 使用 posterizeTime(0) 冻结属性值
  • 不兼容的旧版语法:JavaScript 表达式引擎不兼容部分旧版语法。此处记录了这些不兼容的情况,并介绍了需要如何更改才能让 JavaScript 兼容这些无效的语法。以下是部分核心差别:
    • if...else 语法差别
    • 不使用括号的情况下,if 和 else 不能在同一行
    • 表达式结尾不能使用函数声明
    • 不允许使用 this() 简写语法;使用 thisLayer() 代替
    • 源文本属性数组索引访问字符需要 .value
    • 不允许使用蛇形命名法属性和方法
    • 使用 eval() 和二进制编码 (.jsxbin) 表达式
    • 对 $. (Dollar) 对象的支持有限
    • 不支持 ...reflect.properties、...reflect.methods 和 toSource()
  • 对于 .jsx 表达式库和 eval() 的语法要求:在 .jsx 表达式函数库中使用表达式时,或在 eval() 中调用表达式时,需要显式调用 thisLayer. 和 thisProperty.,且数组的数学运算需要用矢量运算来代替。以下是部分核心差别:
    • 使用 thisLayer.或 thisProperty. 为原生方法和属性添加显式前缀
    • 使用矢量运算函数代替数学运算符

现代 JavaScript 语法:对表达式语言进行了改进

表达式可使用来自 ECMAScript 2018 的 JavaScript 语法

JavaScript 语言中加入了大量 ECMAScript 3 之后新增的内容。提供了更加精简和可读的方式以使用字符串、数组和对象。提供了新的方式用于声明变量和函数,以及默认参数、扩展运算符等等。由于这些变化通常属于 JavaScript 语言的范围,本文档并不涉及这些变化。如需了解这些新增语法的详情,请访问以下链接:

  • JavaScript ES5 指南(又名 ECMAScript 5)
    • 新的数组方法
    • 新的对象方法
    • JSON.stringify() 和 JSON.parse()
  • JavaScript ES6 指南(又名 ECMAScript 2015)
    • let 和 const
    • 箭头函数
    • 扩展运算符
    • ECMAScript 2016 指南
  • ECMAScript 2016 指南
    • Array.includes()
    • 求幂运算符
  • ECMAScript 2017 指南
    • 字符串填充
    • 尾后逗号
  • ECMAScript 2018 指南
    • 对象的 Rest/Spread 属性

额外推荐深入介绍 JavaScript 的学习资源:

  • Mozilla 的 MDN JavaScript Web 文档
  • w3schools 的 JavaScript 教程

从源文本链接到其他属性时不再需要 .value

从源文本资源属性引用另一个属性值时,旧版 ExtendScript 引擎要求在属性的结尾添加 .value。除非明确使用其他属性(例如 .propertyIndex 或 .name),否则 JavaScript 引擎默认显示属性值。

例如:

.syntaxhighlighter .line {white-space: normal !important;}
thisComp.layer("Solid 1").transform.position // In the Legacy engine, this evaluates to "[object Property]" when applied to a Source Text property.// In the JavaScript engine, this evaluates to the value of the Position property of layer "Solid 1" when applied to a Source Text property.

使用 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}// The "else" here is implied but not stated.

而在 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 引擎:

.syntaxhighlighter .line {white-space: normal !important;}
// Solution A: adding a line break before "else" will allow both engines to evaluate correctly. if ( time > 1 ) 100else 50; // Solution B: adding correct bracketing will also allow both engines to evaluate correctly. if ( time > 1 ) { 100 } else { 50 }; // Solution C: Use a ternary operator in place of the if...else statement, which also evaluates correctly in both engines. time > 1 ? 100 : 50;

所有上述解法,都可在 JavaScript 引擎和旧版 ExtendScript 中正确求值。

表达式结尾不能使用函数声明

如果表达式结尾使用函数声明,则 JavaScript 引擎无法对函数求值,且会显示错误信息“发现对象的类型应该是数字、数组或属性”。在 JavaScript 引擎中,求值的最后一项必须返回一个值,而不是声明一个值。

以下示例在旧版引擎中有效,但在 JavaScript 引擎中无效:

timesTen( value );  // The Legacy engine evaluates this line, even though the function is declared below. function timesTen ( val ) {return val * 10}

当调用某个函数(而不是声明)作为最后一行时,表达式在两个引擎中都可正确求值:

function timesTen ( val ) {return val * 10} timesTen( value );  // The JavaScript engine needs the function call to happen below the declaration in order to return the correct value.

不允许使用 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]  // Returns the first character of the Source Text property's text value.

在 JavaScript 引擎中,必须添加 .value 才能访问字符:

text.sourceText.value[0]// Returns the first character of the Source Text property's text value.

此语法两个引擎均兼容。

不允许使用蛇形命名法属性和方法

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; // Replace "thisProperty" with a property-link to your desired property.let props = []; do {Object.getOwnPropertyNames(obj).forEach(prop => {if (props.indexOf(prop) === -1) {props.push(prop);}});} while (obj = Object.getPrototypeOf(obj)); props.join("\n");   // Returns an array of strings listing the properties and methods available.

上方的表达式与旧版 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()
time
source
thisProject
colorDepth
transform
anchorPoint
position
scale
rotation
opacity
orientation
rotationX
rotationY
rotationZ
lightTransmission
castsShadows
acceptsShadows
acceptsLights
ambient
diffuse
specular
specularIntensity
shininess
specularShininess
metal
audioLevels
timeRemap
marker
name
width
height
index
parent
hasParent
inPoint
outPoint
startTime
hasVideo
hasAudio
active
enabled
audioActive
cameraOption
pointOfInterest
zoom
depthOfField
focusDistance
aperature
blurLevel
irisShape
irisRotation
irisRoundness
irisAspectRatio
irisDiffractionFringe
highlightGain
highlightThreshold
highlightSaturation
lightOption
intensity
color
coneAngle
coneFeather
shadowDarkness
shadowDiffusion
valueAtTime()
velocityAtTime()
speedAtTime()
wiggle()
temporalWiggle()
smooth()
loopIn()
loopOut()
loopInDuration()
loopOutDuration()
key()
nearestKey()
propertyGroup()
points()
inTangents()
outTangents()
isClosed()
pointsOnPath()
tangentOnPath()
normalOnPath()
createPath()
velocity
speed
numKeys
propertyIndex

使用矢量运算函数代替数学运算符

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 属性之间的差:

    // Standard Math:wiggle() - value;  // Vector Math:thisLayer.sub( thisProperty.wiggle(), value );

    用于在两个值之间插补(类似于 linear()),但范围超过规定的下限和上限:

    // Standard Math:value1 + ( ( t - tMin ) / ( tMax - tMin ) ) * ( value2 - value1 );  // Vector Math:thisLayer.add( value1, thisLayer.mul( thisLayer.div( thisLayer.sub( t, tMin ), thisLayer.sub( tMax, tMin ) ), thisLayer.sub( value2, value1 ) ) );

    用于从 Position 属性向前或向后循环:

    // Standard Math:loopIn( "cycle" ) + loopOut( "cycle" ) - value;  // Vector Math:thisLayer.sub( thisLayer.add( thisProperty.loopIn( "cycle" ), thisProperty.loopOut( "cycle" ) ), value );

    更多此类内容

    • 表达式语言引用