[[prototype]]机制就是指对象中的一个内部链接引用另一个对象,如果在第一个对象上没有找到需要的属性和方法,引擎就会在[[prototype]]关联的对象上继续查找,如果后者也没找到,则会继续查找后者的[[prototype]],以此类推,这一系列对象的链接称为原型链,JavaScript中的这个机制本质即使对象之间的关联关系
面向委托的设计
let Task={
setID:function (ID) {
this.id=ID;
},
outputID:function () {
console.log(this.id);
}
};
let XYZ=Object.create(Task);
XYZ.prepareTask=function (ID, label) {
this.setID(ID);
this.label=label;
};
XYZ.outputTaskDetails=function () {
this.outputID();
console.log(this.label);
};
互相委托是禁止的
调试,JavaScript规范并不会控制浏览器中的开发者工具对特定结构的表示方式
chrome
function Foo() {}
let a1=new Foo();
console.log(a1);//Foo {}
firefox
function Foo() {}
let a1=new Foo();
console.log(a1);//Object {}
chrome表示{}是一个空对象,由Foo构造
Firefox表示{}是一个空对象,由Object构造
chrome会动态跟踪实际执行构造过程的函数名
function Foo() {}
let a1=new Foo();
console.log(a1.constructor,a1.constructor.name);
function Foo() {}
let a1=new Foo();
Foo.prototype.constructor = function Gotcha(){};
console.log(a1.constructor,a1.constructor.name,a1);
function Foo() {}
let a1=Object.create(Foo);
console.log(a1);
Object.defineProperty(Foo,"constructor",{
enumerable:false,
value:function Gotcha() {}
});
console.log(a1);
如果不是用new来生成对象,chrome就无法跟踪对象内部的构造函数名称(,constructor.name)
(原型)面向对象风格
function Foo(who) {
this.me=who;
}
Foo.prototype.identify=function () {
return "i am "+this.me;
};
function Bar(who) {
Foo.call(this,who);
}
Bar.prototype=Object.create(Foo.prototype);
Bar.prototype.speak=function () {
console.log("hello, "+this.identify()+".");
};
let b1=new Bar("b1");
let b2=new Bar("b2");
b1.speak();
b2.speak();
对象关联风格
let Foo={
init:function (who) {
this.me=who;
},
identify:function () {
return "i am "+this.me;
}
};
Bar=Object.create(Foo);
Bar.speak=function () {
console.log("Hello, "+ this.identify()+".");
};
let b1=Object.create(Bar);
b1.init('b1');
let b2=Object.create(Bar);
b2.init('b2');
b1.speak();
b2.speak();
类与对象
纯JavaScript实现类风格代码
function Widget(width, height) {
this.width=width||50;
this.height=height||50;
this.$elem=null;
}
Widget.prototype.render=function ($where) {
if (this.$elem) {
this.$elem.css({
width:this.width+"px",
height:this.height+"px",
}).appendTo($where);
}
};
function Button(width, height, label) {
Widget.call(this,width,height);
this.label=label||"Default";
this.$elem=$("<button>").text(this.label);
}
Button.prototype=Object.create(Widget.prototype);
Button.prototype.render=function ($where) {
Widget.prototype.render.call(this,$where);
this.$elem.click(this.onclick.bind(this));
};
Button.prototype.onClick=function (evt) {
console.log("Button "+this.label+"clicked");
};
$(document).ready(function () {
let $body=$(document.body);
let btn1=new Button(125,30,"hello");
let btn2=new Button(150,40,"world");
btn1.render($body);
btn2.render($body);
});
使用ES6的class实现
class Widget {
constructor(width, height){
this.width=width||50;
this.height=height||50;
this.$elem=null;
}
render($where){
if (this.$elem) {
this.$elem.css({
width:this.width+"px",
height:this.height+"px",
}).appendTo($where);
}
}
}
class Button extends Widget{
constructor(width, height, label){
super(width,height);
this.label=label||"Default";
this.$elem=$("<button>").text(this.label);
}
render($where){
super($where);
this.$elem.click(this.onclick.bind(this));
}
onClick(evt) {
console.log("Button "+this.label+"clicked");
};
}
$(document).ready(function () {
let $body=$(document.body);
let btn1=new Button(125,30,"hello");
let btn2=new Button(150,40,"world");
btn1.render($body);
btn2.render($body);
});
对象关联风格委托实现,对象关联可以更好的支持关注分离原则,创建和初始化并不需要合并为一个步骤
let Widget={
init:function (width, height) {
this.width=width||50;
this.height=height||50;
this.$elem=null;
},
insert:function ($where) {
if (this.$elem) {
this.$elem.css({
width:this.width+"px",
height:this.height+"px",
}).appendTo($where);
}
}
};
let Button=Object.create(Widget);
Button.setup=function (width, height, label) {
this.init(width,height);
this.label=label||"Default";
this.$elem=$("<button>").text(this.label);
};
Button.build=function ($where) {
this.insert($where);
this.$elem.click(this.onclick.bind(this));
};
Button.onClick=function (evt) {
console.log("Button "+this.label+"clicked");
};
$(document).ready(function () {
let $body=$(document.body);
let btn1=new Button(125,30,"hello");
let btn2=new Button(150,40,"world");
btn1.render($body);
btn2.render($body);
});
更简洁的设计
对象关联除了能让代码看起来更简洁外,还可以通过行为委托模式简化代码结构
function Controller() {
this.errors = [];
}
Controller.prototype.showDialog = function(title,msg){
console.log(title + msg);
};
Controller.prototype.success = function (msg) {
this.showDialog("Success",msg);
};
Controller.prototype.failure = function (err) {
this.errors.push(err);
this.showDialog("Error",err);
};
function LoginController() {
Controller.call(this);
}
LoginController.prototype = Object.create(Controller.prototype);
LoginController.prototype.getUser = function () {
return document.getElementById("login_username").value;
};
LoginController.prototype.getPassword = function () {
return document.getElementById("login_password").value;
};
LoginController.prototype.validateEntry = function (user,pw) {
user = user || this.getUser();
pw = pw || this.getPassword();
if (!(user && pw)){
return this.failure(
"Please enter a username & password"
);
}else if (user.length < 5){
return this.failure(
"Password must be 5+ characters"
);
}
return true;
};
LoginController.prototype.failure = function (err) {
Controller.prototype.failure.call(
this,
"Login invalid: " + err
);
};
function AuthController(login) {
Controller.call(this);
this.login = login;
}
AuthController.prototype = Object.create(Controller.prototype);
AuthController.prototype.server = function (url, data) {
return $.ajax({
url:url,
data:data
});
};
AuthController.prototype.checkAuth = function () {
var user = this.login.getUser();
var pw = this.login.getPassword();
if (this.login.validateEntry(user,pw)){
this.server("/check-auth",{
user:user,
pw:pw
})
.then(this.success.bind(this))
.fail(this.failure.bind(this));
}
};
AuthController.prototype.success = function () {
Controller.prototype.success.call(this,"Authenticated!");
};
AuthController.prototype.failure = function (err) {
Controller.prototype.failure.call(
this,
"Auth Failed: " + err
);
};
let auth = new AuthController();
auth.checkAuth(new LoginController());
反类
let LoginController = {
errors:[],
getUser:function () {
return document.getElementById("login_username").value;
},
getPassword:function () {
return document.getElementById("login_password").value;
},
validateEntry:function (user, pw) {
user = user || this.getUser();
pw = pw || this.getPassword();
if (!(user && pw)){
return this.failure("Please enter a username & password!");
}else if(user.length < 5){
return this.failure("Password must be 5+ character!");
}
return true;
},
showDialog:function (title, msg) {
},
failure:function (err) {
this.errors.push(err);
this.showDialog("Error","Login invalid: " + err);
}
};
let AuthController = Object.create(LoginController);
AuthController.errors = [];
AuthController.checkAuth = function () {
let user = this.getUser();
let pw = this.getPassword();
if (this.validateEntry(user,pw)){
this.server("/check-auth",{
user:user,
pw:pw
})
.then(this.accepted.bind(this))
.fail(this.rejected.bind(this));
}
};
AuthController.server = function (url, data) {
return $.ajax({
url:url,
data:data
});
};
AuthController.accepted = function () {
this.showDialog("Success","Authenticated!");
};
AuthController.rejected = function (err) {
this.failure("Auth Failed: " + err);
}
更好的语法
ES6中的class可以简洁的定义类方法,但要避免使用
class Foo {
methodname(){}
}
对象关联中出现了很多function,也不简洁,ES6中提供了简洁方法声明(和class语法糖一样),委托可以用Object.setPrototypeOf(obj,pro)
let LoginController={
errors:[],
getUser(){
},
getPassword(){
}
};
反词法,简洁方法有一个小但是重要的缺点
let Foo={
bar(){},
baz:function baz() {}
};
去掉语法糖
let Foo={
bar:function(){},
baz:function baz() {}
};
由于函数对象本身没有标识符,所以实际上是一个匿名函数表达式赋值给属性,匿名函数没有名称标识符
- 调用栈更难追踪
- 自我引用更难
- 代码更难理解
但在这里主要是无法避免第2点,第一点由于简洁方法会给对应的函数对象设置一个内部的name属性,所有可以追踪
let Foo={
bar:function(x){
if (x < 10) {
return Foo.bar(x*2);
}
return x;
},
baz:function baz(x) {
if (x < 10) {
return baz(x*2);
}
return x;
}
};
上述情况下,可以通过对象的属性来引用,但当多个对象通过代理共享函数,使用this绑定,最好还是使用函数的name标识符
内省
instanceof,实际上是检查的对象的[[prototype]]链上是否有Foo.prototype对象
ES6中的class
class解决的问题
- 不再引用杂乱的.prototype
- 直接继承,不需要Object.create(..)
- 可以通过super(...)实现相对多态
- class只能声明方法,不能声明属性
- 通过extends自然扩展子类型,甚至是内置的类型
class陷阱
class只是现有[[prototype]]机制的一种语法糖
class不会像传统面向对象语言一样在声明时静态复制所有行为,如果修改了父类的方法,则子类也会受到影响
class C {
constructor(){
this.num=Math.random();
}
rand(){
console.log("Random: "+this.num);
}
}
let c1=new C();
c1.rand();
C.prototype.rand=function () {
console.log("Random: "+Math.round(this.num*1000));
};
let c2=new C();
c2.rand();
c1.rand();
class无法定义类成员属性(只能定义方法),只有使用.prototype
class C {
constructor(){
C.prototype.count++;
console.log("Hello: "+this.count);
}
}
C.prototype.count=0;
let c1=new C();
let c2=new C();
console.log(c1.count===2,c1.count===c2.count);
此外,class语法仍然面临意外屏蔽的问题
class C {
constructor(id){
this.id=id;
}
id(){
console.log("Id: "+id);
}
}
let c1=new C("c1");
c1.id();
super也存在一些问题