《javascript设计模式》读书笔记二:字面量和构造函数

一.对象

对象由下面这三种方式创建:

  • 对象字面量
  • 来自构造函数的对象
  • 来自自定义构造函数的对象
对象字面量

在大括号里面写上键值对,键和值之间以分号分割,不同的键值对以逗号分隔。这种方式就是字面量。值可以是任意类型对象,除值是函数外我们都称为属性。值是函数我们称为方法。

1
2
3
4
5
6
7
8
9
// 创建对象
var obj = {
age: 12
}
// 像对象添加一个属性name
obj.name = "shu"
// 向对象添加一个方法fun
obj.fun = function() { return obj.age}

来自构造函数的对象

可以使用类似 Object(), Date(), String()的内置构造函数来创建对象。

1
var obj = new Object();

但是这种方式是不推荐的, 原因:

  • 在构造时,解析器需要从调用Object()的位置开始一直向上查询作用域链,直到发现全局的object函数(可能以同样的命名创建了一个局部构造函数)—>但字面量并没哟作用域解析。

  • 使用内置构造函数来创建对象。对于不同的内置函数会有自己的特征。例如:Object()构造函数会接受一个参数。并且以来传递进来的参数,导致Object()会委托其他的内置函数来创建对象,并且返回一个并非期望的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var o = new Object();
    console.log(o.constructor === Object) // true

    o = new Object(1)
    console.log(o.constructor === Number) // true
    console.log(o.toFixed(2)) // 1.00

    var o = new Object('tss');
    console.log(o.constructor === String) // true
    console.log(typeof o.substring()) //Object并没有substring方法。但是字符串是由的。

    var o = new Object(true);
    console.log(o.constructor === Boolean) // true
自定义构造函数创建的对象
1
2
3
4
5
6
7
8
9
var Person = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
var newPerson = new Person("shuliqi")
console.log(newPerson.name) // shuliqi
newPerson.getName() // shuliqi

使用new 操作符调用构造函数(自定义+内置)时。函数内部发生的事:

  • 创建一个空对象.
  • this指向该空对象.并且继承函数的原型。
  • 属性和方法被加入到this引用的对象中
  • 新对象由this值引用,并且最后隐式的返回(如果没有显示的返回值)
1
2
3
4
5
6
7
8
var Person = function(name) {
var this = {};
this.name = name;
this.getName = function() {
return this.name;
}
// return this; 隐式的返回
}

使用new操作符创建对象时,构造函数总会返回一个对象。默认情况下返回的是this所引用的对象。如果在构造函数中不向this添加任何属性和方法,将会返回一个空对象(这里的”空”,实际上并不空,它已经从构造函函数的原型继承了很多成员)。

当然构造函数你可以返回你想要的信息。

1
2
3
4
5
6
7
8
9
10
11
12
var Person = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
var that = {}
that.name = name;
return that;
}
var newPerson = new Person("shuliqi");
console.log(newPerson.name) // shuliqi
newPerson.getName // 报错: newPerson.getName is not a function

注意:在用new操作符创建对象时,构造函数总会返回一个对象。默认情况下返回的时this所引用的对象。如果在构造函数中没有向this添加任何属性和方法。那么将返回一个空对象(除了从构造函数的原型继承的成员)

构造函数将隐式的返回this。甚至在函数中没有显式的加入return语句。但是可以根据需要返回任意其他对象。

强制使用new的模式

构造函数也是函数,只不过它却以new的方式调用。如果在调用构造函数时忘记添加new操作符会发生?这虽然不会导致语法和运行时的错误。但是可能导致逻辑错误和意外的行为发生。忘记加new操作符。导致构造函数的this指向全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自定义构造函数
function person() {
this.name ="shuliqi";
}

// 创建一个新对象
var newPerson = new person();
console.log(typeof newPerson) // 'object'
console.log(newPerson.name) // "shuliqi"

// 忘记使用"new"模式

var newPerson1 = person();
console.log(typeof newPerson1) // "undefined"
console.log(window.name) // "shuliqi"

解决的办法

  • 命名约定。使构造函数的函数名首字母是大写的。有助于在一定程度上能避免忘记new操作符。

  • 构造函数不使用this。构造函数中并不用将方法和属性添加到this。而是添加到一个自定义的变量,最后返回。甚至不需要一个这样的局部变量。直接返回一个对象即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function name() {
    return {
    name: "shuliqi"
    }
    }

    var firstName = new name();
    var secondtName = name();
    console.log(firstName.name) // "shuliqi"
    console.log(secondtName.name) // "shuliqi"

    但是这种方式的缺点是会丢失到原型的链接。构造函数的原型属性都是不可用的。

  • 自调用构造函数

    为了使得构造函数的属性在实例对象中也能使用。可以在构造函数中检查this是否为构造函数的实例。如果为否。可以再次调用自身。并且在这次调用中正确的使用了new操作符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function name() {
    if(!(this instanceof name)) {
    return new name;
    }
    this.name = "shuliqi";
    }
    name.prototype.age = 12;

    var newName = name();
    console.log(newName.name,newName.age) // "shuliqi", 12

    另一种检测实例对象的方法是使用arguments.callee进行比较。callen属性指向被调用的函数。这样就不用在编码的时候硬编构造函数的名字了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function name() {
    if(!(this instanceof arguments.callee)) {
    return new name;
    }
    this.name = "shuliqi";
    }
    name.prototype.age = 12;

    var newName = name();
    console.log(newName.name,newName.age) // "shuliqi", 12

二.数组

数组对象可以通过下面这三种方式创建:

  • 数组字面量

  • 数组构造函数

    数组字面量

数组字面量表示法:逗号分隔的元素列表。并且元素列表包装在一个方括号中。可以给元素指定任意类型的值。包括对象或者其他数组。

1
var arr = ["1", "2", "3"]
数组构造函数

可以通过内置的构造函数Arrry()来创建数组

1
var arr = new Array("1", "2", "3")

注意:最好不要使用构造函数来创建数组。因为当向构造函数传递单个数字时,它并不会成为数组的第一个元素的值。而是设置了数组的长度。但是数组中并没有实际的元素。

1
2
3
4
5
var arr = [3];
console.log(arr.length, arr[0]) // 1, 3

var arr2 = new Array(3)
console.log(arr2.length, arr2[0]) // 3, undefined

如果传入的是浮点数将会更糟糕

1
2
3
4
var arr = [3.14];
console.log(arr.length, arr[0]) // 1, 3.14

var arr2 = new Array(3.14) // Uncaught RangeError: Invalid array length(不合法的数组长度)

因此坚决使用字面量将使程序更加安全。

检查数组的性质

因为以数组为操作数的并且使用typeof操作符。结果都会返回”object”。

检查一个对象是否是数组的办法:

  • 可以检查对象是否存在数组的一些属性(length)和方如slice(),split()等。以此来确定该值是否具有”数组性质”。

    缺点:不能保证任何其他非数组对象不能具有相同名称的属性和方法。

  • instance Array进行检查

    缺点:在某些浏览器版本中的不同框架运行并不正确。

  • ES5中的Array.isAarray()方法。该函数在参数为数组的时候返回true

    1
    2
    3
    4
    5
    6
    7
    console.log(Array.isArray([])) // true

    // 尝试以一个类似数组的对象欺骗检查
    console.log(Array.isArray({
    length: 1,
    slice: function() {}
    })) // false

    如果无法使用rray.isAarray()方法, 可以通过调用Object.prototype.toString()方法进行检查。如果在数组上,详细问调用toString()的call()方法,它应该返回”[object Array]”。如果是上下文是对象,则它应该返回字符串”[object object]”。

    1
    2
    3
    4
    5
    if (typeof Array.isArray === 'undefined') {
    Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === "[object Array]";
    }
    }

JSON

JSON是一种轻量级的数据交换格式。是一个数组和对象字面量表示方法的结合。

1
{ "name": 'shu', "age": 23, "some": [1, 2, 3]}
JSON文字对象之间的区别

在JSON中,属性名称需要在包装在引号才能成为合法的JSON。

在对象字面量中。仅当属性名称不是有效的标识符的时候菜会需要使用引号。例如:

1
{"first name": "jsdhs"}

注意: 在JSON中不能使用正则表达式字面量和函数。

使用JSON

使用JSON.parse()解析字符串。

使用JSON.stringify()将基本数据类型序列化成为一个JSON数组。

正则表达式

正则表达式也是对象,可以通过两种方式创建正则表达式:

  • 使用正则表达式字面量
  • 使用new RegExp()
使用正则表达式字面量

正则表达式字面量表示法使用了些刚(”/“)来包装用于匹配的正则表达式模式。在第二个斜杆之后,可以将该模式修改为不加引号的字母形式。

1
var reg = /shuliqi/gm
使用new RegExp()
1
var reg = new RegExp("shuliqi", "i")

能使用久使用字面量。

基本值类型包装器

javascript中的基本数据类型:number,string, boolean,null, undefined。除了null和undefined,其他三个具有所谓的基本包装对象。可以使用内置构造函数Number(),String()和Boolean()创建包装对象。

1
2
3
4
5
6
// 一个基本数值
var n = 100;
console.log(typeof n); // "number"

var nObj = new Number(100);
console.log(typeof nObj);// "object"

基本值只要调用包装对象的方法,就可以在后台杯临时转换为一个对象,并且表现得犹如一个对象。

1
2
3
4
5
6
7
// 用来作为对象的基本字符串
var s = "shu"
console.log(s.toUpperCase()); // 'SHU'

// 值本身可以作为一个对象
console.log("mokey".slice(3,6)); // "key"

由此可看出:基本值也可以充当对象,只要需要他们这么做。但是通常没有理由去使用更长的包装构造函数。

通常使用包装对象的原因之一是有扩充值及持久保存状态的需要。这是由于基本类型不是对象,他们不可能扩充属性

1
2
3
4
5
6
7
8
// 基本字符串
var greet = "shu";
// 为了使用slice()方法,基本数据类型杯转换成对象
console.log(greet.slice('')[0]); // "s"
// 试图添加一个原始数据类型并不会导致错误
greet.smile = true;
// 但它并不是实际运行
console.log(greet.smile) //"undefined"
文章作者: 舒小琦
文章链接: https://shuliqi.github.io/2019/05/23/构建对象/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 舒小琦的Blog