zoukankan      html  css  js  c++  java
  • JS的对象原型

    1.对象

    1.1 语法

    对象可以通过两种形式定义:声明(文字)形式和构造形式。
    对象的文字语法:

    var myObj = {
    	key : value
    	//...
    };
    

    对象的构造语法:

    var myObj = new Object();
    myObj.key = value;
    

    1.2 类型

    对象是JavaScript的基础。在JavaScript中一共有六种主要类型(术语是“语言类型”):

    • string
    • number
    • boolean
    • null
    • undefined
    • object

    函数是对象的一个子类型(从技术角度来说就是“可调用的对象”)。
    数组也是对象的一种类型,具备一些额外的行为。

    JavaScript中还有一些对象子类型,通常被称为内置对象。

    • String
    • Number
    • Boolean
    • Object
    • Function
    • Array
    • Date
    • RegExp
    • Error

    在JavaScript中,它们实际上只是一些内置函数,这些内置函数可以当作构造函数来使用。
    相关阅读:JavaScript 参考手册

    var strPrimitive = "I am a string";
    typeof strPrimitive;//"string"
    strPrimitive instanceof String;//false
    

    原始值 "I am a string"并不是一个对象,它只是一个字面量,并且是一个不可变的值。如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为String对象。
    幸好,在必要时语言会自动把字符串字面量转换成一个String对象,也就是说你并不需要显式创建一个对象。

    var strPrimitive = "I am a string";
    console.log(strPrimitive.length);//13
    console.log(strPrimitive.charAt(3));//"m"
    

    我们可以直接在字符串字面量上访问属性或者方法,因为引擎自动把字面量转换成String对象,所以可以访问属性和方法。
    对于数值字面量和布尔字面量也是如此。
    nullundefined没有对应的构造形式,它们只有文字形式。
    Date只有构造,没有文字形式。
    对于Object、Array、Function和RegExp来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。
    Error对象很少在代码中显式创建,一般是在抛出异常时被自动创建。

    1.3 内容

    对象的内容是由一些存储在特定命名位置的值组成的,我们称之为属性
    在引擎内部,这些值的存储方式是多种多样的,一般并不会存在对象容器内部。
    存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置。

    var myObject = {
    	a : 2
    };
    
    myObject.a;//2
    myObject["a"];//2
    

    如果要访问myObject中a位置上的值,我们需要使用.操作符或者[]操作符。
    .a语法通常被称为“属性访问”, ["a"]语法通常被称为“键访问”。
    .操作符要求属性名满足标识符的命名规范,而[".."]语法可以接受任意UTF-8/Unicode字符串作为属性名。
    在对象中,属性名永远都是字符串。如果你使用string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。

    ES6增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名。

    var prefix = "foo";
    
    var myObject = {
    	[prefix + "bar"] : "hello",
    	[prefix + "baz"] : "world"
    };
    
    myObject["foobar"];//hello
    myObject["foobaz"];//world
    

    数组支持[]访问形式。

    var myArray = ["foo",42,"bar"];
    
    myArray.length;//3
    myArray[0];//"foo"
    myArray[2];//"bar"
    

    数组也是对象,所以虽然每个下标都是整数,你仍然可以给数组添加属性:

    var myArray = ["foo",42,"bar"];
    
    myArray.baz = "baz";
    myArray.length;//3
    myArray.baz;//"baz"
    

    你可以把数组当作一个普通的键/值对象来使用,并且不添加任何数值索引。
    但最好只用对象来存储键/值对,只用数组来存储数值下标/值对。
    注意:如果你试图向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成一个数值下标。

    var myArray = ["foo",42,"bar"];
    
    myArray["3"] = "baz";
    myArray.length;//4
    myArray[3];//"baz"
    

    复制对象

    对于JSON安全的对象来说,有一种巧妙的复制方法:var newObj = JSON.parse(JSON.stringify(someObj))
    ES6定义了Object.assign(..)方法来实现浅复制。

    属性描述符

    从ES5开始,所有的属性都具备了属性描述符。
    在创建普通属性时属性描述符会使用默认值,我们也可以使用Object.defineProperty(..)来添加一个新属性或者修改一个已有属性并对特性进行设置。

    var myObject = {}
    Object.defineProperty(myObject,"a",{
    	value:2,
    	writable:true,
    	configurable:true,
    	enumerable:true
    });
    myObject.a;//2
    //writable:是否可以修改属性的值
    //configurable:是否可以配置
    //enumerable:属性的值是否可以被枚举
    

    遍历

    var myArray = [1,2,3];
    for(var i = 0; i < myArray.length; i++){
    	console.log(myArray[i]);
    }//1 2 3
    

    这实际上并不是在遍历值,而是遍历下标来指向值。
    ES5中增加了一些数组的辅助迭代器,包括forEach(..)every(..)some(..)

    ES6增加了一种用来遍历数组的for..of循环语法。

    var myArray = [1,2,3];
    for(var v of myArray){
    	console.log(v);
    }
    //1
    //2
    //3
    

    for..of循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。

    var myArray = [1,2,3];
    var it = myArray[Symbol.iterator]();
    
    it.next();//{value:1,done:false}
    it.next();//{value:2,done:false}
    it.next();//{value:3,done:false}
    it.next();//{done:true}
    

    我们使用ES6中的符号Symbol.iterator来获取对象的iterator内部属性。
    和数组不同,普通的对象没有内置的iterator,所以无法自动完成for..of遍历。

    2.原型

    2.1 [[Prototype]]

    JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[Prototype]]属性都会被赋予一个非空的值。

    var myObject = {
    	a : 2
    };
    
    myObject.a;//2
    

    当我们试图引用对象的属性时会触发[[Get]]操作,比如myObject.a。对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。但是如果a不在myObject中,就需要使用对象的[[Prototype]]链了。
    对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[Prototype]]链。

    var anotherObject = {
    	a : 2
    };
    
    var myObject = Object.create(anotherObject);
    
    myObject.a;//2
    

    现在myObject对象的[[Prototype]]链关联到了anotherObject。显然myObject.a并不存在,但是尽管如此,属性访问仍然成功地找到了值2。但是,如果anotherObject中也找不到a并且[[Prototype]]链不为空的话,就会继续查找下去。

    Object.prototype

    所有普通的[[Prototype]]链最终都会指向内置的Object.prototype
    所有的“普通”对象都把[[Prototype]]链的顶端设置为这个Object.prototype对象,所以它包含JavaScript中许多通用的功能。

    属性设置和屏蔽

    myObject.foo = "bar"
    如果myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。
    如果foo不是直接存在于myObject中,[[Prototype]]链就会被遍历,类似[[Get]]操作。如果原型链上找不到foo,foo就会被直接添加到myObject上。

    如果属性名foo既出现在myObject中,也出现在myObject上的[[Prototype]]链上层,那么就会发生屏蔽。myObject中包含的foo属性会屏蔽原型链上层的所有属性,因为myObject.foo总是会选择原型链中最底层的foo属性。

    屏蔽比我们想象中更加复杂。
    通常来说,应当尽量避免使用屏蔽。

    var anotherObject = {
    	a : 2
    };
    
    var myObject = Object.create(anotherObject);
    
    anotherObject.a;//2
    myObject.a;//2
    
    anotherObject.hasOwnProperty("a");//true
    myObject.hasOwnProperty("a");//false
    
    myObject.a++;//隐式屏蔽!
    
    anotherObject.a;//2
    myObject.a;//3
    
    myObject.hasOwnProperty("a");//true
    

    2.2 “类”

    所有的函数默认都会拥有一个名为prototype的公有并且不可枚举的属性,它会指向另一个对象。这个对象通常被称为Foo的原型。

    Foo.prototype默认有一个公有并且不可枚举的属性.constructor,这个属性引用的是对象关联的函数。

    在JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用。
    函数不是构造函数,但是当且仅当使用new时,函数调用会变成“构造函数调用”。

    2.3 (原型)继承

    function Foo(name){
    	this.name = name;
    }
    
    Foo.prototype.myName = function(){
    	return this.name;
    };
    
    function Bar(name,label){
    	Foo.call(this,name);
    	this.label = label;
    }
    
    //我们创建了一个新的Bar.prototype对象并关联到Foo.prototype
    Bar.prototype = Object.create(Foo.prototype);
    
    Bar.prototype.myLabel = function(){
    	return this.label;
    };
    
    var a = new Bar("a","obj a");
    
    a.myName();//"a"
    a.myLabel();//"obj a"
    

    这段代码的核心部分是Bar.prototype = Object.create(Foo.prototype)。调用Object.create(..)会凭空创建一个“新”对象并把新对象内部的[[Prototype]]关联到你指定的对象。即创建一个新的Bar.prototype对象并把它关联到Foo.prototype

    在ES6之前,我们通过设置._proto_属性来修改对象的[[Prototype]]关联,但是这个方法并不是标准并且无法兼容所有浏览器。
    ES6添加了辅助函数Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。

    2.4 对象关联

    [[Prototype]]机制是存在于对象中的一个内部链接,它会引用其他对象。
    这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推,这一系列的链接被称为“原型链”。

    创建关联

    var foo = {
    	something : function(){
    		console.log("Tell me something good...");
    	}
    };
    
    var bar = Object.create(foo);
    
    bar.something();//Tell me something good...
    

    Object.create(..)会创建一个新对象(bar)并把它关联到我们指定的对象(foo),这样我们就可以充分发挥[[Prototype]]机制的威力(委托)并且避免不必要的麻烦(比如使用new的构造函数调用会生成.prototype.constructor引用)。

    3.行为委托

    Task = {
    	setID : function(ID){this.id=ID;},
    outputID : function(){console.log(this.id);}
    };
    
    //让XYZ委托给Task
    XYZ = Object.create(Task);
    
    XYZ.prepareTask = function(ID,Label){
    	this.setID(ID);
    	this.label = Label;
    };
    
    XYZ.outputTaskDetails = function(){
    	this.outputID();
    	console.log(this.label);
    };
    

    XYZ通过Object.create(..)创建,它的[[Prototype]]委托了Task对象。
    这种编码风格称为“对象关联”(OLOO)。

    比较思维模型

    模仿类的行为

    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(){
    	alert("Hello, " + this.identify() + ".");
    };
    
    var b1 = new Bar("b1");
    var b2 = new Bar("b2");
    
    b1.speak();
    b2.speak();
    

    对象关联风格

    Foo = {
    	init: function(who){
    		this.me = who
    	},
    	identify: function(){
    		return "I am " + this.me;
    	}
    };
    
    Bar = Object.create(Foo);
    
    Bar.speak = function(){
    	alert("Hello, " + this.identify() + ".");
    };
    
    var b1 = Object.create(Bar);
    b1.init("b1");
    var b2 = Object.create(Bar);
    b2.init("b2");
    
    b1.speak();
    b2.speak();
    

    参考资料:《你不知道的JavaScript》(上卷) 第二部分 this和对象原型

  • 相关阅读:
    TDSSNIClient initialization failed with error 0x7e, status code 0x60.
    SourceSafe Outof Memory
    机器学习(Part I)机器学习的种类
    机器学习PartIII:测试算法和NFL定理
    Google架构学习
    MediaWiki安装问题总结
    [转]IT项目管理实务
    几本关于统计学习的书
    Googlebot开始检索网站深层内容
    中文搜索引擎技术之网页排序
  • 原文地址:https://www.cnblogs.com/gzhjj/p/9018616.html
Copyright © 2011-2022 走看看