文章目录

  • 一、原型继承
  • 二、Class关键字
  • 1.ES6之前
  • 2.ES6后
  • 3.补充
  • 三、Map和Set
  • 四、Iterator(迭代器)
  • 1.遍历数组
  • 2.遍历Map
  • 3.遍历Set
  • 五、JS中不完善的报错+手动改进(throw+【argument】+补充:【...rest】)
  • 1.函数参数中报错不完善
  • 2.var局部作用域中的bug
  • 六、使JS的原生方法失效+恢复
  • 七、多个js文件全局遍历冲突问题
  • 八、方法与函数

一、原型继承

实例中,tom本身没有run方法,无法调用run

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量

但如果想调用,则可让tom的原型对象指向Student,这样,tom对象在调用run方法时,在本对象中查不到run方法时,则会去tom对象的__proto__属性中去查找run方法,也就是先在tom对象的原型链上查找紧邻上一个原型对象中的方法

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_原型对象_02


当然,要注意的是,将tom的原型指向Student时,Student的原型也会连接过来

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_i++_03


每个对象和原型都有原型,对象的原型指向原型对象,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。

作为一个对象,当你访问其中的一个属性或方法的时候,如果这个对象中没有这个方法或属性,那么JavaScript引擎将会访问这个对象的__proto__属性所指向的上一个对象,并在那个对象中查找指定的方法或属性,如果不能找到,那就会继续通过那个对象 的__proto__属性指向的对象进行向上查找,直到这个链表结束

二、Class关键字

1.ES6之前

在ES6之前,只能这样定义类

<script>
"use strict";

function Student(name) {
this.name = name;
}

//给Student类新增一个方法,上面的Student(name)为构造函数
//给类增加方法,因此需指向Student(name)函数的原型对象
Student.prototype.hello = function () {
console.log('Hello ' + this.name);
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_04


【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_05

引入一张图

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_06


2.ES6后

在ES6时,引入了Class关键字,使得此操作更加方便、合理和规范

<script>
"use strict";

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

hello() {
console.log('Hello ' + this.name)
}
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_07


继承用关键字extends

<script>
"use strict";

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

hello() {
console.log('Hello ' + this.name)
}
}

class PrimaryStudent extends Student {
constructor(name, grade) {
super(name);
this.grade = grade;
}

say() {
console.log('I‘m a primary student');
}
}
</script>

如图,extends本质也是原型链的思想

(注:下图Object原型后面还有连接的原型)

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_i++_08


【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_09

3.补充

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_10

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_i++_11


(1)得到原型类:

方法一:对象实例名._proto_

方法二:类名.prototype

三、Map和Set

二者为ES6的新增数据类型。

Map:(键值对)

常用方法:

  • set(‘key’, value):添加某个值。
  • delete(value):删除某个值。
  • get(‘key’):得到此键的某个值

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_12

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_13

Set:(去重的无序集合)

常用方法:

  • add(value):添加某个值。
  • delete(value):删除某个值。
  • has(value):某值是否在集合内。
  • clear():清除集合中所有元素。

四、Iterator(迭代器)

为ES6新特性。

1.遍历数组

普通的遍历方法如下:

for in:遍历下标

for of:遍历值

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_14

2.遍历Map

for of:遍历键+值

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_15

3.遍历Set

for of:遍历值

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_i++_16

五、JS中不完善的报错+手动改进(throw+【argument】+补充:【…rest】)

1.函数参数中报错不完善

例子:

<script>
"use strict";

function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
</script>

函数传递多个参数,或不传递参数,或是传递另一种类型的参数,都不会报错,最多是NaN值,从而可能导致间接报错,但不会出现直接报错

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_17


加一段判断参数类型的语句,来手动抛出异常(但也只能解决只传一个参数时的类型问题)

<script>
"use strict";

function abs(x) {
if (typeof x !== 'number') {
throw 'Not a Number~';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_原型对象_18


由于可利用JS的关键字arguments来接收多个函数参数,否则,若函数只定义了一个参数,则其它传入的参数(从第二个参数往后的所有参数),都不会参与到函数中,但却保留在arguments中

<script>
"use strict";

function abs(x) {
if (typeof x !== 'number') {
throw 'Not a Number~';
}

console.log('x=>' + x);
console.log('argument遍历');
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}

if (x >= 0) {
return x;
} else {
return -x;
}
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_19


因此,再次改进代码,即可写出一个报错完整的函数

<script>
"use strict";

function abs(x) {
if (typeof x !== 'number') {
throw 'Not a Number~';
}

if (arguments.length > 1) {
throw 'Please Input one arg~'
}

if (x >= 0) {
return x;
} else {
return -x;
}
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_20


补充:另外,还有一个…rest(ES6新增),写在函数定义的参数列表最后面,用于接收除已定义参数外的额外传入的参数

<script>
"use strict";

function talk(x, ...rest) {
let i;

for (i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
console.log('-----------------------')
console.log(rest);
}
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_21

2.var局部作用域中的bug

<script>
"use strict";

for (var i = 0; i < 2; i++) {
console.log('in: ' + i);
}
//仍然可以输出i
console.log('out: ' + i);
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_22


改进:将var改为let

<script>
"use strict";

for (let i = 0; i < 2; i++) {
console.log('in: ' + i);
}
//不可以输出i
console.log('out: ' + i);
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_js_23

六、使JS的原生方法失效+恢复

预先记录原方法引用,随后消除原方法:

<script>
"use strict";

let x = 'xxx';

var old_alert = window.alert;

window.alert = function (){

}

window.alert(123);
window.old_alert('old: ' + x);
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_24


当然,也可以设置恢复

<script>
"use strict";

let x = 'xxx';

var old_alert = window.alert;

window.alert = function (){

}

window.alert(123);
// window.old_alert('old: ' + x);

//恢复
window.alert = old_alert;
window.old_alert('re-old: ' + x);
</script>

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_25

七、多个js文件全局遍历冲突问题

问题:由于我们所有的全局变量都会绑定到window对象上,因此,如果使用不同的js文件,遇到相同名字的全局变量时,就会产生冲突。

解决方法:在每个js中都自定义一个唯一的全局变量对象,将这个脚本文件中的所有全局变量都赋予该全局变量对象,从而降低全局命名冲突的问题。(JQuery也参考了这个思想)

例如:

<script>
"use strict";

//唯一全局变量
var appT = {};

appT.name = 'zlc';
appT.add = function(x, y) {
return x + y;
}

//使用时
console.log(window.appT.add(1, 2));
</script>

八、方法与函数

(在讲述的时候需格外注意,不要将方法说成函数)

方法:

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_26


或是这样的形式

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_27

方法调用:obj.method(),需要加括号

函数:

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_全局变量_28


用函数的apply方法调用函数:格式:函数名.apply(对象, 参数列表);

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_i++_29

普通函数调用和apply函数调用,结果相同

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_30


直接调用getAge()函数将会报错

【JavaScript精华荟萃】精华汇总+易混淆知识(中篇)_javascript_31


因为函数中的this,都是指向调用此函数的对象,因此在实例中的getAge()函数,直接调用的话,也就是单纯作为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象,由于window对象中未设置birth属性,而函数体中又有this.birth,因此报错。最终用student对象调用此函数,this才会指向student对象,进而才会取到birth属性值。