• 自学JavaScript第二天- JS 进阶: 对象 函数


    对象进阶

    面向对象有两个基本概念:

    • 类:类是对象的类型模板,例如,定义 Student 类来表示学生,类本身是一种类型, Student 表示学生类型,但不表示任何具体的某个学生;
    • 实例:实例是根据类创建的对象,例如,根据 Student 类可以创建出 xiaoming 、 xiaohong 、 xiaojun 等多个实例,每个实例表示一个具体的学生,他们全都属于 Student 类型。

    不过,在老版本的JavaScript中,这个概念需要改一改。老版本JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。基于原型实现对象模型比较简单,但是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要大量代码,且需要正确实现原型链。于是 ES6 开始 class 被正式引入 js。

    js 使用 class 关键字来定义一个类,类体在一对花括号 {} 中。需注意的是,由于不是所有的主流浏览器都支持ES6的 class,所以如果需要使用 class,可以试试Babel这个工具将 class 代码转换为传统的 prototype 代码。

    构造函数

    由类创建对象时自动执行的方法就是构造函数,js 的类在定义时可以定义特殊方法 constructor() 这个方法就是构造函数。

    class ClassName {
      constructor() { ... }
    }
    
    • 1
    • 2
    • 3

    使用类

    当类定义好后,可以使用 new 关键字来创建类的对象。对象就是类的实例化,当创建对象时,会自动执行其构造函数。

    类的继承

    js 类继承使用 extends 关键字。

    // 基类
    class Animal {
        // eat() 函数
        // sleep() 函数
    };
     
    //派生类
    class Dog extends Animal {
        // bark() 函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用 super() 方法调用父类的构造函数。可以通过在构造函数中调用 super() 方法,调用父类的构造方法,来访问父类的属性和方法,继承父类的属性和方法。

    静态方法

    使用 static 关键字修饰方法,就成为静态方法,也叫类方法。它属于类,但不属于对象。可以使用 类名.静态方法名 来调用静态方法。静态方法不能在对象上使用,只能在类中使用。

    函数进阶

    立即执行函数

    函数在声明时马上调用,就是立即执行函数。通常会定义一个匿名函数并执行,也只执行一次。

    立即执行函数的格式有很多种,但是不同浏览器貌似支持的写法不太一样,有一个较为通用的写法,是将函数定义过程放在一个括号内,并且再加一对括号,表示调用

    // 通常写法
    (function () {alert('匿名函数立即调用')})();
    // 箭头函数写法
    (() => {alert('匿名函数立即调用')})();
    
    • 1
    • 2
    • 3
    • 4

    也有种简单写法,在函数前加叹号

    !function() {alert('匿名函数立即调用')}();
    // 此写法不能用于箭头函数
    
    • 1
    • 2

    立即执行函数表达式

    有这样一段js代码:

    let p = prompt('input');
    if (p) {
        console.log(p);
    }
    
    • 1
    • 2
    • 3
    • 4

    通常来说,不能直接在 if 语句的条件部分进行变量声明和复制,但是可以使用立即执行函数表达式来模拟类似的效果:

    if ((p = prompt('input'))) {
    	console.log(p);
    }
    
    • 1
    • 2
    • 3

    这里赋值表达式会直接返回函数的结果进行判断。需要注意的是,这种写法虽然可以工作,但是某些情况下可能会造成作用域混淆。

    方法

    对象内部的函数成员(即绑定到对象的函数)就是方法。使用上和函数类似,不同的地方在于 this 关键字。this 是一个特殊变量,它始终指向当前对象。当函数没有自身对象时 this 就是全局对象,即 window 对象。比较坑爹的是,下面这个例子即使拿到函数再调用时,this 也是指向的 window

    function getAge() {
    	var y = new Date().getFullYear();
    	return y - this.birth;
    }
    
    var xiaoming = {
    	name: '小明',
    	birth: 1990,
    	age: getAge
    };
    var fn = xiaoming.age; // 先拿到xiaoming的age函数
    fn(); // NaN
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    要保证 this 指向正确,必须用 obj.xxx() 的形式调用!由于这是一个巨大的设计错误,要想纠正可没那么简单。ECMA决定,在strict模式下让函数的 this 指向 undefined ,因此,在strict模式下,上例中 fn() 会得到一个错误 Uncaught TypeError: Cannot read property 'birth' of undefined,将此问题暴漏出来。另外在重构函数中也有类似的问题

    var xiaoming = {
    	name: '小明',
    	birth: 1990,
    	age: function () {
    		function getAgeFromBirth() {
    			var y = new Date().getFullYear();
    			return y - this.birth;
    		}
    		return getAgeFromBirth();
    	}
    };
    
    xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    原因是 this 指针只在 age 方法的函数内指向 xiaoming ,在函数内部定义的函数, this 又指向 undefined 了!(在非strict模式下,它重新指向全局对象 window !)。修复的办法是用一个 that 变量首先捕获 this

    var xiaoming = {
    	name: '小明',
    	birth: 1990,
    	age: function () {
    		var that = this; // 在方法内部一开始就捕获this
    		function getAgeFromBirth() {
    			var y = new Date().getFullYear();
    			return y - that.birth; // 用that而不是this
    		}
    		return getAgeFromBirth();
    	}
    };
    xiaoming.age(); // 25
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    也可以使用函数本身的 apply 方法来手动控制 this 指向的对象。它接收两个参数,第一个参数就是需要绑定的 this 变量,第二个参数是 Array ,表示函数本身的参数。

    function getAge() {
    	var y = new Date().getFullYear();
    	return y - this.birth;
    }
    
    var xiaoming = {
    	name: '小明',
    	birth: 1990,
    	age: getAge
    };
    
    xiaoming.age(); // 25
    getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    或是 call 方法,区别在于参数按照顺序传入。对普通函数调用,我们通常把 this 绑定为 null。

    Math.max.apply(null, [3, 5, 4]); // 5
    Math.max.call(null, 3, 5, 4); // 5
    
    • 1
    • 2

    装饰器

    利用 apply() ,我们还可以动态改变函数的行为。

    var count = 0;
    var oldParseInt = parseInt;		// 保存原函数
    
    window.parseInt = function () {
    	count += 1;
    	return oldParseInt.apply(null, arguments);	// 调用原函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    高阶函数

    JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

    function add(x, y, f) {
    	return f(x) + f(y);
    }
    
    • 1
    • 2
    • 3

    最经常用到的就是数组的一系列遍历操作:

    map / reduce

    map 就是映射,可以将若干元素以一一对应的关系传入函数中进行处理。由于 map() 方法定义在JavaScript的 Array 中,我们调用 Array 的 map() 方法,传入我们自己的函数,就得到了一个新的 Array 作为结果。注意: map() 传入的参数是 pow ,即函数对象本身。

    function pow(x) {
    	return x * x;
    }
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    var results = arr.map(pow);
    console.log(results);		// [1, 4, 9, 16, 25, 36, 49, 64, 81]
    // 或者一些复杂的函数可以简单化
    arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    再看reduce的用法。Array的 reduce() 把一个函数作用在这个 Array 的 [x1, x2, x3…] 上,这个函数必须接收两个参数, reduce() 把结果继续和序列的下一个元素做累积计算,其效果就是

    [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

    比方说对一个 Array 求和,就可以用 reduce 实现

    var arr = [1, 3, 5, 7, 9];
    arr.reduce(function (x, y) {
    	return x + y;
    }); // 25
    
    • 1
    • 2
    • 3
    • 4

    filter

    filter也是一个常用的操作,它用于把 Array 的某些元素过滤掉,然后返回剩下的元素。和 map() 类似, Array 的 filter() 也接收一个函数。和 map() 不同的是, filter() 把传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定保留还是丢弃该元素。

    var arr = [1, 2, 4, 5, 6, 9, 10, 15];
    var r = arr.filter(function (x) {
    	return x % 2 !== 0;
    });
    r; // [1, 5, 9, 15]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可见用 filter() 这个高阶函数,关键在于正确实现一个“筛选”函数。filter() 接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示 Array 的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身

    var arr = ['A', 'B', 'C'];
    var r = arr.filter(function (element, index, self) {
    	console.log(element); // 依次打印'A', 'B', 'C'
    	console.log(index); // 依次打印0, 1, 2
    	console.log(self); // self就是变量arr
    	return true;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    sort

    JavaScript的 Array 的 sort() 方法就是用于排序的,但是排序结果可能让你大吃一惊

    // 看上去正常的结果:
    ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
    
    // apple排在了最后:
    ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
    
    // 无法理解的结果:
    [10, 20, 1, 2].sort(); // [1, 10, 2, 20]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第二个排序把 apple 排在了最后,是因为字符串根据ASCII码进行排序,而小写字母 a 的ASCII码在大写字母之后。第三个排序结果是因为 Array 的 sort() 方法默认把所有元素先转换为String再排序,结果 ‘10’ 排在了 ‘2’ 的前面,因为字符 ‘1’ 比字符 ‘2’ 的ASCII码小。

    sort() 方法的默认排序规则很坑,幸运的是,它是一个高阶函数,可以接收一个比较函数来实现自定义排序。通常规定,对于两个元素 x 和 y ,如果认为 x < y ,则返回负值 ,如果认为 x == y ,则返回 0 ,如果认为 x > y ,则返回正值。这样按照数字大小排序应该为

    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
    	if (x < y) return -1 ;
    	if (x > y) return 1;
    	return 0;
    });
    console.log(arr);	// [1, 2, 10, 20]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    需要注意的是,sort() 方法会直接对 Array 对象进行修改。

    every

    every() 方法可以判断数组的所有元素是否满足测试条件,类似于 filter ,只是不进行筛选而是根据条件返回 True 和 False。

    find

    find() 方法可以用于查找符合条件的第一个元素,找到返回元素,否则返回 undefined

    findIndex

    findIndex() 和 find() 类似,也是查找符合条件的第一个元素,不同之处在于 findIndex() 会返回这个元素的索引,如果没有找到,返回 -1

    forEach

    forEach() 和 map() 类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。 forEach() 常用于遍历数组,因此,传入的函数不需要返回值

    箭头函数

    ES6标准新增了一种新的函数:Arrow Function(箭头函数)。箭头之前的部分是函数参数,之后的部分是函数体。例如

    function (x) {
    	return x * x;
    }
    // 等价于
    (x) => { return x * x }
    // 如果只有一个参数,可以省略参数的括号
    x => { return x * x }
    // 如果函数体只有一条语句,则可以省略函数体的花括号和 return,能够自动返回其值
    x => x * x
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连 { … } 和 return 都省略掉了。还有一种可以包含多条语句,这时候就不能省略 {… } 和 return。如果参数不是一个,就需要用括号 () 括起来

    // 两个参数:
    (x, y) => x * x + y * y
    
    // 无参数:
    () => 3.14
    
    // 可变参数:
    (x, y, ...rest) => {
    	var i, sum = x + y;
    	for (i=0; i<rest.length; i++) {
    		sum += rest[i];
    	}
    	return sum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    需注意返回一个对象,也需要使用括号括起来

    x => { foo: x }		// SyntaxError
    // 应写为
    x => ({ foo: x })
    
    • 1
    • 2
    • 3

    箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的 this 是词法作用域,由上下文确定。箭头函数完全修复了 this 的指向, this 总是指向词法作用域,也就是外层调用者 obj

    var obj = {
    	birth: 1990,
    	getAge: function () {
    		// 省略了 var that = this;
    		var b = this.birth; // 1990
    		var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
    		return fn();
    	}
    };
    obj.getAge(); // 25
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    生成器

    generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。它借鉴了Python的generator的概念和语法。generator和函数不同的是,generator由 function* 定义(注意多出的 * 号),并且,除了 return 语句,还可以用 yield 返回多次

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

    例如编写一个产生斐波那契数列的生成器函数

    function* fib(max) {
    	var
    		t,
    		a = 0,
    		b = 1,
    		n = 0;
    	while (n < max) {
    		yield a;
    		[a, b] = [b, a + b];
    		n ++;
    	}
    	return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    直接调用一个generator和调用函数不一样, fib(5) 仅仅是创建了一个generator对象,还没有去执行它。

    调用generator对象有两个方法

    • 一是不断地调用generator对象的 next() 方法。next() 方法会执行generator的代码,然后,每次遇到 yield x; 就返回一个对象 {value: x,done: true/false} ,然后“暂停”。返回的 value 就是 yield 的返回值, done 表示这个generator是否已经执行结束了。如果 done 为 true ,则 value 就是 return 的返回值。当执行到 done 为 true 时,这个generator对象就已经全部执行完毕,不要再继续调用 next() 了
    • 第二个方法是直接用 for … of 循环迭代generator对象,这种方式不需要我们自己判断 done
  • 相关阅读:
    第一个Vue程序
    表格元素
    HMM算法python实现
    怎么把相册的某一张压缩作为相册的缩略图?
    DBConvert Studio 3.0.6 -2022-08-13 Crack
    计算机网络基础
    SQL Server查询结果导出到EXCEL表格
    外包干了2个月,技术退步明显.......
    haas506 2.0开发教程-sntp(仅支持2.2以上版本)
    一段木棍剪成三段,能够组成三角形的概率是多少
  • 原文地址:https://blog.csdn.net/runsong911/article/details/126404025