只要将方法定义放在构造函数中,那么每次 new 时都会执行 function,这样就会反复创建相同函数的多个副本,导致浪费内存。如果将来发现多个子对象都要使用相同的功能和
属性值时,都可以用继承来解决。
父对象中的成员,子对象无需重复创建就可直接使用,就像使用自己的成员一样,这就是继承。js 中的继承都是通过原型对象实现的,原型对象就是替所有子对象集中保存共有
属性值和方法的特殊父对象。当多个子对象需要使用相同的功能和属性值时,都可将相同的功能和属性值集中定义在原型对象中。
原型对象不用自己创建,在定义构造函数时,程序自动附赠我们一个空的原型对象。构造函数中都有一个自带的属性 prototype,指向自己配对的原型对象——构造函数
.prototype。
new 的第二步自动让新创建的子对象,继承构造函数的原型对象。new 会自动为子对象添加_ _proto_ _ 属性,指向构造函数的原型对象。
向原型对象中添加新的共有属性和方法时,只能是强行赋值:
添加新属性或方法后,用子对象访问对象的成员时,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)修改属性值
示例: 为所有学生添加共有的班级名属性,并修改;
<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 -- 。
如果经常使用的一个功能,但是原型对象中没有提供,我们可以自定义一个函数保存到原型对象中。
举例:为数组类型添加求和的方法
<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>
多态指同一个函数,在不同情况下表现出不同的状态,包括重载和重写。
(1)只更换一个对象的父对象,两种方法:
//示例:更换一个对象的父对象
<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>
在旧的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指将来调用这个共有函数的.前的某个子对象(谁调用指谁)。
阅读量:362
点赞量:0
收藏量:0