一、几个重点
- 对象
- 原型
- 实例属性与原型属性
- 继承
二、详细介绍
2.1 对象
对象的创建方法有以下:
- 字面量
- Object.Create()
2.2 原型
几乎所有对象都有一个prototype的默认属性。prototype属性的值,也是一个对象,它被称为“原型对象”,所以我们可以通过prototype属性,给“原型对象”添加其他一些属性。
原型链
查找属性时会用到,先看一个简单的例子,后面我们会详细介绍。
function Player() {
}
Player.prototype.userBat = function () {
return true;
}
Player.prototype.getPlayerName = function () {
return "ygm"
}
//以一般函数的形式调用,并没有实例化对象,所以obj不是一个对象
//(实际打印结果为undefined,因为函数没有返回值),所以没有prototype
var obj = Player();
//TypeError: Cannot read property 'getPlayerName' of undefined
//console.log(obj.getPlayerName());
//以构造函数的形式调用,得到一个实例对象
var obj1 = new Player();
console.log(obj1.getPlayerName()); //ygm
console.log(obj1.userBat()); //true
//如果改成下面
function Player() {
this.getPlayerName = function() {
return "sub ygm"
}
}
Player.prototype.getPlayerName = function () {
return "ygm"
}
var obj1 = new Player();
console.log(obj1.getPlayerName()); //sub ygm 这就是原型链的效果
2.3 实例属性和原型属性
- 理解原型属性和实例属性
从上面的例子可以看出,当同一个属性同时出现在构造函数的属性列表和原型的属性列表中,构造函数的属性优先级更高,结果是构造函数的实例对象的对应属性指向的方法被调用。这是因为程序运行过程在查找原型链时,是从下往上查找的。【对比作用域链,它们的逻辑很类似,作用域链则是从内向外查找变量的,仅当补充,具体可以查看作用域链相关的文章】。
我们还可以这么理解:构造函数的属性,和prototype指向的“原型对象”的属性,都绑定到了构造函数的实例对象上。而其中相同的属性被加以区分,区分的原则是:这个属性来自构造函数还是来自prototype,对他们标上优先级,来自构造函数的优先级要高于来自prototype的同名属性。【但这种理解只是辅助理解,实际并非如此,请继续看下去】
//如果改成下面
function Player() {
this.getPlayerName = function() {
return "sub ygm"
}
}
var obj1 = new Player();
console.log(obj1.getPlayerName()); //sub ygm 这就是原型链的效果
console.log(obj1.userBat()); //true
- 理解关键字【this】
理解【this指向谁】的问题,主要抓住以下3个方面:
当this用于全局上下文中,this会被绑定到全局上下文。
function global() { return this; } //在全局上下文中被调用 //console.log(global()); //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
当this用于对象方法中,this被赋值或绑定到包含对象上
【注意】:当对象存在嵌套时,包含对象是哪个直接的父对象(也就是包含对象是指直接包含的那个对象)
var obj = { name:"obj", func:function(){ return this; } } //this用于对象方法中,thisz被赋值或绑定包含对象上 // obj是此时的包含对象 console.log(obj.func()); //{name: "obj", func: ƒ} //对象嵌套的情况 var obj = { name:"obj", func:function(){ return { name:"subObj", func:function(){ return this; } } } } //此时被返回的对象才是包含对象 console.log(obj.func()); //{ name: 'subObj', func: [Function: func] }
当this用于构造函数中,this指向被构造出来的对象。
var menber = "global member"; //this用于构造函数中,this指向被构造的实例对象 function constructor() { this.menber = "local member"; } function constructor1() { console.log("global var is:", this.menber);//global var is: global member this.menber = this.menber + " changed"; console.log("after chaneged, global var is:", this.menber); //after chaneged, global var is: global member changed } var obj_cst = new constructor(); console.log(obj_cst.menber);//local member // 函数调用,它的this指向的是全局上下文 constructor1(); console.log(menber)//global member changed
如果没有上下文,默认情况下this会绑定到全局上下文。
2.4 函数作用域实现访问控制
JavaScript是如何控制对象的可见性及其访问方式的呢?我们知道JavaScript并没有访问控制修饰器(public等),但是,之前我么学过的闭包与函数作用域等机制就起到了帮助,有了这些,我们可以完成对象的访问控制。
function Foo(name) {
//私有变量
var local = "local variance";
//private方法
function countPlus() {
count++;
}
var count = 0;
//public方法
this.setLocal= function(name) {
local = name;
countPlus();
}
this.getLocal = function (name) {
return local;
}
//public 属性
this.onwer = "you name";
}
//公共属性
Foo.prototype.count = 0;
//公共方法
Foo.prototype.setCount = function () {
this.count = 0;
}
建议使用控制台,打印看看,Foo.prototype, obj.__proto__
分别是什么。
2.5 理解JavaScript的继承
通过将对象的实例作为原型来实现
function Person() {} Person.prototype.hasName = function () { console.log("yes"); } function Child() { } Child.prototype = new Person(); var child = new Child(); console.log(child instanceof Child);//true console.log(child instanceof Person);//true console.log(child instanceof Object);//true child.hasName()//yes
- 更复杂的继承
function Employee() {
this.name = "";
this.dept = "None";
this.salary = 0.00;
}
function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
function IndividualContributor() {
Employee.call(this);
this.active_projects = [];
}
IndividualContributor.prototype = Object.create(Employee.prototype);
function TeamLead() {
Manager.call(this);
this.dept = "Software";
this.salary = 10000;
}
TeamLead.prototype = Object.create(Manager.prototype);
function Engineer() {
TeamLead.call(this);
this.dept = "Javascript";
this.desktop_id = "007";
this.salary = 8000;
}
Engineer.prototype = Object.create(TeamLead.prototype);
var emp = new Employee();
console.log(emp);
//Employee { name: '', dept: 'None', salary: 0 }
var manager = new Manager();
manager.name = "ygm";
manager.reports = ["cat", "fat", "hat"];
console.log(manager);
// Employee {
// name: 'ygm',
// dept: 'None',
// salary: 0,
// reports: [ 'cat', 'fat', 'hat' ] }
var teamleader = new TeamLead();
teamleader.name = "Json";
console.log(teamleader);
//Employee { name: 'Json', dept: 'Software', salary: 10000, reports: [] }
var engineer = new Engineer();
engineer.name = "凌凌漆";
console.log(engineer);
// Employee {
// name: '凌凌漆',
// dept: 'Javascript',
// salary: 8000,
// reports: [],
// desktop_id: '007' }
关于Obejct.call(this),举例理解。
function Manager() {
Employee.call(this);
this.reports = [];
}
当使用new来创建一个Manager实例对象的时候,Manager里面的this指向的就是Manager实例。call(this)其实就是使用指定的上下文来调动函数,这里调用的是Employee的构造函数。换句话说,call(this)允许指定函数运行时,其内部的this指向哪个对象。当我们创建Manager对象时,this就指向了Manager的实例对象,因此
function Employee() {
this.name = "";
this.dept = "None";
this.salary = 0.00;
}
Employee中的this指向的是Manager对象,通过如此,Employee的属性就绑定到了Manager对象上了,构成了继承的效果。其实这一操作就相当于java
中的super()
。
关于Manager.prototype = Object.create(Employee.prototype);
。我们前面是使用的new来创建一个父对象,将父对象复制给子构造函数的prototype属性,如此来完成继承。
但是,考虑到调用父构造函数时,其内部的逻辑会被执行,有时候我们不需要执行这些内部逻辑,因此我们希望在继承的时候这些逻辑不被执行,而Manager.prototype = Object.create(Employee.prototype);
就满足这样的要求。不需要调用构造函数,通过将父构造函数的原型传给Object.create
也可以完成原型链的创建。