共计 4191 个字符,预计需要花费 11 分钟才能阅读完成。
本篇主要是记录一下对 js 中对于原型的理解 …
原型
原型涉及到构造函数, 原型对象, 实例化对象三者之间的关系 …
-
构造函数
function Person (name,age) {//(1)创建一个空对象:{} //(2)将 this 指向这个空对象 : this = {} //(3)执行构造函数赋值代码(完成对象的赋值)this.name = name; this.age = age; this.sayHi = function () {console.log(this.name + 'hello world'); }; //(4)返回这个对象 }; var man = new Person('huahua',18); 1. 何为构造函数? 构造函数:首先,它是函数,并且任何的函数都可以作为构造函数存在,它的本质是初始化对象。构造函数都是和 new 关键词一起使用的。new 就是在创建对象,从声明开始一共做了 4 件事(如上),构造函数就是在为初始化的对象添加属性和方法(成员) 2. 构造函数的特点:a: 构造函数的首字母必须大写,用来区分于普通函数 b: 内部使用的 this 对象,来指向即将要生成的实例对象 c: 使用 New 来生成实例对象
-
实例对象
1. 上面的 man 就是通过 Person 这个构造函数实例化出来一个对象, 我们称为 ** 实例化对象 **; 何为对象的实例化呢? 2. 在我看来就是给一个空对象添加了一些属性和方法, 使其具有了一些特征和行为... 也就是上面 new 关键字干的事; 跟面向对象中的一些概念比较类似... 面向对象编程: 面向对象就是对现实中的事物进行抽象化... 然后再给其设置特征属性和行为使之具体化; 面向对象就是对面向过程进行封装后的结果... 3. 实例对象中存在一个__proto__属性; 这个属性指向了构造函数的原型 prototype... 注意: 实例对象访问成员的规则:先看自己有没有这个成员,如果有则访问,没有则访问原型的
-
原型对象
上面已经聊过构造函数和实例化对象了, 那么原型对象又是什么呢? 当我们在声明一个函数时, 系统会帮我们创建一个与该函数对应的属性 prototype, 我们称它为原型对象; 以上面的 Person 为例, 这个 prototype 是该函数的一个属性, 我们可以调用 Person.prototype 来修改其成员或者进行重写; 原型对象中有一个构造器指针 constructor 属性来指向对应的构造函数, 他的作用是可以让实例对象知道自己是哪一个构造函数生成的; 如 man.constructor 即 man.__proto__.constructor 指向了 Person.
-
下面用一张图来表示他们之间的关系 …
原型对象中可以存储很多成员属性和方法, 多个实例对象之间就能共享这些属性和方法; 类似实现了面向对象中 继承 的效果 …
面向对象的三大特性:
封装:将功能代码封装到对象中,只暴露外部接口(API), 使用者无需关心内部实现 继承:一个对象拥有另一个对象所有的成员变量(属性和方法) 多态: 一个对象在不同情况下的多种状态; 一个对象经过不同操作后会有不同的行为.... (js 从语法的角度上来说没有多态,因为 js 是基于对象的语言) js 实现继承的方式: 1. 我们可以遍历父对象, 将父对象的属性动态添加到子对象中 (适用于一个子对象的继承) for (var key in father){son[key] = father[key]; }; 2. 替换原型:将父对象作为子对象构造函数的原型(但是会丢失之前的原型对象的成员) // 子对象用构造函数来实例化 function Son(name, age) { this.name = name; this,age = age; } Son.prototype.father = { parent: 'laosong', age: 47, } // Son 原型对象中的成员 var son = new Son('xiaowang', 24) var father = { name: 'laowang', age: 48, } Son.prototype = father; // 相当于 Son 的原型被重新赋值, 替换了,laosong 不在了 3. 综合上面两种情况: 将父对象的成员动态添加到子对象的原型中, 这样就不会丢失了 for (var key in father){Son.prototype[key] = father[key]; }; /** 混合式继承封装 @param method: 子对象的构造函数 @param father: 要继承的父对象 */ function extendMehtd (method,father) {for (var key in father){method.prototype[key] = father[key]; } }; 4. 构造函数实现继承 // 通过更改 this 的指向来实现 function Person(name, age) { this.name = name || 'hello'; this.age = age || 200; }; function Stu(sex, name, age) { this.sex = sex; // 调用 Person 构造函数,修改 Person 中的 this 指向为当前 Student 这个构造函数中 new 创建的对象 // 继承 Person 中默认的初始化属性 Person.call(this, name, age); }; var s = new Stu('male'); console.log(s); // age: 200,name: "hello",sex: "male"
原型链
js 中, 每一个实例对象都存在一个__proto__属性指向了自己的原型 prototype; 但是原型本身也是一个对象,
也有自己的__proto__属性, 指向自己的原型,以此类推就形成一个链式结构,称之为原型链 …对象访问原型链中成员规则:就近原则
先看对象自己有没有,有则访问,没有则看原型有没有,有则访问,没有则看原型的原型有没有,以此类推 … 直到原型链的终点(null);
如果还没有:如果是访问属性:则返回 undefined 如果访问的是方法:则会报错 xxxx is not a function
- 图解原型链
以数组对象为例:
- DOM 中的原型链
图解完整原型链
以上便是 JS 中完整的原型链图解了 …
关于对象的一些知识点补充
1. 静态成员和实例成员
静态成员: 函数对象持有的成员(属性, 方法)
实例成员: 构造函数实例化出来的对象持有的成员
2.instanceof 关键字
语法: 对象 instanceof 构造函数
作用: 用来检测右边函数的原型 是否 在左边对象的原型链中(true/false)
如: Object instanceof Object // true
3.Object.prototype(对象原型)
-- 所有对象的原型链中都会指向它; 所以所有的对象都可以访问 Object.prototype 原型中的成员
常用几个成员:
1.hasOwnProperty(): 检查对象是否包含某个成员;
条件: 自己的成员
2.isPrototypeOf(): 检查 (左边) 一个对象是不是 (右边) 另一个对象的原型
3.propertyIsEnumerable(): 检查对象是否可以枚举某个属性
条件:(1)是自己的成员(2)可以被 for-in 循环遍历(自己的和原型的)4.Function.prototype(函数对象 Function 的原型)
-- 所有的函数对象原型都会指向 Function 构造函数的原型, 所有的函数对象都可以访问 Function.prototype 中的成员
常用的一些成员:
1. name: 获取函数名 (比较鸡肋)
2.caller: 获取调用本函数的引用; 通过 console.log(fn.caller)可以知道自己在哪个地方被人调用(全局调用函数, 这里的 caller 指向 null)
3.length:获取函数形参的数量; fun.length 可以知道函数设置的形参个数
4.arguments: 获取函数所有的实参;
可以理解为函数内部一个隐藏的形参, 作用是获取函数所有的实参,与形参一一对应...
arguments 对象的两个常用属性:
1.callee: 指向函数自身, 应用于匿名函数的递归调用...
arguments.callee === fn //true
2. length: 实参的个数
arguments 是一个伪数组...
5. 给内置的构造函数原型添加自定义成员
当内置构造函数自带的方法不够用,无法实现需求时, 我们就需要给添加自定义方法; 直接添加可能会出现多个人员操作出现相同的方法名, 导致被覆盖掉了
所以需要采用安全的方法添加来避免覆盖...
使用替换原型继承(自定义构造函数,将原型指向内置对象)
// 通过构造函数的方式来添加;
function NewArr(name) {this.name = name;};
NewArr.prototype = []; // 修改为一个空数组对象; 此时 NewArr 的原型拥有数组对象所有的方法
NewArr.prototype.hello = {name: 'hello world',};
NewArr.prototype.min = function () {
var min = Infinity;
for(var i=0; i< this.length; i++) {if (this[i] < min) {min = this[i];
}
};
return min;
};
// 创建一个新对象
var arr1 = new NewArr('huhua');
var arr3 = new NewArr();
arr3.push(1,2,3,-1);
console.log(arr3.min());
console.log(arr1);
console.log(arr1.__proto__);
console.log(NewArr);
console.log(NewArr.prototype);
console.log(NewArr.__proto__.constructor);
var arr2 = [12,241,21];
console.log(arr2.min()); // 不能访问
Array.prototype:对象类型赋值的时候拷贝的是地址,修改了 NewArr 的原型之后,Array.prototype 也会修改
[]:由于空数组的原型会指向 Array.prototype,根据原型链中成员访问规则,NewArr 实例对象可以访问数组成员的成员
并且,修改 MyArr 的原型对象,本质上是修改这个空数组,不会对 Array.protpotype 造成影响
正文完
发表至: javascript
2019-08-16