cocoscreator面试题集锦

公众号 亮亮同学TT
cocoscreator 版本:2.4.x
技术交流: 117871561

js/ts 遵循es6标准

第一篇

Javascript部分

  • Javascript有哪些数据类型? 举例两个最常见的内置对象数据类型。
    【答案】Number, String, Boolean, Null, Undefined, Object
    常见内置对象类型:数组,函数

  • 如下一段代码:

var a = [];
a[100] = 1;
console.log(a.length);
console.log(a[0]);
a[200] = undefined;
console.log(a.length);
console.lolg(a['100']);

请问四条log语句分别输出什么?
【答案】分别输出 101,undefined, 201,1

  • 如下代码 打印结果分别是什么?
function counter_1() {
    let count = 0;
    function inc(count) {
      return count++;
    }
    return () => inc(count);
  }
 let inc = this.counter_1()
    console.log(inc());
    console.log(inc());
    console.log(inc());

答案是 0,0,0

解析:

虽然是闭包 ,但是 变量不同, 每次调用闭包 都会传参 ,此时 参数是number类型 number类型属于基础类型 ,也就是说是值的传递 , 当进行值传递时 在栈内存中 实际上是 新开辟了一份新的内存,所以每次传进来的参数都是全新的 参数是 0 ,也就是count一直是0,里面做了count++操作 ,所以 得到的是 +1之前的 数值 仍是0 最后每次 调用闭包都会是 0

技巧:变量就近原则

  • 如下代码 打印结果分别是什么?
function counter_2() {
    let count = 0;
    function inc() {
      return count++;
    }
    return inc;
  }
 let inc = this.counter_2()
    console.log(inc());
    console.log(inc());
    console.log(inc());

答案:

0,1,2

解析:

利用了闭包的特性,在函数运行结束后count变量不会被释放,此时 变量的内存情况是 存放在堆里面,因为是count++所以 是 先用后加+ 1 结果是 0,1,2

  • 一个数组里面有number类型,string类型和数组 要将里面的所有非数组数据存到一个一元数组中.

答案:

let arr:any[] = []
function  flatDeep(data: any) {
        if (!Array.isArray(data)) {
            arr.push(data)
        } else {
            for (let one of data) {
                flatDeep(one);
            }
        }
    }

解析:

利用递归,每次递归查找筛选 非数组的元素 放入一维数组arr中。

  • parseInt('1.9'); parseInt('hello')分别返回什么值?
    【答案】分别返回 1, NaN

  • null和undefined的区别。
    【参考答案】null表示一个“空”的值,它和0以及空字符串''不同,0是一个数值,''表示长度为0的字符串,而null表示“空”。在其他语言中,也有类似JavaScript的null的表示,例如Java也用null,Swift用nil,Python用None表示。但是,在JavaScript中,还有一个和null类似的undefined,它表示“未定义”。
    JavaScript的设计者希望用null表示一个空的值,而undefined表示值未定义。大多数情况下,我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用。

  • ==和===的区别
    【参考答案】==在比较的时候可以自动转换数据类型。===严格比较,不会进行自动转换,要求进行比较的操作数必须类型一致,不一致时返回flase。

  • 函数中this指向什么? call, apply, bind的用法和区别。
    【参考答案】this指代函数的运行环境,执行obj.func()时,this指向obj,直接执行func()时,this指向全局环境。
    call, apply, bind都可以改变函数执行时的运行环境,即this的指向。
    call和apply都是调用时立刻执行的,而bind调用后返回了绑定this对象的原函数,bind比较适合将this绑定后的函数传入到其他函数中去执行,特别是作为回掉函数异步执行。
    call和bind的参数第一个参数是要绑定的对象,后面是要传入原函数的多个参数;而apply第二个参数必须是一个数组,数组中是要传入的参数。

  • 说说对 prototype和 __proto __ 的理解
    【参考答案】prototype是函数才有的属性,prototype本身也是个函数对象;__proto __是所有对象都有的属性,__proto __指向构造它的对象的对象的prototype。例如:

> var o = new Object()
o.__proto__ == Object.prototype
< true

o是Object构造出的对象,o的__proto __指向Object的prototype,这样o可以使用Object.prototype里面的方法。原型链:当js查找对象的属性时,先查找对象自身是否具有该属性,如果没有,就会去__proto __指向的prototype对象上查找,直到找到或者__proto __为null

  • 使用构造函数实现一个类Foo,需要有属性 count, 方法bar(), 并且写出创建该类对象的方法
    【参考答案】
function Foo(){
  this.count = 0;
}

Foo.prototype.bar = function(){
}

var foo = new Foo();
foo.bar();

  • 以下代码片段输出是什么,为什么?如果想输出0,1,2请问如何修改?
var s = [];
function foo() {
    for(var i=0; i<3; i++){
            s[i] = function(){
                  console.log(i);
            }
    }
}
foo();
s[0]();
s[1]();
s[2]();

【参考答案】输出3,3,3。因为foo()函数执行时生成了三个闭包,这三个闭包绑定了同一个变量i,第三个闭包生成时,i的值为3。因此执行这三个闭包时都会输出3。
修改为输出0,1,2的原则是让三个闭包绑定不同的变量,所以在生成闭包时就要区分出来,一种修改方法如下:

let s = [];
function foo() {
    for(let i=0; i<3; i++){
            s[i] = function(){
                  console.log(i);
            }
    }
}
foo();
s[0]();
s[1]();
s[2]();
  • 说说ES6为什么要引入let关键字
    【参考答案】因为要解决var声明对象产生的问题。
    var是函数级作用域,而let是块作用域。
    var存在变量提升,即变量可以在声明之前使用,值为undefined,而let声明的变量如果在声明之前使用会抛出一个错误。
    另外let不允许重复声明变量。
    ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

Cocos creator部分

  • cocos creator和cocos2dx的区别
    【参考答案】cocos creator是一套包含编辑器在内的开发框架,其内部引擎使用了cocos2d-x js的精简修改版本。creator使用js/ts语言开发,以内容创作为核心,脚本作为自定义组件添加到场景的节点上。

  • creator中需要动态载入的资源,放在工程的哪个子目录中
    【参考答案】asserts/resources

  • 写出代码片段:获取节点node上Label组件,并设置其内容为'hello'
    【参考答案】

let label = node.getComponent(cc.Label);
label.string = 'hello';

  • 列举出组件的生命周期回掉函数,并说明其调用时机
    【参考答案】
onLoad   组件首次激活时触发
onEnable  组件的enabled属性从false变为true时
start    组件第一次执行update之前触发
update     每一帧渲染前调用
lateUpdate 所有组件update调用后调用
onDisable  组件的enabled属性从true变为false时
onDestroy  组件或所在节点调用了destroy()时调用,并在当前帧结束时统一回收组件
  • creator对齐UI控件使用什么组件?如果想制作一个和屏幕大小一样的节点如何设置该组件
    【参考答案】widget组件。设置top,bottom,left,right为0px,且该节点从直接的父节点到场景根节点都必须有widget组件且设置为同屏幕大小。或者1.10之后可设置target为最上层的节点。

  • 写一小段代码,将节点node在1秒钟之内从当前位置移动到(100,100)
    【参考答案】

cc.tween(node)
.to(1,{position:cc.v2(100,100)})
.start()

游戏开发部分

  • 什么是draw call? 为什么减少draw call可以优化游戏速度。如何减少draw call? 在creator中如何做
    【参考答案】
    1)Draw Call 简称 ”绘制调用“
    就是CPU调用图形绘制接口(api)(例如OpenGL中的glDrawElement命令。)来命令GPU进行图形绘制(渲染)的操作。

2)在每次调用Draw Call之前,CPU需要向GPU发送很多内容,包括数据,状态,命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而一旦CPU完成了这些准备工作,GPU就可以开始本次的渲染。GPU的渲染能力是很强的,渲染300个和3000个三角网格通常没有什么区别,因此渲染速度往往快于CPU提交命令的速度。如果Draw Call的数量太多,CPU就会把大量时间花费在提交Draw Call命令上,造成CPU的过载。

因此造成Draw Call性能问题的是CPU。
3)解决办法是 尽可能合批处理 即将小的dc 合并成大的dc 。
1,将碎图 合并成大图
1),静态合图
2),动态合图
3),textuepaker打包图集

注意:
1, 尽量将处于同一界面(UI)下的相邻且渲染状态相同的碎图 打包成图集
2,改变渲染状态会打断渲染合批,例如改变纹理状态(预乘、循环模式和过滤模式)或改变 Material(材质)、Blend(混合模式)等等,所以使用自定义 Shader 也会打断合批。

3,合理控制图集最大尺寸,避免单个图像加载时间过长。
4,尺寸太大的图像没有必要打进图集(如背景图)而且图像尺寸越大,加载的时间也越长,而且是非线性的那种增长,例如加载一张图像比加载两张图像所消耗的时间还长。,得不偿失。
5, 间距保持默认的 2 并保持勾选扩边选项,避免图像裁剪错误和出现黑边的情况。

2,label用bmfont位图

  • 一张1024x1024,32位的贴图,在内存里面占多少字节?
    【参考答案】
    图像占用内存的公式是:numBytes = width * height * bitsPerPixel / 8

套用公式,RGBA8888 是32位
1024102432/8 = 4194304(字节)
4194304/1024/1024 = 4M ,
1024*1024 RGBA8888 图片加载到内存的大小 = 4M

  • cocos中sprite的Blend属性,Src Blend Factor设置为 SRC_ALPHA, Dst Blend Factor设置为 ONE_MINUS_SRC_ALPHA是什么意思,有什么作用?
    【参考答案】这表示绘制这个Sprite时,和Frame buffer上面已经有的像素进行混合的公式参数。以上参数设置的公式为: FinalColor = SpriteColor(RGB) * SpriteAlpha + BufferColor(RGB) * (1-SpriteAlpha)。效果是标准的透明图元渲染。

  • 如果做一个射击游戏,需要发射大量的子弹,为了避免频繁的申请内存,一般会采取什么方法?
    【参考答案】会采用对象池/内存对象缓存的机制。cocoscreator提供了相关的api nodepool对象池。

常用算法:

对数

如果a^x=N(a>0,且a≠1),则x叫做以a为底N的对数,记做x=log(a)(N),其中a要写于log右下。其中a叫做对数的底,N叫做真数 。通常我们将以10为底的对数叫做常用对数,以e为底的对数称为自然对数。

「基本知识」

image

时间复杂度

通常我们会用大O来表示一个算法的时间复杂度,即 某个算法的时间增量。

例如: 要在 一个 5个元素的数组中 查找 1个数 , 不管从第几个元素开始查找 要想 找到这个数 ,最多需要 5次查找操作 ,最少需要1次 如果是 6个元素的数组,要找到这个数 则 最多 6次 ,最少1次。 通过 大O表示 时间复杂度就是 O(n), 即 每增加 n个元素 就需要 多查找n次 ,算法的时间增量就是o(n).通常大O表示算法复杂度增量 都是以 最坏的 情况为 准。

简单查找

在一个数组中挨个查找某个数据,这种查找就是简单查找

比如 5个元素的数组中 查找 1个数 ,从头到尾查找,最多需要查找五次。

「时间复杂度」:O(n) , 算法时间是线性增长的。 n个数 最多查找n次 最少查找1次。

「例子」

   * 简单查找
   * @param arr 目标数组
   * @param num 要查找的数字
   */
  function simpleSearch(arr: number[], num: number):number {
    for (let one of arr) {
      if (one === num) {
        return one;
      }
    }
    return -1;
  }

二分查找

「前提」:已经排序的数据

「逻辑」:在排好序(降序如 1,2,3)的 容器中 每次选择 容器的 中间或者+1或者-1的数字,进行比较,如果 该数字 大于要查找的数字 ,那么以该数字索引-1对应的值为最大元素,以原来最小的值为最小值 ,继续 折中 查找,如果 该数字 小于要查找的数字 则以 该数字索引+1所对应的值为最小值 ,原最大值为最大值 再次折中查找 ,一直到最小数索引等于最大数索引 即 找到对应的值或者 查找完 没有该值为止。

「时间复杂度」:O(log2n) 以2为底 真数为n的对数。

比如 在 n个已经排好序的数中查找某个数据,每次折中查找 ,那么找到这个数的 最多次数是 log2n 次 。如果 n是 10 那么 找到这个数 最多需要4次 即 2 * 2 * 2 * 2。因为222是 8不到10 不能完全查完容器的数据,所以还需要折中查找一次,所以是 4次

「例子」

   * arr 目标数组
   * num  要查找的数字
  */
 function binarySearch(arr: number[], num: number):number {
    let low = 0;
    let hight = arr.length - 1;
    let count = 0;
    while (low <= hight) {
      count++;
      let mid = Math.floor((low + hight) / 2);
      let guess = arr[mid];
      if (guess === num) return guess;
      if (guess > num) {
        hight = mid - 1;
      } else {
        low = mid + 1;
      }
    }
    return -1;
  }

选择排序

「逻辑」:需要遍历n-1趟,n表示容器中n个元素,每趟从待排序的记录序列中选择最小或最大的数字放到已排序表的最前位置,直到全部排完。 每趟交换一次数据

「时间复杂度」:log(n*n) 最多需要 n * n 次排完

「稳定性」:不稳定

「例子」

   * 选择排序 从小到大
   * @param arr 容器
   */
 function selectionSort(arr: number[]) {
    let len = arr.length;
    for (let i = 0; i < len - 1 ; i++) {
      //设定第一个元素为最小元素
      let minindex = i;
      for (let j = i + 1; j < len ; j++) {
        if (arr[minindex] > arr[j]) {
          minindex = j;
        }
      }
      //每次遍历找出最小值与上一次的最小值交换
      if (i != minindex) {
        let temp = arr[minindex];
        arr[minindex] = arr[i];
        arr[i] = temp;
      }

    }
  }

冒泡排序

「逻辑」:在要排序的一组数中,对当前还未排好序的范围内的全部数,对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。 需要遍历 n-1趟,每趟 需要对比 n- 当前排序索引-1 次。每趟 交换 n-当前排序索引 -1次 数据

「时间复杂度」:log(n*n) 最多需要 n * n 次排完

「稳定性」:稳定

「例子」

 * 冒泡排序 从小到大
 * @param arr 
 */
 function bubbleSort(arr: number[]) {
    let len = arr.length;
    for (let i = 0; i < len - 1; i++) {
      for (let j = 0; j < len - i - 1; j++) { // -i 是 排除已经沉到最下面的数,没必要再次比较。
        if (arr[j] > arr[j + 1]) {
          let temp = arr[j];
          arr[j] = arr[j + 1];
          arr[j + 1] = temp;
        }
      }
    }
  }

快速排序

「逻辑」

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

「时间复杂度」:最好情况 nlog(2n),最差情况log(n*n)

「理想的情况是」:每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。

「最坏的情况是」:每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n*n)

「稳定性」:不稳定

「例子」

   * 快速排序
   * @param array 输入待排序数组
   * @returns 排序后的数组
   */
function  quickSort(array:number[]) {

    const sort = (arr, left = 0, right = arr.length - 1) => {
     if (left >= right) {//如果左边的索引大于等于右边的索引说明整理完毕
      return
      }

    let low = left
    let index = right
    const baseVal = arr[right] // 取无序数组最后一个数为基准值

    while (low < index) {//把所有比基准值小的数放在左边大的数放在右边

      //找到一个比基准值大的数交换
      while (low < index && arr[low] <= baseVal) { 
        low++
      }
      arr[index] = arr[low] // 将较大的值放在右边如果没有比基准值大的数就是将自己赋值给自己(i 等于 j)

      while (low < index && arr[index] >= baseVal) { //找到一个比基准值小的数交换
        index--
        break
      }
      arr[low] = arr[index] // 将较小的值放在左边如果没有找到比基准值小的数就是将自己赋值给自己(i 等于 j)
    }

      arr[index] = baseVal // 将基准值放至中央位置完成一次循环(这时候 j 等于 i )

      sort(arr, left, index-1) // 将左边的无序数组重复上面的操作
      sort(arr, index+1, right) // 将右边的无序数组重复上面的操作
    }
    sort(array)
    return array
   }

你可能感兴趣的:(cocoscreator面试题集锦)