Salesforce开发人员的JavaScript技能-编写异步JavaScript

学习目标

完成本单元后,您将能够:

  • 识别JavaScript中重要的异步功能。
  • 使用setTimeout异步调用函数。
  • 编写和调用回调函数。
  • 编写和调用基于promise的函数。
  • 描述Aura组件中的异步功能。

回想起我们最初引入JavaScript引擎时的方式。引擎有一个单线程,它可以正常工作,完成工作,然后填充新的工作以重新开始。

当然,至关重要的是不要阻塞线程。 

让我们来看一个例子。

<html>
  <script>
    alert("Does JavaScript show first?");
  </script>
  <body>
    <p>
      Does HTML show first?
    </p>
  </body>
</html>

如果在浏览器中加载该HTML页面,则会发现先弹出警报,然后阻止HTML显示。这是因为该alert()函数会中止JavaScript线程的执行,直到用户将其关闭为止。简而言之,当JavaScript阻止您的浏览器时,就永远不会带来良好的用户体验。 

好消息是,除了一些alert()上面的功能仍然存在的遗留特性外,JavaScript是一种异步语言。 

异步JavaScript无处不在

为了开始异步之旅,让我们重新回顾事件和功能。以前我们看过这样的HTML和JavaScript。

<!-- HTML -->
<button id="clicker">
//JavaScript
let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

在此示例中,我们将handleClick事件控件添加到按钮发出的click事件中。 

在那里!我们已经编写了一些异步JavaScript。 

事件触发时,所有发生的事情是将新消息添加到队列中。没有事件可以接管线程。触发的每个事件都必须进入队列并等待轮流运行。 

一种说明方法是使用setTimeout函数。在此 示例 invoking中setTimeout,我们以毫秒为单位传递了事件处理程序和计时器。计时器到时,它将触发,将事件处理程序添加到队列中。 

setTimeout(function(){
  console.log("This comes first");
}, 0);
console.log("This comes second");
//output in console
// "This comes second"
// "This comes first"

在这里,我们将计时器设置为零。但这并不意味着“立即致电”。这仅表示“立即将其放入队列中”。但是,代码块本身的执行需要完成,从而清除了调用堆栈。只有这样,函数才能setTimeout 轮流使用。 

另一个常见的错误是认为计时器是事件处理程序何时触发的准确预测器,但不一定。事件处理程序仍然必须等待轮到队列。通过测量时间,我们可以看到这个 在行动。 

const timer  = function(){
  let start = Date.now();
  setTimeout(function(){
    let end = Date.now();
    console.log( "Duration: " + (end - start) )
  },1000);
};
timer();
// Console output when invoked several times:
// "Duration: 1007"
// "Duration: 1000"
// "Duration: 1002"
// "Duration: 1004"

时间设置为一秒,并且它接近于该时间。但是很明显,将函数添加到队列然后在每次调用时运行的速度有所不同。 

现在,我们已经看到了一些异步调用的示例,我们可以看看一些常见的异步模式和构造。 

回调模式

回调只是一个传递给另一个函数的函数,该函数会在将来的某个时刻调用它。 

因此,实际上,我们已经看到了很多回调。 

setTimeout(callback, timer)
Element.addEventListener(event, callback)
Array.map(function(item){...})

让我们将其应用于自行车 用例,以了解如何实现回调。当您换挡时,大多数情况下它都起作用。但是失败的可能性仍然很小。这是异步JavaScript的理想方案。让我们看一下回调的外观,该回调接收有关齿轮如何移动的数据,然后在完成时调用传入的函数。 

Bike.prototype.changeGearAsync = function(shiftObject, callback){
  let newIndex = shiftObject.currentIndex + shiftObject.changeBy;
  if (newIndex < 0 || newIndex > shiftObject.maxIndex) {
    callback(new Error("There is a problem"), null);
  } else {
    callback(null, newIndex);
  }
};

该参数callback实际上是一个函数。如果有错误,我们将调用它并为第一个参数设置要发送回的任何错误数据。成功后,我们将错误参数设为空并传回正确的数据。然后,我们可以看到如何调用新的换档功能。 

Bike.prototype.changeGear = function(frontOrRear, changeBy) {
  const shiftIndexName = frontOrRear + "GearIndex"
  const that = this;
  //contains state change for making the shift
  let shiftObject = {
    currentIndex: this[shiftIndexName],
    maxIndex: this.transmission[frontOrRear + "GearTeeth"].length,
    changeBy: changeBy
  }
  // invoke async function with anonymous callback
  this.changeGearAsync(shiftObject, function(err, newIndex){
    if (err) {
      console.log("No Change");
    } else {
      that[shiftIndexName] = newIndex;
    }
  });
};

回调模式已被广泛接受并广泛使用,但是它也有一些缺点。首先,当多个回调链接在一起时,它们被嵌套在另一个中。这会造成不必要的复杂性,可读性问题,并且在阅读别人的代码时很难推理。此缺陷称为回调地狱。回调也没有隐式错误状态(像try/catch这样)。由开发人员编写回调以明确查找带有if条件的错误,这取决于开发人员。这些障碍导致了诺言的创造。 

箭头功能

在前面的示例中,您可能已经注意到了这一行。

const that = this;

这是旧版JavaScript的遗物。我们仅介绍一种新的函数语法:箭头函数。回想一下调用函数时会发生什么。具体来说,它将绑定到新this上下文。与匿名函数关闭范围内的其他变量不同,JavaScript this实际上在实际上我们需要this包含函数时重新绑定。 

一个长期的解决方法是分配this一个新变量(按照惯例通常称为selfor that),然后将上下文引用保留在闭包中。

箭头函数通过不重新绑定来消除编码技巧的这一点this。箭头函数语法如下所示:

(arg1, arg2) => {...function body...}

使用箭头功能,我们可以删除该that = this位并将其调用更改changeGearsAsync为以下内容。

  // the anonymous function is now an arrow function
this.changeGearAsync(shiftObject, (err, newIndex)=>{
  if (err) {
    console.log("No Change");
  } else {
    // we reference this instead of that
    this[shiftIndexName] = newIndex;
  }
});

有前途的东西

开发为处理异步代码的库的承诺,使您可以更轻松地推断代码成功或失败的时间。它们还包含内置机制,可将一个呼叫链接到另一个呼叫。最终,竞争库在浏览器中被标准化为Promise对象。让我们变身bike一个多 的时间。 

Bike.prototype.changeGearAsync = function(shiftObject){
  return new Promise(
    (resolve, reject) => {
      let newIndex = shiftObject.currentIndex + shiftObject.changeBy;
      if (newIndex < 0 || newIndex > shiftObject.maxIndex) {
        reject("New Index is Invalid: " + newIndex);
      } else {
        resolve(newIndex);
      }
    }
  );
};

首先,更新后的changeGearAsync函数接收我们传递的数据,并返回一个新的Promise对象。我们传入一个参数:一个回调函数,它本身有两个函数传递给它,resolvereject。 

在实现promise时,您可以执行回调函数中所需的任何计算,请求等。完成后,如果一切顺利,您将resolve使用要传递回的数据进行调用。如果遇到问题,您可以通过调用reject任何相关错误作为参数来向函数调用者发出信号。 

让我们看看我们现在如何使用它。 

// invoke async function that returns a promise
this.changeGearAsync(shiftObject)
  .then(
    (newIndex) => {
      this[shiftIndexName] = newIndex;
      console.log(this.calculateGearRatio());
    }
  )
  .catch(
    (err) => {console.log("Error: " + err);}
  );

现在,我们有了一些更容易推理的东西。如果changeGearAsync可行,则将then函数与传递给其参数的函数一起调用。如果没有,catch则被调用。 

如果回调函数本身返回的实例Promise,那么事情就会变得令人兴奋。您可以简单地将这两个Promise函数链接在一起。举例来说,如果我们想改变 这两个前后齿轮。 

Bike.prototype.changeBothGears = function(frontChange, rearChange) {
  let shiftFront = {
    currentIndex: this.frontGearIndex,
    maxIndex: this.transmission.frontGearTeeth.length - 1,
    changeBy: frontChange
  };
  let shiftRear = {
    currentIndex: this.rearGearIndex,
    maxIndex: this.transmission.rearGearTeeth.length - 1,
    changeBy: rearChange
  };
  this.changeGearAsync(shiftFront)
    .then(
      (newIndex) => {
        this.frontGearIndex = newIndex;
        console.log(this.calculateGearRatio());
        return this.changeGearAsync(shiftRear);
      }
    )
    .then(
      (newIndex) => {
        this.rearGearIndex = newIndex;
        console.log(this.calculateGearRatio());
      }
    )
    .catch(
      (err) => {console.log("Error: " + err);}
    );
  };

changeBothGears上面的函数向我们显示了对的两个调用的链接changeGearsAsync,每个调用都与对应于前齿轮或后齿轮的对象相关。第一次调用之后,我们在第一次调用结束时再次调用它thenthen可以在上面加上另一个。从根本上讲,每当一个then承诺返回时,都可以跟随另一个承诺,then直到我们用尽了所有链式动作。  

异步/等待

在签字之前,值得一提的是异步武器库中的一些新功能:asyncawait运算符。这些基于promise,使它们的使用方式与同步JavaScript非常相似。

闪电Web组件和异步JavaScript

利用Lightning Web Components,开发人员可以同时使用基于承诺的异步功能和异步/等待功能。现在唯一的建议是,为开发人员在Internet Explorer 11上为用户创建功能,asyncawait 在该浏览器中未实现。不过,请放心,您的代码可以使用。但是,当在IE11中使用async / await运行任何内容时,LWC会自动使用polyfill,以便语法正确运行。因此,如果经常使用,IE11的性能可能会有所下降。 

与Salesforce互动

在Lightning Web Components中实现了使用异步JavaScript的若干功能。其中大多数围绕与服务器的交互。一个示例是可以在Lightning Web组件中强制调用Apex方法的方法。 

考虑以下Apex类和方法: 

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        if (String.isBlank(searchKey)) {
            return new List<Contact>();
        }
    String key = '%' + searchKey + '%';
    return [SELECT Id, Name, Title, Phone, Email, Picture__c FROM Contact WHERE Name LIKE :key AND Picture__c != null LIMIT 10];
    }
...

Lightning Web Components使用基于Promise的API来展示此方法。您可以这样调用它: 

import { LightningElement, track } from 'lwc';
import findContacts from '@salesforce/apex/ContactController.findContacts';
export default class ApexImperativeMethodWithParams extends LightningElement {
    @track searchKey = '';
    @track contacts;
    @track error;
    handleSearch() {
        findContacts({ searchKey: this.searchKey })
        .then(result => {
            this.contacts = result;
            this.error = undefined;
        })
        .catch(error => {
            this.error = error;
            this.contacts = undefined;
        });
    }
}
注意

注意

这里有一些超出此模块范围的内容,最值得注意的是ES6中JavaScript模块的使用。要了解这些功能,这是考虑进入本教程“现代JavaScript开发”中的下一个模块的绝佳时机。但是在您这样做之前,请先了解一下此代码。 

当我们称其import findContacts…为标准模块语法时,该组件中将包含另一个模块的功能。我们在findContacts这里将Apex方法展示为同名的JS函数。 

当我们在handleSearch()函数中调用它时,Apex方法的参数作为文字对象传递,然后我们看到了then和catch函数的基于promise的语法。

Salesforce开发人员的JavaScript技能-了解上下文,范围和闭包

学习目标

完成本单元后,您将能够:

  • 确定变量在JavaScript中的范围。
  • 描述如何this根据调用函数的位置进行更改。
  • 使用闭包捕获对函数中变量的引用。

理解任何编程语言的关键在于理解变量的可用性,如何维护状态以及如何访问该状态。 

在JavaScript中,变量的可用性和可见性称为范围。范围由声明变量的位置确定。 

上下文是当前代码执行的状态。通过this指针访问它。 

可变范围

在JavaScript变量使用申报varletconst关键字。在何处调用关键字指示要创建的变量的范围。 

了解这三者之间的区别归结为两个因素:分配可变性和支持非功能块作用域。我们在本模块的第一个单元中介绍了分配可变性。现在该讨论范围了。 

范围的乐趣

声明变量或参数的代码块确定其范围。但var不能识别非功能代码块。这意味着调用var在一个if块或一个环块将变量分配给最近的封闭功能的范围。此功能称为提升。 

使用let或时const,参数或变量的范围始终是在其中声明参数的实际块。有一个经典的思维 练习可以证明这一点。

function countToThree() {
  // i is in the scope of the countToThree function
  for (var i = 0; i < 3; i++){
    console.log(i); // iteration 1: 0
    // iteration 2: 1
    // iteration 3: 2
  }
  console.log(i); // What is this?
}

所述console.log内部输出for回路不足为奇,输出的值i对于每次迭代。可能更令人惊讶的是最终console.log声明,它输出3。您可能已经预料到了错误,因为i在您认为是for循环范围之内声明了该错误。但是随着吊装,i实际上属于countToThree的范围。 

虽然不一定很糟糕,但是如果在代码块中重新声明了变量,则吊装通常会被误解,并可能导致变量泄漏或导致意外覆盖。为了解决这些误解let,将const其添加到语言中以创建具有块级作用域的变量。让我们重新思考一下。

for (let j = 0; j < 3; j++){
  console.log(j); // 0
  // 1
  // 2
}
console.log(j); // error

通过替换letvar,我们现在有了一个仅在for循环上下文中存在的变量。在循环关闭后尝试访问它会给我们一个错误。 

上下文和这个

正如我们探索的那样,JavaScript围绕对象。对象是跟踪状态的地方。调用一个函数时,该函数周围始终有一个对象容器。该对象容器是其上下文,this关键字指向该上下文。因此,在声明函数时不会设置上下文,而是在调用函数时设置上下文。 

因为功能可以在对象之间传递,所以this指向的内容可以更改。 

例如说这个JavaScript 对象。

var obj = {
  aValue: 0,
  increment: function(incrementBy) {
    this.aValue = this.aValue + incrementBy;
  }
}

如果然后访问增量功能,它将按预期工作。

obj.increment(2);
console.log(obj.aValue); // 2

但是,让我们将该函数分配给另一个变量,看看它是如何工作的。 

//assign function to variable
var newIncrement = obj.increment;
//now invoke through the new pointer
newIncrement(2);
console.log(obj.aValue); // still 2 not 4

通过将变量分配给newIncrement,现在可以在其他上下文中执行该功能。具体而言,在这种情况下,在全局范围内。 

注意

注意

Function.apply()Function.call()Function.bind()函数提供的方式来调用函数,同时明确其绑定到不同的对象上下文。

全局对象

当执行JavaScript而没有以开发人员身份编写的任何包含对象时,它将在全局对象中运行。因此,据称在此调用的函数正在全局上下文中运行,这意味着访问this将指向该全局上下文。 

在浏览器中,全局上下文是window对象。您可以通过在浏览器开发人员工具中运行以下命令来轻松测试该功能。 

this === window; // true

在该increment示例中,将increment函数分配给newIncrement变量会将调用它的上下文移动到全局对象。这很容易证明。

console.log(this.aValue); // NaN
console.log(window.aValue); // NaN
console.log(typeof window.aValue); // number

当我们尝试this.aValue使用新的上下文进行分配时,JavaScript对象的可变性开始发挥作用。新的未初始化aValue属性已添加到中this。对未初始化的变量执行数学运算将失败,因此该NaN值也会失败。但是我们可以看到aValue存在于window,的确是一个数字。 

与对象的上下文

在此increment示例中,只要increment使用obj点符号来调用函数,就this指向obj。或者,通常来说,当调用函数作为object.function() 点左侧的事物时,始终是调用该函数的上下文。 

想想这个Bike例子。的Bike构造限定了与几个属性this参考。它还具有分配给其引用该原型的功能this。 

const Bike = function(frontIndex, rearIndex){
  this.frontGearIndex = frontIndex || 0;
  this.rearGearIndex = rearIndex || 0;
  ...
}
...
Bike.prototype.calculateGearRatio = function(){
  let front = this.transmission.frontGearTeeth[this.frontGearIndex],
  rear = this.transmission.rearGearTeeth[this.rearGearIndex];
  if (front && rear) {
    return (front / rear) ;
  } else {
    return 0;
  }
};

然后Bike,我们使用new关键字进行调用。 

const bike = new Bike(1,2);
console.log(bike.frontGearIndex); // 1
console.log(bike.rearGearIndex); // 2

看起来我们正在Bike全局上下文中调用构造函数。但是,new关键字将上下文(和this指针)移动到分配左侧的新对象。 

当我们调用任何函数时,它们现在是bike对象的成员,因此它们将其用作包含上下文。 

let gearRatio = bike.calculateGearRatio();
console.log(gearRatio); // 3

以错误的方式调用构造函数很容易。在这里事情会崩溃。

const badBike = Bike(1,2);
console.log(badBike.frontGearIndex); // error
console.log(window.frontGearIndex); // 1

当您忘记使用时newBike将像其他任何函数一样调用,并且thiswindow到新创建的对象的关键转换失败。frontGearIndex 引入对象可变性,并添加属性window。 

注意

classJavaScript中的语法会强制您使用new关键字调用构造函数,因此您不会误导上下文。

关闭

声明函数时,它将保留对其中声明的任何变量或参数的引用,以及对其所包含范围内引用的任何变量的引用。它的变量和自变量以及其包含范围中的局部变量和自变量的这种组合称为闭包。 

考虑此 函数及其返回的函数。 

const greetingMaker = function(greeting){
  return function(whoGreeting){
    return greeting + ", " + whoGreeting + "!";
  }
}
const greetingHello = greetingMaker("Hello");
const greetingBonjour = greetingMaker("Bonjour");
const greetingCiao = greetingMaker("Ciao");
console.log(greetingHello("Gemma")); // Hello, Gemma!
console.log(greetingBonjour("Fabien")); // Bonjour, Fabien!
console.log(greetingCiao("Emanuela")); // Ciao, Emanuela!

greetingMaker被调用时,我们通常可以想象它的greeting参数仅在被调用的整个生命周期内持续存在。 

但是返回的函数会greetinggreetingMaker的范围内保留对参数的引用。这样,最后通过greetingHelloBonjour/ 调用它时Ciao,它仍然可以访问。 

掌握闭包也是理解和使用该语言的重要组成部分。

Salesforce开发人员的JavaScript技能-对事件和功能采取行动

学习目标

完成本单元后,您将能够:

  • 定义功能
  • 区分函数声明和函数表达式
  • 调用功能
  • 传递和分配功能
  • 描述闪电网络组件中功能和事件的使用

当要使事情发生在JavaScript中时,一切都与事件和函数有关。

还记得我们的运行时模型吗?让我们扩展一下。 

使用浏览器中的JavaScript,事件无处不在。DOM的某些部分发出的事件与该DOM对象的行为相对应。按钮具有单击事件,输入和选择控件具有更改事件,并且可见DOM的几乎每个部分实际上都具有鼠标光标与其交互(例如,经过它)的事件。窗口对象甚至具有用于处理设备事件的事件处理程序(例如检测移动设备的运动)。 

为了使某件事发生在网页中,将函数分配给这些事件作为事件处理程序。 

重申一下,DOM事件和与浏览器环境相关的其他事件实际上并不是核心JavaScript语言的一部分,而是它们是在浏览器中为JavaScript实现的API。 

发出事件时,将在引擎中创建一条消息。这些消息被放置在我们前面讨论的事件队列中。 

队列中的消息和堆栈中的帧。

释放堆栈后,将调用事件处理程序。这将在调用堆栈上创建所谓的框架。每次一个函数调用另一个函数时,都会将一个新框架添加到堆栈中,完成后会从堆栈中弹出,直到最后弹出实际事件处理程序的框架,堆栈为空,然后我们重新开始。 

定义和分配功能

在JavaScript中,函数本质上是特殊的对象。作为对象,它们是JavaScript的一流成员。可以将它们分配为变量的值,将其作为参数传递给其他函数,然后从函数中返回。 

函数生命周期有两个基本阶段:定义和调用。  

声明函数时,其定义将加载到内存中。然后以变量名,参数名或对象属性的形式将指针分配给它。但是,有几种不同的语法可以做到这一点也就不足为奇了。

功能声明

声明是使用function关键字创建函数的语句。实际上,当我们查看对象构造函数时,我们已经看到了它。该构造函数是一个函数。但是构造函数有点特殊,所以让我们退一步,谈论普通的旧函数,看看它们是如何工作的: 

// declare function
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// call function
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

在此代码示例中,函数后跟函数名称,并在括号中包含参数。 

这可以正常工作,但是有一些隐式事件正在发生。首先,函数名称成为变量名称。它还将变量隐式分配给封闭的上下文。最后,您可以在声明此函数之前调用它,例如在声明下面calculateGearRatio的那一行调用下面。 

// call function
let gearRatio = calculateGearRatio(42, 30);
// function is declared after the line it is called
// this is allowed in function declaration
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4

函数表达式

函数表达式更明确地实现了与声明相同的功能。 

const calculateGearRatio = function(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// the rest works the same
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

在这种情况下,我们有一个明确分配的变量。由于我们已经命名了指针,因此可以将函数名称放在function关键字之后。这里唯一的陷阱是必须在调用函数之前声明该函数。 

值得注意的是,函数表达式还用于将函数分配为对象的成员。回想一下我们将changeGear功能分配给Bike.prototype什么时候?  

Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

返回函数

由于函数是一类对象,因此声明函数的另一种方法是函数返回另一个函数。这种模式通常称为工厂功能。 

// when invoked, this function will assign a function
function gearFactory(){
  return function(driverGear, drivenGear){
    return (driverGear / drivenGear);
  }
}
// calculateGearRatio can now be invoked as a function
const calculateGearRatio = gearFactory();
// and all the rest

虽然上面的例子很简单,但是它是有效的。工厂函数对于一次性可重用函数很有用,尤其是在闭包中捕获变量引用时。我们将在以后的单元中讨论闭包。 

匿名函数

JavaScript中有许多API,要求您传递一个函数才能使它们起作用。例如,假设您有一个数组,并且想要创建一个从该数组的值派生的新数组。在这种情况下,您可能会使用该Array.map 功能。

let myArray = [1, 5, 11, 17];
let newArray = myArray.map( function(item){ return item / 2 } );
console.log(newArray); // [0.5, 2.5, 5.5, 8.5]

在此代码段中,myArray.map采用一个参数:该函数对中的每个项目执行一次myArray。 

此功能永远不会重用。它被声明为传递给函数的参数(没有名称…因此“匿名”),并在map函数实现的内部执行。匿名函数( 在某些语言中也称为lambda)在JavaScript中很常见。 

功能调用

声明函数后,您可能想遍历调用它。调用函数时,会发生一些事情。 

请记住,第一件事是将新框架推入堆栈。然后在内存中创建一个包含其变量和参数的对象。this然后,将指针与其他一些特殊对象绑定到该对象。然后,将传递给参数的值赋值,最后运行时开始执行函数体内的语句。 

的绑定this有一个重要的例外,我们将在异步JavaScript的单元中重新进行讨论。 

调用与分配

使用函数时,与JavaScript新手混淆的一个潜在原因是您是分配/传递函数还是调用它。这一切都取决于您是否使用()。 

考虑我们的自行车对象的calculateGearRatio功能。 

let bike = {
  ...,
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
    rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  ...
}

现在考虑这两种访问calculateGearRatio函数的不同方式。  

// invoke function and assign value to ratioResult
let ratioResult = bike.calculateGearRatio();
// assign calculateGearRatio function to a new pointer
const ratioFunction = bike.calculateGearRatio;

在第一种情况下,calculateGearRatio将调用,并将从函数返回的结果分配给该ratioResult变量(在这种情况下作为原始值)。另一方面ratioFunction ,仅已分配或指向该calculateGearRatio功能。然后,您可以掉头并以方式调用它ratioFunction。 

有理由将一个函数分配给另一个指针,特别是作为另一个函数的参数,就像该Array.map()函数一样。但是使用this参考的任何功能都有可能会被破坏,因为它this可以在不同的时间指向不同的事物。以后再说。 

充当事件处理程序

如果您希望某个函数作为事件的结果而触发,则需要将其连接到该事件。这样做会使该函数成为事件处理程序。函数定义需要包含一个参数:指向触发它的事件的指针。 

var handleClick = function(event) {
}

每个事件都具有告诉您处理该事件所需了解的属性。例如,click您可以检测到有关点击的数据(事件类型,触发该事件的元素,点击的坐标等)。  

var handleClick = function(event) {
  console.log(event.type);  // click
  console.log(event.currentTarget); // the thing you clicked
  console.log(event.screenX); // screen X coordinate
  console.log(event.screenY); // screen Y coordinate
}

通过DOM API分配事件处理程序

在简单的网页中,您有时可能会在HTML中看到显式分配的事件处理程序。 

<button onclick="handleClick(event)">
  Click to Go
</button>

但是,现代Web应用程序很少在HTML中使用事件绑定。相反,首选DOM API,特别是JavaScript Element.addEventListener()函数。 

首先,您需要对HTML元素的引用。下面,我们在id按钮中添加了一个属性,并删除了onclick属性。 

<button id=”clicker”>

现在我们进入DOM,获取对按钮的引用,并通过将事件侦听器handleClick作为值(注意,不带括号)传递给事件侦听器。 

let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

使用DOM API可使开发人员灵活地使UI高度交互并响应用户的操作。如果需要关闭功能,开发人员还可以删除事件侦听器。 

button.removeEventListener("click", handleClick);

您还将看到匿名函数添加为事件侦听器。 

button.addEventListener("click", function(event){
  //...anonymous function body...
});

请记住,不能使用来删除匿名函数removeEventListener,因为没有指针可以传入以识别该函数。 

Lightning Web Components中的事件和功能

Lightning Web组件的关键代码工件是JavaScript模块,HTML模板和CSS文件。其中唯一需要的是JavaScript模块(注意,.xml还需要的文件不是代码,而是有关组件的元数据)。 

Lightning Web组件中的功能通常以方法的形式出现,这些方法是该组件的JavaScript模块导出的类的成员。这些函数可以是事件处理程序,也可以是从下游向下游调用的其他函数。 

HTML模板以类似于静态HTML绑定的方式引用处理程序函数,但实际上是不同的。由于模板已编译为JavaScript工件,因此看起来静态的绑定实际上只是框架addEventListener在组件生命周期中某个时间调用的语法约定。 

模板中的此标记显示事件处理程序绑定。

<lightning-input onchange={handleChange} label="Input Text" value={text}>
</lightning-input>

这是事件处理程序。 

    handleChange(event){
        this.text = event.target.value;
    }
注意

注意

Lightning Web Components中有许多事件的更高级的功能。要探索这些功能,请完成Lightning Web Components基础知识模块,或深入研究其中一个示例 应用程序。

Salesforce开发人员的JavaScript技能-对事件和功能采取行动

学习目标

完成本单元后,您将能够:

  • 定义功能
  • 区分函数声明和函数表达式
  • 调用功能
  • 传递和分配功能
  • 描述闪电网络组件中功能和事件的使用

当要使事情发生在JavaScript中时,一切都与事件和函数有关。

还记得我们的运行时模型吗?让我们扩展一下。 

使用浏览器中的JavaScript,事件无处不在。DOM的某些部分发出的事件与该DOM对象的行为相对应。按钮具有单击事件,输入和选择控件具有更改事件,并且可见DOM的几乎每个部分实际上都具有鼠标光标与其交互(例如,经过它)的事件。窗口对象甚至具有用于处理设备事件的事件处理程序(例如检测移动设备的运动)。 

为了使某件事发生在网页中,将函数分配给这些事件作为事件处理程序。 

重申一下,DOM事件和与浏览器环境相关的其他事件实际上并不是核心JavaScript语言的一部分,而是它们是在浏览器中为JavaScript实现的API。 

发出事件时,将在引擎中创建一条消息。这些消息被放置在我们前面讨论的事件队列中。 

队列中的消息和堆栈中的帧。

释放堆栈后,将调用事件处理程序。这将在调用堆栈上创建所谓的框架。每次一个函数调用另一个函数时,都会将一个新框架添加到堆栈中,完成后会从堆栈中弹出,直到最后弹出实际事件处理程序的框架,堆栈为空,然后我们重新开始。 

定义和分配功能

在JavaScript中,函数本质上是特殊的对象。作为对象,它们是JavaScript的一流成员。可以将它们分配为变量的值,将其作为参数传递给其他函数,然后从函数中返回。 

函数生命周期有两个基本阶段:定义和调用。  

声明函数时,其定义将加载到内存中。然后以变量名,参数名或对象属性的形式将指针分配给它。但是,有几种不同的语法可以做到这一点也就不足为奇了。

功能声明

声明是使用function关键字创建函数的语句。实际上,当我们查看对象构造函数时,我们已经看到了它。该构造函数是一个函数。但是构造函数有点特殊,所以让我们退一步,谈论普通的旧函数,看看它们是如何工作的: 

// declare function
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// call function
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

在此代码示例中,函数后跟函数名称,并在括号中包含参数。 

这可以正常工作,但是有一些隐式事件正在发生。首先,函数名称成为变量名称。它还将变量隐式分配给封闭的上下文。最后,您可以在声明此函数之前调用它,例如在声明下面calculateGearRatio的那一行调用下面。 

// call function
let gearRatio = calculateGearRatio(42, 30);
// function is declared after the line it is called
// this is allowed in function declaration
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4

函数表达式

函数表达式更明确地实现了与声明相同的功能。 

const calculateGearRatio = function(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// the rest works the same
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

在这种情况下,我们有一个明确分配的变量。由于我们已经命名了指针,因此可以将函数名称放在function关键字之后。这里唯一的陷阱是必须在调用函数之前声明该函数。 

值得注意的是,函数表达式还用于将函数分配为对象的成员。回想一下我们将changeGear功能分配给Bike.prototype什么时候?  

Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

返回函数

由于函数是一类对象,因此声明函数的另一种方法是函数返回另一个函数。这种模式通常称为工厂功能。 

// when invoked, this function will assign a function
function gearFactory(){
  return function(driverGear, drivenGear){
    return (driverGear / drivenGear);
  }
}
// calculateGearRatio can now be invoked as a function
const calculateGearRatio = gearFactory();
// and all the rest

虽然上面的例子很简单,但是它是有效的。工厂函数对于一次性可重用函数很有用,尤其是在闭包中捕获变量引用时。我们将在以后的单元中讨论闭包。 

匿名函数

JavaScript中有许多API,要求您传递一个函数才能使它们起作用。例如,假设您有一个数组,并且想要创建一个从该数组的值派生的新数组。在这种情况下,您可能会使用该Array.map 功能。

let myArray = [1, 5, 11, 17];
let newArray = myArray.map( function(item){ return item / 2 } );
console.log(newArray); // [0.5, 2.5, 5.5, 8.5]

在此代码段中,myArray.map采用一个参数:该函数对中的每个项目执行一次myArray。 

此功能永远不会重用。它被声明为传递给函数的参数(没有名称…因此“匿名”),并在map函数实现的内部执行。匿名函数( 在某些语言中也称为lambda)在JavaScript中很常见。 

功能调用

声明函数后,您可能想遍历调用它。调用函数时,会发生一些事情。 

请记住,第一件事是将新框架推入堆栈。然后在内存中创建一个包含其变量和参数的对象。this然后,将指针与其他一些特殊对象绑定到该对象。然后,将传递给参数的值赋值,最后运行时开始执行函数体内的语句。 

的绑定this有一个重要的例外,我们将在异步JavaScript的单元中重新进行讨论。 

调用与分配

使用函数时,与JavaScript新手混淆的一个潜在原因是您是分配/传递函数还是调用它。这一切都取决于您是否使用()。 

考虑我们的自行车对象的calculateGearRatio功能。 

let bike = {
  ...,
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
    rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  ...
}

现在考虑这两种访问calculateGearRatio函数的不同方式。  

// invoke function and assign value to ratioResult
let ratioResult = bike.calculateGearRatio();
// assign calculateGearRatio function to a new pointer
const ratioFunction = bike.calculateGearRatio;

在第一种情况下,calculateGearRatio将调用,并将从函数返回的结果分配给该ratioResult变量(在这种情况下作为原始值)。另一方面ratioFunction ,仅已分配或指向该calculateGearRatio功能。然后,您可以掉头并以方式调用它ratioFunction。 

有理由将一个函数分配给另一个指针,特别是作为另一个函数的参数,就像该Array.map()函数一样。但是使用this参考的任何功能都有可能会被破坏,因为它this可以在不同的时间指向不同的事物。以后再说。 

充当事件处理程序

如果您希望某个函数作为事件的结果而触发,则需要将其连接到该事件。这样做会使该函数成为事件处理程序。函数定义需要包含一个参数:指向触发它的事件的指针。 

var handleClick = function(event) {
}

每个事件都具有告诉您处理该事件所需了解的属性。例如,click您可以检测到有关点击的数据(事件类型,触发该事件的元素,点击的坐标等)。  

var handleClick = function(event) {
  console.log(event.type);  // click
  console.log(event.currentTarget); // the thing you clicked
  console.log(event.screenX); // screen X coordinate
  console.log(event.screenY); // screen Y coordinate
}

通过DOM API分配事件处理程序

在简单的网页中,您有时可能会在HTML中看到显式分配的事件处理程序。 

<button onclick="handleClick(event)">
  Click to Go
</button>

但是,现代Web应用程序很少在HTML中使用事件绑定。相反,首选DOM API,特别是JavaScript Element.addEventListener()函数。 

首先,您需要对HTML元素的引用。下面,我们在id按钮中添加了一个属性,并删除了onclick属性。 

<button id=”clicker”>

现在我们进入DOM,获取对按钮的引用,并通过将事件侦听器handleClick作为值(注意,不带括号)传递给事件侦听器。 

let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

使用DOM API可使开发人员灵活地使UI高度交互并响应用户的操作。如果需要关闭功能,开发人员还可以删除事件侦听器。 

button.removeEventListener("click", handleClick);

您还将看到匿名函数添加为事件侦听器。 

button.addEventListener("click", function(event){
  //...anonymous function body...
});

请记住,不能使用来删除匿名函数removeEventListener,因为没有指针可以传入以识别该函数。 

Lightning Web Components中的事件和功能

Lightning Web组件的关键代码工件是JavaScript模块,HTML模板和CSS文件。其中唯一需要的是JavaScript模块(注意,.xml还需要的文件不是代码,而是有关组件的元数据)。 

Lightning Web组件中的功能通常以方法的形式出现,这些方法是该组件的JavaScript模块导出的类的成员。这些函数可以是事件处理程序,也可以是从下游向下游调用的其他函数。 

HTML模板以类似于静态HTML绑定的方式引用处理程序函数,但实际上是不同的。由于模板已编译为JavaScript工件,因此看起来静态的绑定实际上只是框架addEventListener在组件生命周期中某个时间调用的语法约定。 

模板中的此标记显示事件处理程序绑定。

<lightning-input onchange={handleChange} label="Input Text" value={text}>
</lightning-input>

这是事件处理程序。 

    handleChange(event){
        this.text = event.target.value;
    }
注意

注意

Lightning Web Components中有许多事件的更高级的功能。要探索这些功能,请完成Lightning Web Components基础知识模块,或深入研究其中一个示例 应用程序。

Salesforce开发人员的JavaScript技能-处理对象,类和原型继承

学习目标

完成本单元后,您将能够:

  • 使用对象文字符号和构造函数创建对象。
  • 将属性和功能分配给对象。
  • 确定原型在JavaScript对象继承中的作用。
  • 描述JavaScript类语法。
  • 描述闪电网络组件中继承的作用和对象文字表示法。

有很多方法可以将JavaScript描述为一种语言。无论您选择哪种定义,每个人都可以同意对象的JavaScript概念的重要性。您越了解JavaScript对象以及它们如何工作,就越能编写有效的JavaScript。

在开始之前,请先对对象进行一些说明。 

  • 对象没有Apex,Java或C#开发人员可能会想到的类。
  • 每个对象都继承自另一个对象。
  • 对象是可变的。
  • 对象在创建时会获得自己的变量上下文。

创建对象

从语法上讲,有几种方法可以用JavaScript创建对象。但是,无论您如何创建对象,它实际上都是在抽象一个称为的底层API Object.create()。  

在某些情况下,有充分的理由Object.create()直接使用,但我们不在此介绍。相反,让我们看一下创建对象的更常见方法。 

对象文字表示法

第一个对象创建语法称为对象文字表示法。这是一次同时声明和分配对象的简单声明方式。然后立即将对象分配为同一语句的一部分。 

const bike = {
  gears: 10,
  currentGear: 3,
  changeGear: function(direction, changeBy) {
    if (direction === 'up') {
      this.currentGear += changeBy;
    } else {
      this.currentGear -= changeBy;
    }
  }
}
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

对象文字符号本质上是声明性的。bike此 示例中的对象具有三个成员:gearscurrentGear属性以及changeGear函数。若要在创建对象后引用这些成员,请使用点表示法。 

注意

您可能会注意到JSON和对象文字表示法彼此相似,但是它们并不相同。JSON是一种数据交换格式。对象文字符号是一种编程语法。但是,JSON规范基于对象文字表示法,因此很容易将两者混为一谈。

文字对象非常适合一次性对象。但是,如果要创建两个或更多个相同类型的对象,则它们是不实际的。为此,您需要可重复的逻辑来创建新对象。 

构造函数的新对象

创建对象的另一种方法是使用构造函数。构造函数是一种函数,其中包含用于在创建和分配对象时建立对象属性的指令。与对象文字相比,这具有优势,因为您可以创建具有相同属性的对象的许多实例。 

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction,changeBy){
  if(direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}
const bike = new Bike(10, 3);
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

在此 示例中, Bike是定义对象的普通JavaScript函数。我们遵循JavaScript约定并大写第一个单词来表示此函数是构造函数。该new关键字是至关重要的。如果不使用new,则this指针将不会指向您期望的对象,并且会导致意外的行为。this当我们在以后的单元中介绍上下文时,我们会重新进行讨论。 

请注意,该changeGear函数的分配是通过使用来完成的prototype。这样可以确保函数定义一次,并由该构造函数创建的所有实例共享。我们将在本单元后面介绍原型的使用和继承。 

在语法方面,对象文字表示法和构造函数完全不同。但是在每种情况下,您仍然最终会在内存中创建一个新对象,并使用变量bike作为指向该对象的指针。使用构造函数,您可以使许多Bike具有相同属性和功能的对象。

为对象分配属性和功能

如果从bike上面的示例推断出对象中有两种可能的成员类型-属性和函数-您将是正确的。  

属性具有三种基本形状。

  • 原语
  • 对象
  • 数组

有六种基本类型在JavaScript:字符串,数字,布尔nullundefined和符号。如果变量是原始类型,则在分配时按值传递。也就是说,每次分配基元时,都会复制值并将其分配给新变量。 

几乎所有不是JavaScript中原始语言的东西都是对象。在对象文字表示法中,对象属性用大括号表示。 

数组本身也被实现为JavaScript中的对象。可以使用Array()构造函数或以方括号表示的文字符号来创建数组。 

函数在此模块中有其自己的单元,因此我们在这里不再讨论它们,但是基于以上内容,让我们通过bike使用对象文字符号定义更复杂的对象的方法。

const bike = {
  frontGearIndex: 0,
  rearGearIndex: 0,
  transmission: {
    frontGearTeeth: [30,45],
    rearGearTeeth: [11,13,15,17,19,21,24,28,32,36]
  },
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
        rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  changeGear: function(frontOrRear, newValue) {
    if (frontOrRear === 'front') {
      this.frontGearIndex = newValue;
    } else {
      this.rearGearIndex = newValue;
    }
  }
};

通过括号语法引用属性

引用对象成员通常是使用点符号来完成的。例如,在前面的示例中,我们按如下方式引用对象的属性和功能。

bike.frontGearIndex
bike.transmission.frontGearTeeth
bike.calculateGearRatio()

用点表示法,对属性名称有严格的规定。但是,JavaScript还允许使用另一种称为括号符号的语法。上面的成员将在括号中引用如下。

bike["frontGearIndex"]
bike["transmission"]["frontGearTeeth"]
bike["calculateGearRatio"]()

虽然需要更多的输入,但方括号表示法有两个好处。您可以为属性或函数命名任何所需的名称,并且由于它是字符串,因此可以通过变量传递属性或函数名称并进行调用。

让我们通过重新构想changeGear功能来了解这一点。现在,我们使用四个功能来定义前后齿轮的上下变速。在changeGear函数中,我们根据String参数构造要调用的函数名称,然后调用它。 

changeGear: function(frontOrRear, upOrDown) {
  let shiftFunction = frontOrRear + upOrDown;
  this[shiftFunction]();
},
frontUp: function(){
  this.frontGearIndex += 1;
},
frontDown: function(){
  this.frontGearIndex -= 1;
},
rearUp: function(){
  this.rearGearIndex += 1;
},
rearDown: function(){
  this.rearGearIndex -= 1;
}

将它们添加到我们的自行车对象中,我们可以在工作中看到它们。 

console.log(bike.calculateGearRatio()); // 2.727272727
//Calls the frontUp() function
bike.changeGear("front", "Up");
console.log(bike.calculateGearRatio()); // 4.090909091
//calls the rearUp() function
bike.changeGear("rear", "Up");
console.log(bike.calculateGearRatio()); // 3.461538461

对象可变性

除了用于定义对象的不同语法外,JavaScript对象还有另一个关键原理:可变性。 

JavaScript中的对象是可变的,这意味着如果您想要修改对象的形状,则可以。 

让我们来看看bike我们创建的对象。例如,我们可以添加一个新的属性或函数。 

bike.isTandem = true;
bike.popAWheelie = function() {
…
};

即使您可能无法访问最初定义对象的代码,也可以在将对象保存在内存中后修改其形状。不过,重要的一点是,只有一个对象实例发生了变化。让我们回顾一下我们的Bike 构造函数:

const bike1 = new Bike();
const bike2 = new Bike();
bike1.isTandem = true;
console.log(bike1.isTandem); // true
console.log(bike2.isTandem); // undefined

如果您希望多个对象共享相同的属性或方法,则有一个继承模型。让我们看看。 

对象与继承

尽管没有古典语言定义的类,JavaScript仍具有一个继承模型,称为原型继承。 

实际上,原型是另一个对象。它位于内存中,并定义其他对象共享相同原型时所继承的属性或函数。 

传统上,JavaScript对象通过共享相同的构造函数来共享相同的原型。记住Bike 构造函数。我们将changeGear函数分配给prototype。 

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

这样,从中创建的每个对象都会Bike继承该changeGear函数。 

您还可以使用原型实现多级继承。它被称为原型链。使用构造函数来实现原型链非常复杂,并且需要大量样板代码。它也超出了本模块的范围。您需要知道的是,为了解决原型链的复杂性,ECMA为实现继承的更直接的语法制定了一个标准:class语法。 

类和JavaScript

如果您阅读“类”一词并感到温暖而模糊,以为您正在寻找可以创建基于类的真正继承的东西,请准备感到失望。尽管如此,classJavaScript中的关键字还是一个不错的语法糖,可以使用构造函数来解决原型继承的复杂性。在幕后,引擎仍在使用,Object.create并且没有类(在面向对象的意义上),只是内存中的原型对象是实际的继承源。 

好消息是,它确实从Java或C#中读取了很多类似的代码,并且需要考虑一些JavaScript特定的内容。 

注意

当由Lightning Web Components使用时,包括类语法的多级原型链超出了此模块。有关使用JavaScript对象的较新功能的更多详细信息,请参见本单元末尾的资源链接。我们强烈建议您在本教程中使用其他 模块,即Modern JavaScript Development,该模块在Classes中有一个完整的单元

尽管我们在这里不会深入探讨JavaScript类的细节,但出于学术目的,很高兴看到使用类语法实现的bike对象的版本。 

class Bike {
    constructor(gears, startGear){
        this.gears = gears;
        this.currentGear = startGear;
    }
    changeGear(direction, changeBy) {
        if (direction === 'up') {
            this.currentGear += changeBy;
        } else {
            this.currentGear -= changeBy;
        }
    }
}
const bike = new Bike(10, 5);
console.log(bike.currentGear); // 5
bike.changeGear('up', 2);
console.log(bike.currentGear); // 7

如您所见,语法看起来更像Java或Apex中的类。明显的区别是,构造函数始终为命名constructor。一个重要的功能是,函数和属性自动属于原型链,而不必直接引用Object.prototype。这也简化了创建多级原型继承的过程。 

闪电Web组件和对象

本单元的几个部分与开发Lightning Web Components有关,包括我们讨论的一些语法以及原型链。 

类和闪电Web组件

闪电Web组件利用了JavaScript的许多现代改进,最明显的是使用了类语法。组件通常由JavaScript类定义,该类扩展了另一个名为LightningElement的类。看起来是这样的: 

import { LightningElement } from lwc;
export default class MyComponent extends LightningElement {
    myProperty;
    myFunction() {
        console.log(this.myProperty);
    }
} 

Lightning Web组件的功能在JavaScript类中定义。此示例还使用了一些关于模块(importexport)尚未涉及的语法。

论对象文字

在本模块的某些示例中,为了学习对象的工作方式,我们在对象文字中声明了函数。请注意,这不是现代JavaScript中推荐的做法。对象文字是创建临时数据结构以在JavaScript程序的功能部分之间传递数据的一种好方法,但是您应避免在对象文字中定义函数。 

Salesforce开发人员的JavaScript技能-了解浏览器中的JavaScript

学习目标

完成本单元后,您将能够:

  • 描述JavaScript和浏览器之间的交互。
  • 解释Web API的角色。
  • 使用基本的文档对象模型API。
  • 描述闪电组件抽象DOM的方式。

JavaScript在浏览器中的工作方式

您刚刚了解了一些JavaScript运行时引擎和语言。您还学到了一些编写JavaScript的人都应该知道的东西。在本模块中,我们重点介绍JavaScript与浏览器之间的关系,特别关注您的网页在JavaScript中的外观。 

JavaScript运行时不是浏览器,也不是浏览器JavaScript

因此,JavaScript运行时引擎可以在许多不同的地方运行,但是最常托管在浏览器中。那么JavaScript和浏览器之间有什么区别? 

浏览器的主要工作是充当Web服务器的客户端。它使用几种协议(通常为HTTP / HTTPS)之一通过Internet请求资源。服务器将其中的一些资源传递给服务器后,浏览器需要对其进行处理。至少将HTML和CSS渲染到页面中。当资源包含一些JavaScript时,浏览器将到达JavaScript运行时引擎以解析,评估和执行该代码。 

同样,在执行脚本时,它也可以返回到浏览器以执行诸如修改网页,与本地环境交互或与其他Web资源交互的操作。

具有API的JavaScript引擎

为了使这些交互有效,浏览器会显示API。实际上,人们认为客户端JavaScript的很大一部分实际上就是这些API。 

Web API

标准品

能够与浏览器进行交互的API并不奇怪。就像任何其他编程语言或平台界面API与其在其上运行的环境进行交互一样,您的浏览器也是如此。 

虽然超过一半的浏览器互联网 流量来自Google Chrome浏览器,但大约另外30%来自其他五个浏览器。这强调了Web标准对于这些API的重要性,因此JavaScript可以编写一次并可以在任何地方运行。 

将Web API添加到图片

以前,我们创建了JavaScript运行时的图片。 

  • 请求使用单个线程在堆栈上执行。
  • 一个请求将保留线程,直到该线程执行完该请求的所有同步逻辑为止。
  • 新请求将排队,直到线程准备就绪。
  • 事件循环在下一个排队的请求可用时将其移入堆栈。

现在,我们将Web API添加到JavaScript图片中。这些扩展了核心JavaScript语言。它们显示界面,这些界面执行了许多对于现代Web用户体验至关重要的工作。例如,浏览器API可以: 

  • 与浏览器中呈现的当前页面的结构进行交互(文档对象模型或DOM API)
  • 在不离开当前页面的情况下执行对服务器的异步请求(Fetch API)
  • 与音频,视频和图形进行交互
  • 与显示在浏览器上的设备功能进行交互(地理位置,设备方向,客户端数据存储)

在大多数情况下,正是这些API向队列添加了新请求。 

注意

注意

我们正在避开JavaScript生态系统的重要组成部分:抽象或扩展浏览器功能的第三方API和JavaScript库。这些在野外使用JavaScript方面起着重要作用。 

就我们在​​Salesforce中的目的而​​言,我们涵盖了我们的JavaScript框架:Lightning Component Framework。具体来说,当谈论Lightning Platform上的JavaScript时,此模块介绍了Lightning Web组件开发模型。

举个例子。您在带有表单的网页上有一个注释列表。您填写表单上的某些字段,单击按钮以保存该数据,然后将新项添加到列表中。为了好玩,我们还说您将在本地缓存此新记录,以加快涉及该记录的将来请求。 

这个简单的用例将与以下浏览器API交互

  • 提取API(以保存记录)
  • DOM API(用于将新项目添加到HTML列表中)
  • 客户端数据存储(用于本地缓存数据)

让我们深入了解DOM API,因为它代表了用户在JavaScript中看到的内容。 

文档对象模型

DOM:您的JavaScript页面

当请求页面然后由浏览器接收时,浏览器将解析HTML并创建该页面的描述或模型。此模型用于在浏览器的视口中绘制页面。它也通过DOM浮出水面。 

将DOM视为一棵树。它始于浏览器显示功能的根:窗口。从那里开始,页面被封装在中window.document,页面的主体位于window.document.body。然后,树会扇动到页面上所表示的每一位内容。大多数网页都有一个非常复杂的树,其中许多节点最终以叶节点作为层次结构中最细化的部分结束。 

作为一个API,DOM是巨大的,它使您可以触摸树的每个部分。它还具有许多优化与DOM交互的方法。看一下jsbin.com 上的这个简单 示例。它 包括:

  • 输入字段
  • 添加项目按钮
  • 无序列表

每次单击按钮时,代码都会到达输入字段,读取其值,将其转换为文本节点,创建一个新li元素,将文本节点插入该li节点,最后将新的li-and-text节点粘贴到的ul。 

注意

为了获得更多乐趣,并且如果您想实践到目前为止讨论的一些原理,请查看是否可以修改jsbin以执行以下操作。

  • 检查文本输入中是否有数据
  • 如果不是,请不要在列表中添加新的li
  • 添加新li后,清除文本输入

如果悬念杀死了您,请查看修改后的 代码。

如您所见,仅此简单示例就需要开发人员执行一系列手动步骤。而且它甚至不会在服务器上存储任何数据,也不会以任何其他方式与服务器交互。因此,JavaScript库(reactjs,jQuery)和框架(Angular,vuejs)已成为交互式页面的标准。这样的框架会抽象化并简化DOM交互,并且通常会自动对缺少的功能应用polyfill。闪电组件框架没有什么不同。 

使用Shadow DOM封装

DOM API丰富而灵活。使用相对简单的JavaScript,很容易对UI调用的外观,行为和动作进行更改。 

但是有一个陷阱。DOM模型使得难以封装UI部分并保护它们免受意外(或有目的和恶意)更改的影响。 

因此,开发了Shadow DOM标准。Shadow DOM在UI功能的特定部分周围创建边界。该边界可防止父母更改孩子的元素或CSS。它还会强制跨边界传播的所有事件重新确定其目标范围,从而阻止父级跨越阴影DOM边界。

Lightning组件框架:Aura组件和Lightning Web组件

从Spring ’19版本(API版本45.0)开始,您可以使用两个编程模型来构建Lightning组件:Lightning Web组件模型和原始的Aura组件模型。闪电Web组件是使用HTML和现代JavaScript构建的自定义HTML元素。闪电Web组件和Aura组件可以在页面上共存和互操作。此内容仅涵盖Lightning Web Components。

闪电Web组件和DOM

注意

从现在开始,每个单元都有一个相似的部分,并带有一些示例,以说明与Lightning Web Components的关联。如果您想应用所学内容,现在是开始在Trailhead上使用Lightning Web Components Basics 模块的好时机 。

自动DOM更新

Lightning组件的基本价值在于构建由Salesforce数据驱动的自定义UI。这与DOM操作有关,因为DOM本身是由Salesforce数据驱动的。 

该组件以最简单的形式演示了该原理。

具有数据驱动UI的简单组件,显示的文本为“此文本来自JS道具。

这是此组件的代码。

// JavaScript Module: demo.js
import { LightningElement } from 'lwc';
export default class Demo extends LightningElement {
    text = 'This text came from a JS prop';
}
<!-- HTML Template: demo.html -->
<template>
    <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <lightning-formatted-text value={text}></lightning-formatted-text>
            </p>
        </div>
    </lightning-card>
</template>

首先介绍一下上下文:每个Lightning Web组件都需要一个JavaScript模块。如果组件包含要呈现给用户的任何元素,则也需要HTML模板。大多数组件在捆绑中都包含一个JavaScript和一个HTML文件。 

Lightning Web Components使将数据呈现到UI变得毫不费力。例如,上面的代码使用JavaScript属性text存储硬编码的字符串。在<lightning-formatted-text>标记中,value 属性的text属性绑定到JavaScript text属性,从而在UI中呈现数据。 

但是,如果您希望用户交互时更改UI,该怎么办?我们可以扩展这个例子来做到这一点。

这是代码。

// JavaScript Module demo2.js
import { LightningElement, track } from 'lwc';
export default class Demo2 extends LightningElement {
    @track text = '';
    handleChange(event){
        this.text = event.target.value;
     }
}
<!-- HTML Template demo2.html -->
<template>
    <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <lightning-formatted-text value={text}></lightning-formatted-text>
            </p>
            <p>
                <lightning-input onchange={handleChange} label="Input Text" value={text}></lightning-input>
            </p>
        </div>
    </lightning-card>
</template>

如果text再次查看该属性,则会看到我们添加了一些新行为。通过使用@track,我们利用了称为装饰器的相对较新的JavaScript功能。此装饰器将功能附加到属性,以确保在其值更改时重新呈现组件的DOM。  

在模板中,我们添加了一个<lightning-input>。尽管绑定text属性是显示数据的关键,但请注意该onchange属性。handleChange现在已由change事件触发已添加到JavaScript模块的功能。用户对值的每次修改都会更新的值text。文本更新时,DOM也更新。  

尽管这是一个人为的示例,但它说明了Lightning Web Components如何显示使隐式DOM更改易于实现为开发人员的功能。

显式DOM操作

在构建Lightning Web组件时,手动DOM操作不应该是您的首要策略,但是更复杂的组件可能需要您这样做。这是一个更高级的主题,但是在签署DOM之前值得先了解一下它的工作原理。 

有条件的渲染

编写Lightning Web组件时,可以显式定义UI的部分,这些部分仅在满足某些条件时才呈现。您可以通过在嵌套 标记中添加if:trueor if:false指令来实现template。 

//conditionalButton.js
import { LightningElement, track } from 'lwc';
export default class ConditionalButton extends LightningElement {
    @track show = true;
    handleClick(){
        this.show = !this.show;
    }
}
//conditionalButton.html
<template>
    <lightning-card title="Conditional Rendering with Button" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <template if:true={show}>
                    Peek-a-boo!
                </template>
                <template if:false={show}>
                    I'm hiding!
                </template>
            </p>
            <p>
                <lightning-button onclick={handleClick} variant="neutral" label="Switch"></lightning-button>
            </p>
        </div>
    </lightning-card>
</template>

在此代码示例中,文本Peek-a-boo!show属性值为时出现true。当show设置为时false我隐藏的文本出现。单击该按钮可切换show属性的值。

手动操作DOM的其他方法

在某些情况下,可以使用显示CSS样式来操作DOM元素的显示。尽管此方法在Lightning Web Components中照常工作,但超出了此特定模块的范围。 

Salesforce开发人员的JavaScript技能-了解JavaScript核心概念

学习目标

完成本单元后,您将能够:

  • 描述JavaScript运行时环境的性质。
  • 区分JavaScript引擎和语言。
  • 学习JavaScript时避免关键陷阱。
  • 描述一些重要的JavaScript最佳实践。

JavaScript的内容,原因和方式

在2000年代初期,软件世界开始爆炸,您可以构建完全通过Internet托管和交付的应用程序。尽管我们现在将Web应用程序视为理所当然,但当时它们就像看到机器人骑自行车一样令人着迷。 

骑自行车的机器人。

浏览器是简单的HTML渲染器,而HTML和JavaScript标准是分散的。要创建具有甚至稍微复杂的功能的网页,您必须强制用户仅使用一种浏览器,通常是Internet Explorer。直到今天,由于这个时代做出的设计决策,仍有一些应用程序强制执行此操作。 

为了解决这些局限性,服务器端UI框架得到了发展,允许软件开发人员在服务器上动态创建网页。在服务器上执行计算能力的复杂逻辑:然后,将渲染的HTML投放到功能弱的浏览器中。在这个世界上,JavaScript主要是一种使页面具有某种程度的交互性并执行基本逻辑而无需服务器跳闸的方法。服务器端框架非常流行,其中包括Salesforce框架Visualforce。为了证明这种受欢迎程度,今天在Salesforce中存在着数百万个Visualforce页面。 

快进当今世界是一个不同的地方。浏览器是功能强大的应用程序,可以优化网页查看。JavaScript已按照ECMAScript标准进行了很好的标准化,并且顶级浏览器制造商通常都擅长采用新功能。今天的Web应用程序具有丰富的用户界面,该用户界面在浏览器中运行现代JavaScript。代替服务器端框架,现代Web应用程序倾向于客户端渲染。在Salesforce中,这是通过Lightning Component Framework完成的。但是,对于许多开发人员而言,编写JavaScript仍然是新事物。如果您主要使用Visualforce页面和Apex控制器,则可能需要全面了解JavaScript的工作原理,以便更好地了解您的组件。 

是时候提高您的JavaScript技能了。 

JavaScript运行时

JavaScript运行时是解释JavaScript代码的引擎。它可以是浏览器或服务器等其他运行时环境的一部分。现代JavaScript引擎功能完善,功能强大,可以优化执行,并符合ECMAScript标准。 

JavaScript引擎的定义功能是由下面的堆栈表示的单线程运行时。在堆栈中完成的工作拥有线程,并且必须先完成其同步逻辑,然后再移交对线程的控制。 

具有堆栈,队列,事件和API的JavaScript引擎

运行时是一个繁忙的地方。可以随时随地从许多来源来进行新工作,包括用户(UI事件)和Web API(例如地理位置或设备移动)。由于只有一个线程,因此有一个队列等待工作轮到使用该线程。 

当堆栈为空时,事件循环从队列中获取等待完成的工作,并将其移入堆栈。 

事件循环从队列中获取工作,并将其传递给堆栈

当然,这是一种简化,但是它说明了JavaScript引擎如何完成工作的基本模型。由于具有这种体系结构,因此JavaScript语言可以像实际一样工作。 

JavaScript语言

作为一种语言,JavaScript经常被误解。如果您将其拾起任何时间,无疑会问自己一些问题。它是脚本语言吗?它与Java有什么关系?它是一种真正的编程语言吗?为什么会做? 

让我们围绕JavaScript作为一种语言来设置一些上下文,以帮助您回答其中的一些问题。 

JavaScript正在改变

因为JavaScript是根据ECMAScript标准构建的,所以它一直在变化。每年都会发布描述新功能的标准更新,然后JavaScript引擎项目(浏览器和运行时制造商)将它们安装到位。 

随着JavaScript的成熟,更新可能包括更多现代语言功能。在其他情况下,添加功能以实现现有功能的更简洁的语法(这些称为语法糖)。 

API的采用并不普遍

这句话看起来很吓人,但是绝大多数JavaScript API都可以在最常见的浏览器平台上使用。尽管如此,实现者并没有以相同的速度采用每种语言功能或API。有些人从不采用某些功能(尽管这种情况很少见)。通常,如果您想使用该语言的较新功能,则应使用caniuse.com之类的资源来了解它在目标浏览器中的 运行情况。 

如果新功能不是本机实现的,那么通常会编写一些代码来暂时实现该缺失功能的目的。此临时填写代码称为polyfill。实际上,Lightning Component Framework使用精选的polyfill列表,这些列表在运行其他代码之前被应用以自动改善浏览器的兼容性。  

必不可少的事情

现在该看一些代码了。让我们从每个JavaScript开发人员应该知道的一些事情开始,使他们的生活更轻松。 

记住区分大小写

JavaScript区分大小写。对于许多习惯于Apex和SOQL不区分大小写的Salesforce开发人员而言,这是棘手的。请记住,每次在Salesforce中编写JavaScript代码时,请注意区分大小写。 

声明变量

变量声明做是使用三个运营商之一:varlet,和const。一般使用letconst。下表总结了这三个关键字之间的功能差异。 

关键词
范围
可变分配
变种
功能




const

没有

范围是我们稍后讨论的主题,因此让我们解决可变性的含义。 

所有变量都是指针。赋值是将变量指向内存中某物的行为。可变性是在初始分配变量后是否可以重新分配变量。使用varlet创建可变指针,而该指针const是不可变的。通常,最好通过演示来理解这一点 。 

注意

JavaScript游乐场是一个有价值的工具,可以试用代码,并在浏览器中实时查看其工作方式。有很多JavaScript游乐场,但是在本模块中,我们将链接到jsbin中的示例 。

//primitive assignments
var myBike = "Mountain Bike";
let currentGear = 5;
const numberOfGears = 12;
//reassignment
myBike = "Penny Farthing"; // this works
currentGear = 1; // so does this
numberOfGears = 1; // error

以上,myBike并且currentGear在重新分配值时没有问题。但是,当尝试这样做时numberOfGears,会出现错误。 

当 工作与对象(而不是原语),请记住,const只有防止重新分配您的变量设置不同的对象。仍然可以更改对象本身(其属性,函数等),如下所示。 

// call constructor, new object, assign it to bike
const bike = new Bike();
//Change internal state by calling a function
bike.changeGear("front", "Up");
// add a new member to bike that did not exist before
bike.type = "Penny Farthing";
// check for success
console.log(bike.calculateGearRatio()); // 4.0909...
console.log(bike.type); // "Penny Farthing"
// attempt to point bike to new instance of Bike
bike = new Bike(1,2); // error

在这里,我们从Bike构造函数创建一个对象,并将其分配给bike变量(记住,区分大小写)。然后,我们可以更改所需的任何内容,例如调用更改其状态甚至添加新成员的函数。但是,当我们尝试将Bike重新分配给其他东西时,在这种情况下,通过再次调用构造函数,就会出现错误。 

隐式强制

当大多数JavaScript运算符遇到无效类型时,它们会尝试将值转换为有效类型。这种隐式转换类型的过程称为隐式类型强制。考虑以下: 

let num1 = 9 * "3";
console.log(num1); // 27 (a number)
let num2 = 9 + "3";
console.log(num2); // "93" (a string)

在第一个示例中,*运算符只能用于数学运算,将字符串强制"3"为数字。结果为27。在第二个+运算符中,该运算符将字符串视为"3"一元运算符(并置)。这将强制9以字符串"9"的结果强制输出到字符串"93"。 

乍一看,这似乎很方便,但实际上可能导致令人困惑的结果。 

不要使用隐式强制

隐式类型强制的许多实例令人困惑。例如布尔比较。C系列语言常用的==  and !=比较运算符将尝试将任何内容转换为布尔值。有确定性的规则,但是它们太复杂而无法实际应用。这是一些有趣的例子。

false == ""; // true
false == "0"; // true
"" == "0"; // false
[0] == 0; // true

“为什么这样做?” 

究竟。

对于布尔比较,最佳实践是使用===!==。使用这些运算符,基本类型仅在类型和值都匹配时才等效,并且对象比较仅在它们各自的指针指向相同的内存地址时才为真。尝试与上述相同的比较: 

false === ""; // false
false === "0"; // false
"" === "0"; // false
[0] === 0; // false

真假

您知道我们刚刚针对隐式类型强制发出警告的规则吗?好吧,这是个例外。 

当表达式期望布尔值时,以下值始终被视为false。 

  • false (当然)
  • 0 (数字零)
  • ""''(一个空字符串)
  • null
  • undefined
  • NaN (数学运算失败的结果)

falsefalse(当然!)。这些值的其余部分被强制为false。作为一个整体,他们被称为虚假。 

毫无疑问,true仅此而已true。但是任何其他类型的值都被强制转换为true。我们称这些价值观是真实的。 

这有一个方便的副作用。假设您要通过简单地将变量传递到if表达式中来测试几种类型的无效数据。 

const myRecord = response.getReturnValue();
if (myRecord) {
  //now process the record
}

如果由于某种原因上述分配失败,并且我们最终得到一个未初始化的变量(undefined),一个空字符串或0,我们仅通过对该myRecord变量进行条件检查就覆盖了所有这些基础。这是JavaScript中广泛接受的做法。 

this 是棘手的

该模块包含有关如何使用this指针的整个章节。它具有定义明确的规则,但是this即使在同一功能内,指向的内容也可以更改。例如,在Apex类中,您可能会看到: 

public class MyApexClass {
  private String subject;
  public MyApexClass(String subject) {
    this.subject  = subject;
  }
}

在此示例Apex类中,this仅引用的当前实例MyApexClass。在JavaScript中,this指向的内容不取决于定义函数的位置,而是取决于该函数的调用位置。更多关于this以后。 

功能就是价值

在JavaScript中,一切都是对象。这也适用于功能。与其他对象一样,可以将函数分配给变量,将其传递给其他函数的参数,并使用与其他任何对象相同的方式。 

如果您未使用函数是一等公民或使用lambda的语言,这可能会有些令人震惊。稍后,我们将整个单元用于功能上。 

此后,还有更多其他事情。 

现在,您已经对JavaScript概念有了很好的基本介绍。接下来,我们来看一下JavaScript在浏览器中的工作方式。