闭包
之前在学习委托事件的时候了解了一下闭包, 没想到后来在游戏制作中就犯了一个闭包的错误.
使用的是 Unity 引擎, 先看一个 Start 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private void Start() { GameObject gameObject; characterPackage = characterDataScript.characterData.Package;
for (int index = 0; index < characterPackage.Count; index++) { gameObject = Instantiate(itemMenuItemPrefab, itemMenuItemParent.transform);
gameObject.name = index.ToString();
gameObject.transform.Find("Icon").Find("Image").GetComponent<Image>().sprite = characterPackage[index].Icon;
gameObject.transform.Find("Name").Find("Text").GetComponent<Text>().text = characterPackage[index].Name;
gameObject.GetComponent<Button>().onClick.AddListener(() => Event_ItemSelected(gameObject)); } }
|
执行步骤:
- 新建一个菜单项, 用 gameObject 保存.
- 修改菜单项各项属性. 其中名称属性就是遍历索引 index 的值, 即第一个菜单项的名称是 0, 第二个是 1, 第十个是 9 等等;
- 给菜单项上的按钮注册事件, 事件的参数为 gameObject, 即将菜单项自身作为参数传递到方法中.
那么问题来了, 假设有 4 个菜单项, 那么在事件 Event_ItemSelected
中输出按钮的名称的话, 依次点击菜单项会输出什么呢?
不是很了解闭包的人应该会说出这个答案: 0, 1, 2, 3
, 而正确的答案是: 3, 3, 3, 3
!
什么是闭包?
在上面的代码段中, gameObject
变量是在 Start 方法中定义的, 那么按理来说只要出了 Start 方法, gameObject 变量就不存在了. 但是我们在按钮的 Event_ItemSelected
事件中却又访问了变量 gameObject, 这种现象就是闭包.
当一个变量脱离了其自身的作用域后, 根据上下文关系继续在某些方法或者类中发挥作用的现象就是闭包. 或者说闭包可以让变量脱离其作用域继续发挥作用.
答案解析
在上面的代码中, 每次循环都会创建一个菜单项, 并将 gameObject 变量作为参数绑定事件, 所以最后的结果就是 菜单项 0, 菜单项 1, 菜单项 2, 菜单项 3 都使用了 gameObject 变量作为参数, 而 gameObject 是一个变量啊, 不是一个常量, 创建菜单项 0 的时候, gameObject 确实是菜单项 0, 但是等到了创建菜单项 3 的时候, gameObject 也就变成菜单项 3 了, 因此调用事件的瞬间, 使用的也是那个瞬间的 gameObject 值, 也就是 3.
纠正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private void Start() { characterPackage = characterDataScript.characterData.Package;
for (int index = 0; index < characterPackage.Count; index++) { GameObject gameObject = Instantiate(itemMenuItemPrefab, itemMenuItemParent.transform);
gameObject.name = index.ToString();
gameObject.transform.Find("Icon").Find("Image").GetComponent<Image>().sprite = characterPackage[index].Icon;
gameObject.transform.Find("Name").Find("Text").GetComponent<Text>().text = characterPackage[index].Name;
gameObject.GetComponent<Button>().onClick.AddListener(() => Event_ItemSelected(gameObject)); } }
|
使用这段代码就可以纠正那个错误, 两段代码只有一个区别, 就是 gameObject 变量定义的位置不同. 一个在 for 循环外, 一个在 for 循环内.
在 for 循环外部定义时, 每一个菜单项使用的都是同一个 gameObject;
在 for 循环内部定义时, 每一次循环都会重新定义一个 gameObject, 每一个菜单项之间使用的都是不同的 gameObject;