ThreeJS 学习笔记——JavaScript 中的函数与对象

259 阅读7分钟

一、前言

ThreeJs 的代码不管是源码还是 demo 的代码,都是相对规范的。而对于 JavaScript 基础不扎实的同学,比如我这种入门的,还是需要好好补一下的。特别是 JavaScript 中的函数与对象,与 Java 中的差异还是相当大的,而且也是这门语言的核心基础之一。

鉴于此,我还是默默地打开了 廖老师的 JavaScript ,然后埋头苦读。

二、函数

1.定义方法

至少2种定义: 一种是

Function namexxx(参数) {函数体}

另一种是

var namexxx = function() {}

2.作用域

(1)使用 var 关键字定义的变量,如果在外部则是全局的,如果在函数内部则是函数内的。 (2)使用 let 关键字定义的变量,是块级别的,只在当前模块内有效。 (3)不使用关键字定义的变量则是全局的。

注意:全局变量都会被绑定到 window 对象中去。所以对于全局对象来说,window.xxx 与 xxx 是一致的。所以可以大胆的猜测,js 中的全局对象是唯一的,就是 window。

3.关于 this

(1) 在函数内部使用 this ,则表示为指向这个函数的对象。 (2) 在调用函数时,如果函数属于某个对象,则需要以 obj.xxx() 的方式,而不是 xxx() 的方式。如果单独调用 xxx() 方式,其实调用的是 window.xxx()。这是一个设计上的巨大缺陷,在 use strict 模式下还会报出错误。 (3) 嵌套函数的情况下,内函数的 this 指向也是 window。解决的办法是在定义内函数前提前将 this 保存在另一个变量中去,如 let that = this / var that = this。或者使用 apply(obj,argments[]) 方法指定 this。

4.方法

被绑定到对象上的函数称为方法,与 java 不一样,可以先定义函数,然后再将函数绑定到对象上去。如构造方法大部分就是这么干的。

var OBJLoader2Example = function ( elementToBindTo ) {
       ......
};
OBJLoader2Example.prototype = {
     constructor: OBJLoader2Example,
     ......

5.高阶函数

JavaScript 中的函数其实都指向一个变量,因此在函数的参数中也可以传递一个函数,这种函数的参数又接收了另一个函数作为参数的函数便称之为高阶函数。这个有点类似于数学中方程中的 x 与 x平方的关系。 (1) map() / reduce(): map() / reduce() 都是 Array 的方法,map() 方法可以理解成是一个 map…to,其接收的参数是一个函数,而这个函参数接收一个参数。reduce() 可以理解成把 … 分解,其接收的参数也是一个函数,而这个函数接收两个参数。 (2) filter(),起到过滤的作用,例如去除 Array 中的素数,其接收的函数的参数是一个。 (3) sort(),排序方法,对 Array 的数据进行排序,其接收的函数参数等同于 java 中的 compator(),而函数的参数是 2 个。如果不传递函数参数则默认作为 string 来处理。

6.闭包

(1)定义

当把一个函数当作返回值返回时,这个函数便称之为一个闭包。

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

(2)特点

(1) 当把闭包赋值给一个变量xxx时并不会立即调用这个闭包,而是需要再进一步调用 xxx()。 (2) 闭包除了会保存闭包本身(闭包内的变量以及执行代码),还会保存定义闭包的函数的的参数以及变量。所以在闭包里面还可以继续访问函数里的变量。 (3) 闭包并不是真正的把外部变量克隆保存起来,而只是持有其引用。如果在调用闭包之前改变了某个外部变量,那么在调用闭包,闭包里获取到的变量的值是最近被修改的那一次的值。 (4) 闭包除了可以通过赋值给某个变量xxx,然后通过 xxx.closure() 来调用之外 ,还可以直接通过 func.xxx() 来调用。 (5) 闭包的功能是非常强大的,其不仅仅只局限于延迟调用,比如还有实现定义私有变量。

7.箭头函数

箭头函数类似于匿名函数,但其与匿名函数又有着明显的区别就是其纠真了 this 的正确指向。写法上有

x => x * x;
(x,y) => x + y;

8.generator

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次。

9.IIFE 立即调用函数

“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。写法上有

(function(){ /* code */ }());

或者

(function(){ /* code */ })();

三、面向对象

1.概述

不同于 Java,JavaScript 中的没有 class 与 实例的概念,其是直接通过原型(prototype)来实现面向对象编程。而所谓的继承不过是把一个对象的原型(proto)指向另一个对象罢了。例如:

Var student = {
 Name:’sutdent’,
 Age:10
}

Var XiaoMing = {
 Name:’xiaoming'
}

XiaoMing.__proto__ = student

通过上面的定义,XiaoMing 不仅有 name 属性,其还有 age 属性。当然,一般在实际项目中不这样写,而是推荐使用 Object.create() 来基于已有对象继承实现新的对象。

2.创建对象

(1) 函数原型链

函数的原型即 prototype 属性,JavaScript 中指定了其对象的 prototype 为要继承的对象,各级的 prototype 就构成了原型链。当我们用 obj.xxx 去访问一个属性时,其首先会在 obj 里面找,如果找不到就会到 prototype 上去找,如果还找不到就会到 prototype 的 prototype 里去找,最后会到 object 的 prototype,如果还找不到就报 undefine 了。

(2) 构造函数

先定义一个普通的函数 function xxx(){} 。如果在使用时在其前面增加了关键字 new,那么其便成为了构造函数。在构造函数中可以通过 this 来为对象定义属性和方法。但要注意方法是属于每个对象的,它们之间并不相等。如果要共享同一个方法,应该用 prototype 来定义,如下

Xxx.prototype.yyy = function(){}

这样定义的 yyy方法就是共享的了。

3.原型链继承

不能直接用 A.prototype = B.prototype,这样的话 A 和 B 就共享了同一个 prototype。那继承就没有意义了。一个做法是通过定义一个中间空对象,也即定义一个空的 function。如下是继承的一个简明的封装与实现。

(1) 封装一个 inherites 函数

Function inherits(Child , Parent) {
     //1.定义一个空的 function 对象
     var Empty = function() {};
    // 2.空 function 指定 Parent.prototype
   Empty.prototype = Pranet.prototype;
   // 3.指定 child.prototype 为空对象
   Child.prototype = new Empty();
   // 4.修复 child 的构造函数 child.constructor
   child.constructor = Child;
}

(2) 还要在 Child 中通过 Parent.call()方法 绑定 this

function Child(props) {
     Parent.call(this.props);
     this.xxx = props.xxx;
     …..
}

4.class 继承

ES6 开始增加了 class 以及 extends 关键字来实现类的定义和继承,这就和 Java 的继承很相似了。

(1)class 定义

class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

(2)class继承

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

这样看起来就舒服多了,有 class 也有 extends,仿佛又有了看 Java 代码的感觉。但其本质并不是像 Java 一样真的有类的概念,而只是原型链的一种实现 ,不用我们辛辛苦苦地去封装 inherites 函数了,当然看起来也好理解多了。

三、后记

这真的就只是一个学习笔记,但对于我来说学习完成后,把学到的东西和理解到的东西整理出来还是有一定收获的。

最后,感谢廖老师的课程,真的是浅显易懂,基础不好而又感兴趣的同学应当去通篇学习。同时也感谢你能读到此文章,希望这个学习笔记能与君共勉。