Web前端开发工程师知识体系_22_JavaScript core
Posted 小马学前端
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web前端开发工程师知识体系_22_JavaScript core相关的知识,希望对你有一定的参考价值。
一、面向对象
1.继承
只要将方法定义放在构造函数中,那么每次new时都会执行function,这样就会反复创建相同函数的多个副本,导致浪费内存。如果将来发现多个子对象都要使用相同的功能和属性值时,都可以用继承来解决。
父对象中的成员,子对象无需重复创建就可直接使用,就像使用自己的成员一样,这就是继承。js中的继承都是通过原型对象实现的,原型对象就是替所有子对象集中保存共有属性值和方法的特殊父对象。当多个子对象需要使用相同的功能和属性值时,都可将相同的功能和属性值集中定义在原型对象中。
原型对象: 不用自己创建。在定义构造函数时,程序自动附赠我们一个空的原型对象。构造函数中都有一个自带的属性prototype,指向自己配对的原型对象——构造函数.prototype。
补充:new的四个作用 |
1. 创建一个新的空对象等待 |
2. 让子对象继承构造函数的原型对象(继承专用) |
3. 调用构造函数,将this替换为新对象,通过强行赋值方式为新对象添加规定的属性 |
4. 返回新对象地址 |
new的第二步自动让新创建的子对象,继承构造函数的原型对象。new会自动为子对象添加_ _proto_ _属性,指向构造函数的原型对象。
向原型对象中添加新的共有属性和方法时,只能是强行赋值:
构造函数.prototype.共有方法=function(){ ... } |
构造函数.prototype.共有属性=属性值 |
添加新属性或方法后,用子对象访问对象的成员时,js引擎先在子对象内部查找自有的属性;如果子对象没有,则js引擎会自动延_ _proto_ _属性去父元素查找。如果在父元素中找到了想要的属性或方法,则和访问子对象的方法一样调用。
示例: 将所有子对象共用的方法保存进构造函数里
<script>
// 构造函数
function Student(sname, sage) {
this.sname = sname;
this.sage = sage;
}
// 强行向student类型的原型对象(prototype)中添加一个所有子对象共用的的方法intr
Student.prototype.intr = function () {
console.log(`我叫${this.sname},我今年${this.sage}岁`);
}
// 用构造函数反复创建多个相同结构但内容不同的对象
var lilei = new Student("李雷", 45);
var hmm = new Student("韩梅梅", 30);
console.log(lilei);
console.log(hmm);
lilei.intr();
hmm.intr();
console.log(lilei.__proto__ == Student.prototype); //true说明原型对象是子元素的父级
console.log(hmm.__proto__ == Student.prototype); //true
</script>
· 自有属性和共有属性
自有属性:保存在子对象内部,只归当前子对象自有的属性; |
共有属性:保存在父对象(原型对象)中,归多个子对象共有的属性; |
(1)获取属性值时,毫无差别,都可用: 子对象.属性名。如果js引擎发现要使用的属性不在子对象中,则自动延_ _proto_ _属性向父对象继续查找要用属性。
(2)修改属性值
自有属性: | 子对象.属性名=属性值; |
共有属性: | 正确方法 | 错误方法 |
构造函数.prototype.共有属性=新值 | 子对象.共有属性=新值 | |
共有属性,必须用原型对象修改 | 此方法不但不会修改原型对象中的共有属性,而且还会给当前子对象添加一个同名的自有属性。从此,这个子对象,在这个属性的使用上,与其他子对象,再无法保持同步! |
示例: 为所有学生添加共有的班级名属性,并修改
<script>
function Student(sname, sage) {
this.sname = sname;
this.sage = sage;
}
Student.prototype.className = "初一二班";
var lilei = new Student("lilei", 11);
var hmm = new Student("hmm", 12);
// 修改自有属性
lilei.sname = "zhangsan";
console.log(lilei);
console.log(hmm);
console.log(lilei.className, hmm.className);
// 一年后,两位同学一起升级(修改共有属性)
// 1.错误方式
// lilei.className = "初二二班";
// 此方式不会修改原型对象中的共有属性,而会给当前子对象添加一个同名的自有属性,导致该子对象与其他子对象无法继续保持同步。
// 2.正确方式,必须修改原型对象
Student.prototype.className = "初二二班";
console.log(lilei.className, hmm.className);
</script>
· 内置类型的原型对象
内置类型就是ES标准中规定的,浏览器已经实现,我们可以直接使用的类型。包括十一种String、Number、Boolean、Array、Date、RegExp、Error、Function、Object、Math(不是类型,而是一个{ }对象)、global(全局作用域对象,在浏览器中被window代替)。
每种类型一定有2部分组成:构造函数,负责创建该类型的子对象;原型对象,负责为该类型所有子对象集中保存共有的属性值和方法定义。除Math和global之外,其余也都可以通过new创建子对象。
想要查看该类型中有哪些API,可以使用-- 类型名.prototype --。
如果经常使用的一个功能,但是原型对象中没有提供,我们可以自定义一个函数保存到原型对象中。
构造函数.prototype.新方法=function(){...} |
举例:为数组类型添加求和的方法
<script>
var array1 = [1, 2, 4, 5, 8, 8];
var array2 = [125, 48, 48, 478, 2584];
// 创建自定义函数
Array.prototype.sum = function () {
console.log("调用自定义函数sum");
// 数组求和套路
// 1.定义变量临时保持求和的值
var sum = 0;
// 2.遍历数组元素
for (i = 0; i < this.length; i++) {
// 3.将遍历出的元素值累加
sum += this[i];
}
// 4.返回累加结果
return sum;
}
// 调用sum
console.log(array1.sum());
console.log(array2.sum());
</script>
· 原型链
原型链是由多级父对象逐级继承形成的链式结构,保存着:一个对象可用的所有属性和方法,控制着属性和方法的使用顺序,采用就近原则,先子级后父级。
<script>
function Student(sname, sage) {
this.sname = sname;
this.sage = sage;
}
// 向Student原型对象中添加一个toString方法
Student.prototype.toString = function () {
// 此处this指将来调用这个toString()的点前的某个学生的类型的子对象.
return `{sname:${this.sname},sage:${this.sage}}`;
}
var lilei = new Student("lilei", 12);
var arr = [1, 2, 3];
var now = new Date();
console.log(lilei.toString());
console.log(arr.toString());
console.log(now.toString);
</script>
2.多态
多态指同一个函数,在不同情况下表现出不同的状态,包括重载和重写。
重载overload | 同一个函数,输入不同的参数,执行不同的逻辑。 |
重写override | 在子对象中定义一个和父对象中的成员同名的自有成员。当从父对象继承来的个别成员不好用时,就可以在子对象中定义同名成员,来覆盖父对象中的同名成员。 |
3.自定义继承
(1)只更换一个对象的父对象,两种方法:
子对象._ _proto_ _=新父对象 | 不推荐 |
Object.setPrototypeOf(子对象, 新父对象) | 推荐 |
//示例:更换一个对象的父对象
<script>
function Student(sname, sage) {
this.sname = sname;
this.sage = sage;
}
var lilei = new Student("李磊", 11);
var hmm = new Student("韩梅梅", 12);
var father = {
money: 1000000000000,
car: "infiniti"
}
// 1.只更换一个对象的父对象
// 更换hmm的继承父对象为father
// hmm.__proto__ = father;//不推荐
Object.setPrototypeOf(hmm, father); //推荐
console.log(hmm.money, hmm.car);
console.log(lilei.money, lilei.car);
console.log(lilei);
console.log(hmm);
</script>
(2) 批量更换多个子对象的父对象,只需要更换构造函数的prototype属性就可以,但必须在创建子对象之前更换!
//示例: 批量更换两个子对象的父对象
<script>
function Student(sname, sage) {
this.sname = sname;
this.sage = sage;
}
var father = {
money: 1000000000000,
car: "infiniti"
}
// 2.批量更换多个子对象的父对象
// 必须在创建子对象之前更换构造函数的原型对象
Student.prototype = father;
var lilei = new Student("李磊", 11);
var hmm = new Student("韩梅梅", 12);
console.log(hmm.money, hmm.car);
console.log(lilei.money, lilei.car);
console.log(lilei);
console.log(hmm);
</script>
二、ES5(ECMAScript 第5个版本)
1.严格模式
在旧的js中有很多广受诟病的缺陷,严格模式就是比旧的js运行机制要求更严格的新运行机制,今后企业中所有代码都要运行在严格模式下。启用严格模式只需在当前代码段的顶部添加: "use strict";即可。严格模式有四个新规定:
a:禁止给未声明过的变量赋值;旧的js中,如果强行给未声明过的变量赋值,不会报错,而是,自动在全局设置该变量,这就造成了全局污染。而严格模式中,强行给未声明过的变量赋值,会报错,这样就减少了因为写错变量名造成的全局污染!
举例:给未声明的局部变量赋值
<script>
function send() {
var gf;
// 假设不小心写错了变量名
qgf = "今晚308,w84u"; //直接报错qgf is not defined
console.log(`女朋友收到${gf}`);
}
send();
console.log(`全局中:${qgf}`);
</script>
未使用严格模式,打印如下,本来想给“女朋友”发送的消息,却发到了全局,但系统不报错。
启用严格模式,在代码段首行添加"use strict";
<script>
// 启用严格模式
"use strict";
// 禁止给未声明过的变量赋值
function send() {
var gf;
// 假设不小心写错了变量名
qgf = "今晚308,w84u"; //直接报错qgf is not defined
console.log(`女朋友收到${gf}`);
}
send();
console.log(`全局中:${qgf}`);
</script>
打印如下,此时直接报错,便于我们发现程序中的问题并修改。
b. 静默失败升级为错误;静默失败指程序运行不成功,但是也不报错,这样极其不利于调试程序,严格模式:会将绝大部分静默失败都升级为报错。举例:
<script>
// 静默失败升级为错误
var eric = {
aid: 1001,
sanme: "斯塔克"
}
// 这里将eid属性设置为只读
Object.defineProperty(eric, "eid", {
writable: false
})
// 试图篡改
eric.eid = 1002;
console.log(eric);
</script>
打印如下:即使将eid设置为只读,属性值仍被修改,但不报错。
启动严格模式后打印如下:
c. 普通函数调用中的this不再指window,而是指undefined,在旧js中普通函数调用中的this默认指window,极容易造成全局污染。启用严格模式后普通函数调用中的this指向undefined,不再指window,可防止因为错误使用this而导致的全局污染。举例:
<script>
// 启用严格模式
"use strict";
// 普通函数调用中的this不再指window,而是指undefined
function Student(sname, sage) {
console.log(this);
this.sname = sname; //报错 Cannot set property 'sname' of undefined
this.sage = sage;
}
var lilei = new Student("李蕾", 12);
// 假设忘记写new
var hmm = Student("韩梅梅", 13);
console.log(lilei);
console.log(hmm);
console.log(window);
</script>
d. 禁用了arguments.callee,arguments.callee;是在一个函数内,获得当前函数本身的一种特殊关键字(递归)。在函数内写死当前函数名,一旦外部函数名改变,内部函数名忘记修改,则程序立刻报错造成紧耦合,所以在函数内使用arguments.callee代替写死的函数名。在运行时,自动获得当前函数本身(松耦合)。
而且递归重复计算量太大,效率极低,如果递归调用严重影响程序的性能时,就要用循环来代替递归。举例:分别使用递归和循环实现计算斐波那契数列第n个数
//递归方式
<script>
// 禁用了arguments.callee
// 斐波那契数列
// 前两个数是都是1,从第三个数开始,每个数都是它相邻的前两个数的和
function f(n) {
if (n < 3) {
return 1;
} else {
return arguments.callee(n - 1) + arguments.callee(n - 2);
}
}
console.log(f(10));//55
</script>
//循环方式
<script>
function f(n) {
if (n < 3) {
return 1;
} else {
var f1 = 1,
f2 = 1,
fn;
for (var i = 3; i <= n; i++) {
fn = f1 + f2;
f1 = f2;
f2 = fn;
}
return fn;
}
}
console.log(f(10)); //55
</script>
补充:this 4种
(1)obj.fun() fun中的this指 .前的obj对象(谁调用指谁);
(2)new Fun() Fun中的this指new创建的新对象;
(3)fun() 或 (function(){ ... })() 或 回调函数 thisz默认指windozw;
(4)原型对象(prototype)中的this指将来调用这个共有函数的.前的某个子对象(谁调用指谁)。
往期javascript高级博文链接:
以上是关于Web前端开发工程师知识体系_22_JavaScript core的主要内容,如果未能解决你的问题,请参考以下文章