资讯专栏INFORMATION COLUMN

《你不知道的javascript》笔记_对象&原型

seasonley / 1811人阅读

摘要:上一篇你不知道的笔记写在前面这是年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响年是向上的一年在新的城市稳定连续坚持健身三个月早睡早起游戏时间大大缩减,学会生活。

上一篇:《你不知道的javascript》笔记_this

写在前面

这是2019年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响2018年是向上的一年:在新的城市稳定、连续坚持健身三个月、早睡早起、游戏时间大大缩减,学会生活。根据上一年目标完成情况,新一年的计划将采取【敏捷迭代】的方式:制定大方向,可临时更新小目标的策略。哈哈,话不多说..

在学习《javascript高级程序设计》这本书时,关于对象和原型,之前写过下面几篇文章,可作参考:

《javascript高级程序设计》笔记:对象数据属性和访问器属性

《javascript高级程序设计》笔记:创建对象

《javascript高级程序设计》笔记:原型图解

《javascript高级程序设计》笔记:继承

一、对象基本知识 1.1 数据属性与访问属性

《javascript高级程序设计》笔记:对象数据属性和访问器属性,这篇文章对数据属性和访问器属性有基本的介绍了,下面会做出一点补充说明:

(1)默认值

通过Object.defineProperty的方式声明的属性默认值

var obj = { a: 1 };
Object.getOwnPropertyDescriptor(obj, "a");
/*{
    value: 1, 
    writable: true,
    enumerable: true, 
    configurable: true
}*/

通过Object.defineProperty的方式声明的属性默认值

var obj = {};
Object.defineProperty(obj, "a", {});
Object.getOwnpropertyDescriptor(obj, "a");
/*{
    value: undefined,
    writable: false,
    enumerable: false,
    configurable: false
}*/

(2)属性configurable

configurable属性设为false为不可逆过程

configurable:false其他属性无法修改同时会禁止删除该属性

特例configurable: false时,writable的状态可由true改为false,但不能由false变为true

var obj = {};
Object.defineProperty(obj, "a", {
    configurable: false,
    writable: true,
    value: 1
})// {a: 1}
Object.defineProperty(obj, "a", {
    configurable: false,
    writable: false,
    value: 2
})// {a: 2}
Object.defineProperty(obj, "a", {
    configurable: false,
    writable: false,
    value: 3
})// Uncaught TypeError: Cannot redefine property: a

(3)区分数据属性和访问属性

当我们同时定义这两个属性时,会提示错误:

var obj = { _a: 1 };
Object.defineProperty(obj, "a", {
    configurable: true,
    enumerable: true,
    
    writable: true,
    value: 1,
    
    get() { return this._a; },
    set(newVal) { this._a = newVal * 10; }
});
Object.getOwnPropertyDescriptor(obj, "a");
// Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

因此:这两个属性不能同时存在,根据他们特有的属性反过来判断究竟是哪种属性即可

(4)属性enumerable: false的对象如何遍历

可通过Object.getOwnPropertyNames获取key,再遍历

var obj = {};
Object.defineProperty(obj, "a", {value: 10});

// 一般的遍历无法获取
for(let k in obj){
    console.log(k)
}// undefined

Object.getOwnPropertyNames(obj); // ["a"]
1.2 不变性

(1)对象常量

创建方式:数据属性:wraitable:false,configurable:false
特点:不可修改/重定义/删除

var obj = {};
Object.defineProperty(obj, "MY_CONSTANT", {
    value: 10,
    writable: false,
    configurable: false,
})
obj.MY_CONSTANT = 11;
console.log(obj.MY_CONSTANT); // 非严格模式下为10,严格模式报错

同样的,如果这个对象常量是对象

var obj = {};
Object.defineProperty(obj, "MY_CONSTANT_OBJ", {
    value: {a: 1},
    writable: false,
    configurable: false,
})
obj.MY_CONSTANT_OBJ.b = 2;
console.log(obj.MY_CONSTANT_OBJ); // {a: 1, b: 2}

因此:const定义的普通常量类似,只要对象的地址不变便不会报错

(2)禁止扩展

创建方式Object.preventExtensions()
特点:不可添加新属性(可删除可修改可重定义)
判断Object.isExtensible()不可扩展返回false

var obj = {a: 1, b: 2};
Object.preventExtensions(obj);

obj.c = 3; // {a: 1, b: 2} 不可添加
obj.a = 2; // {a: 2, b: 2} 可修改
delete obj.a; // {b: 2} 可删除
Object.defineProperty(obj, "b", {
    configurable: false,
    value: 3
});
delete obj.b; // {b: 3} 可重定义

Object.isExtensible(obj); // false

(3)密封

创建方式Object.seal()
特点:不可添加/修改/删除
判断Object.isSealed()密封时返回true

密封是在禁止扩展的基础上禁止删除和重定义。相当于把现有属性标记为configurable:false

var obj = {a: 1, b: 2};
Object.seal(obj);

obj.c = 3; // {a: 1, b: 2} 不可添加
delete obj.a; // {a: 1, b: 2} 不可删除
// configurable:false 不可重定义

obj.a = 2; // {a: 2, b: 2} 可修改

Object.isSealed(obj); // true

(4)冻结

创建方式Object.freeze()
特点:最高级别的不可变
判断Object.isFrozen()

冻结是在密封的基础上把现有属性标记为writable:false所有属性相当于常量属性

1.3 存在性

in操作符:检查属性是否在对象及其原型链中
hasOwnProperty:仅检查属性是否在该对象上

因此,可结合二者来判断属性是否仅存在与原型链上

function hasPrototypeProperty(obj, property) {
    return (property in obj) && !(obj.hasOwnProperty(property));
}

另外,可获取的某个对象所有key(未遍历至原型链),再判断指定属性是否在对象内;
遍历方法:

Object.keys();
Object.getOwnPropertyNames(); // 可获取不可枚举的属性
1.4 深浅拷贝

理解深浅拷贝需要对内存有一定理解,如果对基础类型和复杂类型(值类型和引用类型)在内存中的区别仍没有清楚的认识,可参考《javascript高级程序设计》笔记:值类型与引用类型这篇文章,否则忽略即可

值类型数据是直接存在于栈内存中,复制操作是直接开辟内存并存值;

引用类型数据栈内存中存储的只是堆内存中真实数据的一个引用,复制操作仅复制引用,并未真实复制堆中的数据;

根据上面的认识,我们思考一下:什么是深拷贝,什么是浅拷贝?

深浅拷贝仅仅是针对引用类型而言,深拷贝是按照目标对象的结构复制一份完全相同的对象,新的对象与原对象各个嵌套层级上内存完全独立,修改新对象不会更改原对象;引用类型的拷贝中除深拷贝外的均为浅拷贝(不完全深拷贝)

本文仅介绍我在项目中经常使用的深浅拷贝几种方式,不对底层实现探究:

扩展运算符【浅】

var obj = {a: 1, b: 2};
var arr = [1,2,3,4];
// 对象
var copyObj = {...obj};
copyObj === obj; // false
// 数组
var copyArr = [...arr];
copyArr === arr; // false

Object.assign()【浅】

// 对象
var copyObj = Object.assign({}, obj);
copyObj === obj; // false
// 数组
var copyArr = Object.assign([], arr);
copyArr === arr; // false

数组也是对象,因此Object.assign也可使用,只是一般不这么用且有更简单的方式

数组拷贝实现【浅】

var copyArr1 = arr.slice();
var copyArr2 = arr.concat();

lodash中的clone/cloneDeep【浅/深】
工具库lodash中提供了深浅拷贝的方法,简单易用且能够按需引入

// 全部引入
import _ from "lodash";
// _.clone() _.cloneDeep()

// 按需引入
import clone from "lodash/clone";
import cloneDeep from "lodash/cloneDeep";

JSON方式【深】
这个是平时项目中最常用的深拷贝方式,局限性就是,无法拷贝方法

JSON.parse(JSON.stringify(obj));

其实,深拷贝就是通过递归逐级浅拷贝实现的,因为对于复杂类型的元素均为值类型的浅拷贝便是深拷贝。例:[1,2,3].slice()便是深拷贝

如需更深入,可参考:JavaScript 浅拷贝与深拷贝

二、原型&继承
类/继承描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域的建模方法

下面会对这种建模慢慢阐述

2.1 原型

非常庆幸,之前写过《javascript高级程序设计》笔记:原型图解的文章得到了许多朋友的认可。关于原型的一些知识这里面也说的七七八八了,读完《你不知道的javascript》后再做些许补充

下面搬出经典的铁三角镇楼,在原型图解中已做说明,在此不唠述

(1)原型有什么用

为什么要抽离出原型的概念?在js中原型就相当于类;类有什么作用呢?

书中有一个恰当的比喻:类相当于建造房子时的蓝图,实例相当于我们需要真实建造的房子

这个蓝图(类)抽离出了房子的诸多特性(属性),如宽/高/占地/窗户数量/材料等等。我们建造房子(创建实例)时只需要按照这些搭建即可,而不是从零开始

回到编程中:有了类的概念,针对一些具有公共的属性和方法对象,我们可以将其抽离出来,以便下次使用,简化我们构建的过程

这个是js中数组的【类】,实例以后就能够直接使用,而无需将公用的方法定义在实例上

(2)属性屏蔽规则

属性查找规则:沿着原型链查找,到最近的对象截止,若一直到Object.prototype也无法找到,则返回undefined

反言之,属性的屏蔽规则亦是如此?原型链上,同名属性靠前会屏蔽掉后面的同名属性?答案远没有这么简单,分为三种情况考虑:(以myObject.foo = "bar";为例)

1. 如果在原型链上层存在名为foo的普通数据访问属性并且没有被标记为只读writable:true,那么直接在myObject中添加一个名为foo的新属性

function Fn() {}
Fn.prototype.foo = "this is proto property";

var myObject = new Fn();
myObject.foo = "this is my own property";

myObject.foo; // "this is my own property";

2. 如果原型链上层存在foo,但是被标记为只读writable:false,那么无法修改已有属性或者在myObject中创建屏蔽属性

function Fn() {}
Object.defineProperty(Fn.prototype, "foo", {
    value: "this is proto property",
    writable: false,
});

var myObject = new Fn();
myObject.foo = "this is my own property";

myObject.foo; // "this is proto property";

3. 如果在原型链上层存在foo并且他是一个setter,那么一定会调用这个setter

function Fn() {}
Object.defineProperty(Fn.prototype, "foo", {
    set(newValue) {
        this._foo = "haha! this is setter prototype";
    },
    get() {
        return this._foo;
    }
});

var myObject = new Fn();
myObject.foo = "this is my own property";

myObject.foo; // "haha! this is setter prototype";

(3)一个面试题

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

anotherObject.a; // ?
myObject.a; // ?
// node2

上面问号处输出值为多少?

分析:
node1node2处分别执行下面的代码并输出

// node1
anotherObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("a"); // false
// node2
anotherObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("a"); // true

看到了什么,执行完myObject.a++;后实例对象创建了一个自己的属性;为什么?自增操作相当于myObject.a = myObject.a + 1首先查找到属性,后在实例对象上创建一个新的同名属性,屏蔽原型上的属性;
答案:2 3

2.2 继承

《javascript高级程序设计》笔记:继承这篇文章分析了各种继承的情况,一步一步演化至更精致的继承情况

(1)继承有什么用

需求:为我的汽车建一个对象
以前:{id: XX, 牌照: XX, 品牌: XX, 油耗: XX, 载人: XX, 颜色: XX .. }
建模的思想来搭建类和继承体系:

抽离交通工具的类 Vehicle = {油耗: XX, 载人: XX, 颜色: XX, drive()}

抽离汽车的类 Car = {继承Vehicle, 品牌: XX }

实例化我的汽车 {继承Car, id: XX, 牌照: XX}

抽离类并继承,加上实例对象特有的属性便能够具体某一对象,达到高效利用的目的

(2)新的继承

下面的方式便是在“组合继承”的进一步“精致”,当然还有class语法糖(后续计划..)

function Foo(name) {
    this.name = name;
}
Foo.prototype.sayName = function() {
    console.log(this.name);
}
var foo = new Foo("xiaoming");
console.log(foo)

function Bar(name, id) {
    Foo.call(this, name)
    this.id = id;
}

// Object.create()方式
Bar.prototype = Object.create(Foo.prototype);
// Object.setPrototypeOf()方式
// Object.setPrototypeOf( Bar.prototype, Foo.prototype );

Bar.prototype.sayId = function() {
    console.log(this.id);
}
var bar = new Bar("xiaofeng", 1234)
console.log(bar);

两者对比而言,显然Object.setPrototypeOf()方式更加完备一些(无需再次声明constructor

2.3 原型本质【行为委托】
对象之间并非从属关系,而是委托关系,js中委托关系正是通过Object.create()完成

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

在上面的继承中,我们已经看到了Object.create()的身影;下面来对比类和委托思维模型

// 类/继承模型
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(who) {
        this.me = who;
    },
    identify() {
        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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100761.html

相关文章

  • 你不知道javascript笔记_this

    下一篇:《你不知道的javascript》笔记_对象&原型 写在前面 上一篇博客我们知道词法作用域是由变量书写的位置决定的,那this又是在哪里确定的呢?如何能够精准的判断this的指向?这篇博客会逐条阐述 书中有这样几句话: this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式当一个函数被调用时...

    cpupro 评论0 收藏0
  • 前端笔记——JS基础(原型&&原型链)

    摘要:基础原型原型链构造函数默认有这一行张三李四构造函数扩展其实是的语法糖其实是的语法糖其实是使用判断一个函数是否是一个变量的构造函数原型规则和示例所有的引用类型数组对象函数,都具有对象属性即可自有扩展的属性,除外所有的引用类型数组对象函数, JavaScript基础 —— 原型&&原型链 构造函数 function Foo(name, age) { this.name = na...

    n7then 评论0 收藏0
  • 深入JavaScript(一)this & Prototype

    摘要:然而事实上并不是。函数本身也是一个对象,但是给这个对象添加属性并不能影响。一图胜千言作者给出的解决方案,没有麻烦的,没有虚伪的,没有混淆视线的,原型链连接不再赤裸裸。所以是这样的一个函数以为构造函数,为原型。 注意:本文章是个人《You Don’t Know JS》的读书笔记。在看backbone源码的时候看到这么一小段,看上去很小,其实忽略了也没有太大理解的问题。但是不知道为什么,我...

    The question 评论0 收藏0
  • 浅谈 null & undefined

    摘要:初识在中有两种特别的基本数据类型初学者对其也很模糊或者直接认为它俩相等。作为函数参数,表示该函数的参数不是对象对象原型链的终点。对象属性没有赋值,该属性为当函数没有返回值时,默认返回第一次分享文章,如有错误请斧正 1.初识 null & undefined 在javascript 中有两种特别的基本数据类型 null undefined 初学者 对其也很模糊或者直接认为它俩相等。 确实...

    lewif 评论0 收藏0
  • 笔记-你不知道JS-原型

    摘要:如果存在于原型链上层,赋值语句的行为就会有些不同。中包含的属性会屏蔽原型链上层的所有属性,因为总是会选择原型链中最底层的属性。如果不直接存在于中而是存在于原型链上层时会出现的三种情况。类构造函数原型函数,两个函数通过属性和属性相关联。 1 [[Prototype]] 对于默认的 [[Get]] 操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的 [[Prototype]] ...

    vincent_xyb 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<