zoukankan      html  css  js  c++  java
  • JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)

    一、对象

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

    var myObj = {
    	key: value
    	// ...
    };
    
    或:
    
    var myObj = new Object();
    myObj.key = value;
    

    对象还有一些内置对象: String、Number、Boolean、Object、Function、Array、Date、RegExp、Error。

    访问对象属性可以使用. 操作符(属性访问)或者[] 操作符(键访问),区别在于:于. 操作符要求属性名满足标识符的命名规范,而[".."] 语法
    可以接受任意UTF-8/Unicode 字符串作为属性名。

    属性描述符

    ES5 开始,JS所有的属性都具备了属性描述。

    1、 Writable(是否可修改属性)
    var myObject = {};
    Object.defineProperty( myObject, "a", {
    	value: 2,
    	writable: false, // 不可写!
    	configurable: true,
    	enumerable: true
    } );
    myObject.a = 3;      (严格模式会报错)
    myObject.a; // 2
    
    2、 Configurable

    只要属性是可配置的,就可以使用defineProperty(..) 方法来修改属性描述符。但是有一个例外:即便属性是configurable:false, 我们还是可以把writable 的状态由true 改为false,但是无法由false 改为true。除了无法修改,configurable:false 还会禁止删除这个属性。

    3、 Enumerable(是否可枚举)

    表示能否通过for-in 循环返回属性

    4、 访问描述符(Getter、Setter)

    当你给一个属性定义getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的value 和writable 特性,取而代之的是关心set 和get(还有configurable 和enumerable)特性。

    遍历

    for..in 循环可以用来遍历对象的可枚举属性列表(包括[[Prototype]] 链),实际上并不是在遍历值,而是遍历下标来指向值。

    ES5 中增加了一些数组的辅助迭代器,包括forEach(..)、every(..) 和some(..)

    ES6 增加了一种用来遍历数组for..of 循环语法(如果对象本身定义了迭代器的话也可以遍历对象),直接遍历值而不是数组下标。数组有内置的@@iterator,因此for..of 可以直接应用在数组上,普通的对象没有内置的@@iterator,所以无法自动完成for..of 遍历。

    关于new操作符

    调用构造函数实际上会经历以下4个步骤:

    (1) 创建一个新对象;

    (2) 让空对象的__proto__(IE没有该属性)成员指向了构造函数的prototype成员对象;

    (3) 使用apply调用构造器函数,this绑定到空对象obj上;

    (4) 返回新对象。

    function NEW_OBJECT(Foo){
    
    	var obj={};
    	obj.__proto__=Foo.prototype;
    	obj.__proto__.constructor=Foo;
    	Foo.apply(obj,arguments)
    	return obj;
    
    }
    

    二、原型

    1. [[Prototype]]

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

    当我们试图访问一个对象下的某个属性的时候,会在JS引擎触发一个GET的操作,首先会查找这个对象是否存在这个属性,如果没有找的话,则继续在prototype关联的对象上查找,以此类推。如果在后者上也没有找到的话,继续查找的prototype,这一系列的链接就被称为原型链。

    2. property

    只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性。

    3. constructor

    ,对象的.constructor 会默认指向一个函数,这个函数可以通过对象的.prototype引用,.constructor 并不是一个不可变属性。它是不可枚举的,但是它的值是可写的(可以被修改)。

    一种混合模式:

    function Person(name, age, job){
    	this.name = name;
    	this.age = age;
    	this.job = job;
    	this.friends = ["Shelby", "Court"];
    }
    Person.prototype = {
    	constructor : Person,
    	sayName : function(){
    		alert(this.name);
    	}
    }
    
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    
    person1.friends.push("Van");
    
    alert(person1.friends); //"Shelby,Count,Van"
    alert(person2.friends); //"Shelby,Count"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true
    

    4. 继承

    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.constructor 了
    	// 如果你需要这个属性的话可能需要手动修复一下它
    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]] 关联到你指定的对象(本例中是Foo.prototype)。

    备注:Object.create原理

    上述代码如果用以下两种方式:

    // 和你想要的机制不一样!
    Bar.prototype = Foo.prototype;
    
    // 基本上满足你的需求,但是可能会产生一些副作用 :(
    Bar.prototype = new Foo();
    

    Bar.prototype = Foo.prototype 并不会创建一个关联Bar.prototype 的新对象,它只是让Bar.prototype 直接引用Foo.prototype 对象。因此当你执行类似Bar.prototype.
    myLabel = ... 的赋值语句时会直接修改Foo.prototype 对象本身。

    Bar.prototype = new Foo() 的确会创建一个关联到Bar.prototype 的新对象。但是它使用了Foo(..) 的“构造函数调用”,如果函数Foo 有一些副作用(比如写日志、修改状态、注册到其他对象、给this 添加数据属性,等等)的话,就会影响到Bar() 的“后代”,后果不堪设想。

    Bar.prototype = new Foo() 的确会创建一个关联到Bar.prototype 的新对象。但是它使用了Foo(..) 的“构造函数调用”,如果函数Foo 有一些副作用(比如写日志、修改状态、注册到其他对象、给this 添加数据属性,等等)的话,就会影响到Bar() 的“后代”,后果不堪设想。

    Object.create的polyfill

    if (!Object.create) {
    	Object.create = function(o) {
    		function F(){}
    		F.prototype = o;
    		return new F();
    	};
    }
    

    ES6 添加了辅助函数Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。

    我们来对比一下两种把Bar.prototype 关联到Foo.prototype 的方法:

    // ES6 之前需要抛弃默认的Bar.prototype
    Bar.ptototype = Object.create( Foo.prototype );
    // ES6 开始可以直接修改现有的Bar.prototype
    Object.setPrototypeOf( Bar.prototype, Foo.prototype );
  • 相关阅读:
    Java 获取指定日期的方法总结
    【Struts2+Spring3+Hibernate3】SSH框架整合实现CRUD_1.0
    struts2标签Iterator迭代时获取下标
    【转】svn 修改log信息
    pppoe移植到arm上 2.0
    镜像制作
    proc增加节点的一个例子
    libcurl库的使用
    sed & awk Manuals
    用一个函数保存文件
  • 原文地址:https://www.cnblogs.com/ylweb/p/8540141.html
Copyright © 2011-2022 走看看