JavaScript书摘

JavaScript的作用域

函数作用域和块作用域

除了let和const之外,JavaScript的作用域不是以花括号包围的块级作用域。

"use strict"

if (true) {
    var somevar = 'value';
    let somelet = 'valuelet';
    const someconst = 'valueconst'
}
console.log(somevar);
console.log(somelet);
console.log(someconst);

somevar是有定义的,而somelet和someconst已经没有定义了。

在一个函数中定义的变量只对这个函数内部可见。如果没有找到则搜索其上层作用域,一直到全局作用域。

例1:

var v1 = 'v1';

var f1 = function(){
    console.log(v1);
};
f1(); // 输出v1

var f2 = function(){
    var v1 = 'local';
    console.log(v1);
};
f2(); // 输出local

例2:

因为f的scope内有定义scope变量,所以全局的不起作用。当console.log执行时,定义scope的还没有执行到,所以是undefined

var scope = 'global';

var f = function(){
    console.log(scope); //输出undefined
    var scope = 'f';
};
f();

函数作用域的嵌套关系是定义时决定的,而不是调用时决定的。也就是说,JavaScript的作用域是静态作用域,又叫词法作用域。

例:

var scope2 = 'top';

var f3 = function() {
    console.log(scope2);
};
f3();

var f4 = function() {
    var scope2 = 'f4';
    f3();
}();//输出top

全局作用域

以下变量属于全局作用域:
* 在最外层定义的变量
* 全局对象的属性
* 任何地方隐式定义的变量

闭包

var generateClosure = function() {
    var count = 0;
    var get = function() {
        count++;
        return count;
    };
    return get;
};

var counter1 = generateClosure();

console.log(counter1()); //输出1
console.log(counter1()); //输出2
console.log(counter1()); //输出3

当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。

闭包的用途:
1. 嵌套的回调函数
2. 实现私有成员

对象

Practice does not make perfect. Only perfect practice makes perfect.
-Vince Lombardi

JavaScript中没有类,只有对象。JavaScript中的对象是基于原型的。
在JavaScript中,对象仅仅是属性(properties)的容器,或者叫做属性包。每个对象可以有0个到多个属性,这些属性可以持有一个基本类型的值,也可以指向一个复杂类型的对象。

创建和访问

创建一个空对象的方法有三种:
* {}:字面量
* new Object()
* Object.create(null)

在JavaScript中,可以使用.来创建一个简单的对象:

var foo1 = {};
foo1.prop_1 = 'bar';
foo1.prop_2 = false;
foo1.prop_3 = function() {
    return 'hello,prototype';
}
console.log(foo1.prop_3());

还可以用关联数组的模式来创建对象:

var foo2 = new Object();
foo2['prop1'] = 'bar';
foo2['prop2'] = false;
foo2['prop3'] = function() {
    return 'hello,prototype2';
}
console.log(foo2.prop3());

Property Manager

Many developers assume that an object’s property is only a container that can be assigned a name and a value. In actuality though, JavaScript gives the developer a series of powerful perperty descriptors that further shpae how the property behaves. Let’s iterate over them now:

configurable

When its attribute is set to true, the affected property can be deleted from the parent object, and the property’s descriptor can be modified later. When set to false, the property’s descriptor is sealed from further modifications. Here is a simple example:

var car = {};

// A car can have any number of doors
Object.defineProperty(car, 'doors', {
    configurable: true,
    value: 4
});

// A car must have only four wheels
Object.defineProperty(car, 'wheels', {
    configurable: false,
    value: 4
});

delete car.doors;

console.log(car.doors); // => undefined

// TypeError: Cannot delete property 'wheels' of #<Object>
//delete car.wheels;

console.log(car.wheels); // => 4

Object.defineProperty(car, 'doors', {
    configurable: true,
    value: 5
});

console.log(car.doors);

/* TypeError: Cannot redefine property: wheels Object.defineProperty(car, 'wheels', { configurable: true, value: 4 }); */

As you can see in the previous example, wheels becomes fixed while doors remains malleable. A programmer might want to revoke the configurable attribute of a property as a form of defensive programming to prevent an object from being modified much like built-in object of the language do.

enumerable

Enumerable properties appear if an object’s properties are iterated over using code. When set to false, those properties cannot be iterated over. Here is an example:

var car2 = new Object();

Object.defineProperty(car2, 'doors', {
    writable: true,
    configurable: true,
    enumerable: true,
    value: 4
});

Object.defineProperty(car2, 'wheels', {
    writable: true,
    configurable: true,
    enumerable: true,
    value: 4
});

Object.defineProperty(car2, 'secretTrackingDeviceEnabled', {
    enumerable: false,
    value: true
});

// 输出:
//doors
//wheels
for (let x2 in car2) {
    console.log(x2);
}

console.log(Object.keys(car2)); //输出:[ 'doors', 'wheels' ]

console.log(Object.getOwnPropertyNames(car2)); //输出:[ 'doors', 'wheels', 'secretTrackingDeviceEnabled' ]

console.log(car2.propertyIsEnumerable('secretTrackingDeviceEnabled')); //输出:false

console.log(car2.secretTrackingDeviceEnabled); //输出:true

As you can see from the previous example, even though a property is not enumerable it does not mean the property is hidden altogether. The enumerable attribute can be used to dissuade a programmer from using the property, but should not be used as a method to secure an object’s properties from inspection.

writable

When true, the value associated with the property can be changed; otherwise, the value remains constant.

var car3 = Object.create(null);

Object.defineProperty(car3, 'wheels', {
    value: 4,
    writable: false
});

console.log(car3.wheels);

// 下面这句运行报错:TypeError: Cannot assign to read only property 'wheels' of [object Object]
//car3.wheels = 5;

Inspecting Objects

In the last section, you learned how to define your own properties on objects you create. Just as in life, it’s helpful to know how to read and write, so in this section you’ll learn how to git through the underbrush of objects in JavaScript. What follows is a list of functions and properties worth knowing when it comes to inspecting objects.

Object.getOwnPropertyDescriptor

In the last section, you saw the various ways to set the attributes of a property. Object.getOwnPropertyDescriptor gives you a detailed description of those settings for any property of an object:

let o1 = {
    foo: 'bar'
};
//输出:
/* { value: 'bar', writable: true, enumerable: true, configurable: true } */
console.log(Object.getOwnPropertyDescriptor(o1, 'foo'));

Object.getOwnPropertyNames

This method returns all the property names of an object, even the ones that cannot be enumerated.

let box1 = Object.create({}, {
    openLid: {
        value: function() {
            return "nothing";
        },
        enumerable: true
    },
    openSecretCompartment: {
        value: function() {
            return "treasure";
        },
        enumerable: false
    }
});

//输出:[ 'openLid', 'openSecretCompartment' ]
console.log(Object.getOwnPropertyNames(box1).sort());

Object.getPrototypeOf

This method is used to return the prototype of a particular object. In lieu of this method, it may be possible to use the __proto__ method, which many interpreters implemented as a means of getting access to the object’s prototype. However, __proto__ was always considered somewhat of a hack, and the JavaScript community used it mainly as a stopgap. It is worth nothing, however, that even if Object.getPrototypeOf gives you access to the prototype of an object, the only way to set the prototype of an object instance is by using the __proto__ property.

var a1 = {};

console.log(Object.getPrototypeOf(a1) === Object.prototype); // 输出true
console.log(Object.prototype === a1.__proto__); // 输出true

Object.hasOwnProperty

JavaScript’s prototype chain allows you to iterate over an instance of an object and return all properties that are enumerable. It includes properties that are not present on the object but somewhere in the prototype chain. The hasOwnProperty method allows you to identify whether the property in question is present on the object instance:
hasOwnProperty函数可以让你分辨出某个属性是否存在于对象实例中

let bar7 = Object.create(foo7, {
    bar7: {
        enumerable: true,
        value: 'bar7'
    }
});

for (let x7 in bar7) {
    console.log(x7);
}

let myProps7 = Object.getOwnPropertyNames(bar7).map(function(i) {
    return bar7.hasOwnProperty(i) ? i : undefined;
});

console.log(myProps7); // 输出: [ 'bar7' ]

Object.keys

This method returns a list of only the enumerable properties of an object

Object.isFrozen

This method returns true or false if the object being checked cannot be extended and its properties cannot be modified:

    let bombPop = {
        wrapping: 'plastic',
        flavors: ['Cherry','Lime','Blue Raspberry']
    };

    console.log(Object.isFrozen(bombPop)); // false

    delete bombPop.wrapping;

    console.log(bombPop.wrapping);

    Object.freeze(bombPop);

    //下面这句,执行报TypeError: Cannot delete property 'flavors' of #<Object>错
    //delete bombPop.flavors;

    console.log(bombPop.flavors);

    console.log(Object.isFrozen(bombPop)); // true

Object.isPrototypeOf

This method checks every link in a given object’s prototype chain for the existence of another object.

console.log(Object.prototype.isPrototypeOf([])); // true
console.log(Function.prototype.isPrototypeOf(() => {})); //true;
console.log(Function.prototype.isPrototypeOf(function() {})); //true
console.log(Object.prototype.isPrototypeOf(() => {})); //true

Object.isExtensible

By default, new objects in JavaScript are extensible, meaning that new properties can be added. However, an object can be marked to prevent it from being extended in the future. In some environments, setting a property on an inextensible object throws an error. You can use Object.isExtensible to check an object before trying to modify it:

    var car = {
        doors: 4
    };

    console.log(Object.isExtensible(car));

    Object.preventExtensions(car);

    console.log(Object.isExtensible(car));

Object.isSealed

This function returns ture of false depending on whether an object cannot be extended and all its properties are nonconfigurable:

    let ziplockBag = {};

    console.log(Object.isSealed(ziplockBag));
    console.log(Object.isExtensible(ziplockBag));

    Object.seal(ziplockBag);

    console.log(Object.isSealed(ziplockBag));
    console.log(Object.isExtensible(ziplockBag));

Object.valueOf

If you have ever tried to inspect an object only to see it spit out “[object Object]”, you have seen the handiwork of this function. Object.valueOf is used to describe an object with primitive value. All objects receive this function, but it is essentially a stub, meant to be overridden by a custom function later. Creating your own valueOf function provides a way to give an extra layer of descriptive detail to your custom objects:

    let Car = function(name) {
        this.name = name;
    }

    let tesla = Object.create(Car.prototype, {
        name: {
            value: 'tesla'
        }
    });

    console.log(tesla.valueOf());

    Car.prototype.valueOf = function() {
        return this.name;
    };

    console.log(tesla.valueOf());

Object.is (ECMAScript 6)

Testing equality of two values has long been a sore spot for some developers in JavaScript because JavaScript actually supports two forms of equality comparison. For checking abstract equality, JavaScript uses the double equal syntax ==. When checking strict equality, JavaScript uses the triple equal syntax ===. The major difference between the two is that by default the abstract equality operator coerces some values in order to make the comparison. The Object.is method determines whether the two supplied arguments have the same value without the need of coercing.

    //true
    console.log(Object.is('true', 'true'));

    //false
    console.log(Object.is('True', 'true'));

    //true
    console.log(Object.is(! function() {}(), true));

    //true
    console.log(Object.is(undefined, Math.prototype));

Do not confuse this behavior with the strict equality comparison operator, which returns true only if the two share the same type, not the same value. It can be represented easily with the following example:

    //false
    console.log(NaN === 0 / 0);

    //true
    console.log(Object.is(NaN, 0 / 0));

修改对象

调用对象

创建对象

原型编程

抽象

封装

多态

继承

强大的原型

类的转换

构造函数

javascript定义了构造函数,可以用new语句来创建对象。

function User(name, uri) {
    this.name = name;
    this.uri = uri;
    this.display = function() {
        console.log(this.name);
    }
}

const someuser = new User('hello,prototype4', 'http://world.com');
someuser.display();

上下文对象this

在一个函数内部,this就代表函数本身。
下面的例子可以更清楚地说明上下文对象的使用方式。


var someuser2 = {
    name : 'SomeUser2',
    display : function(){
        console.log(this.name);
    }
};

someuser2.display(); //输出SomeUser2

var foo2 = {
    bar : someuser2.display,
    name : 'foobar2'
};
foo2.bar(); //输出foobar2

在JavaScript中,本质上,函数类型的变量是指向这个函数实体的一个引用,在引用之间赋值不会对对象产生复制行为。我们可以通过函数的任何一个引用调用这个函数,不同之处仅仅在于上下文。

下面的例子可以进一步帮助我们理解:

var someuser2 = {
    name : 'SomeUser2',
    display : function(){
        console.log(this.name);
    }
};

var foo3 = {
    name : 'foobar3'
}

foo3.display = someuser2.display;
foo3.display(); //输出foobar3

name = 'global';
display = someuser2.display;
display(); //输出global

仔细观观察上面的例子,使用不同的引用来调用同一函数时,this指针永远是这个引用所属的对象。
JavaScript的函数作用域是静态的,是在预编译的语法分析中就可以确定的,而上下文对象则可以看作是静态作用域的补充。

call和apply

call和apply的功能是以不同的对象作为上下文来调用某个函数,就是允许一个对象去调用另一个对象的成员函数。其实JavaScript并没有严格所谓的“成员函数”的概念,函数与对象的所所属关系在调用时才展现出来。
call和apply的功能是一致的,细微的差别在于call以参数表来接受函数参数,而apply以数组来接受被调用函数的参数。

例:

var someuser3 = {
    name: 'User3',
    display: function(words) {
        console.log(this.name + ' says ' + words);
    }
};

var foo4 = {
    name: 'foobar4'
}

someuser3.display.call(foo4, 'hellow,call!');

bind

使用call或apply方法改变上下文,重复使用会很不方便,因为每次都要把上下文对象作为参数传递,而且还会使代码变得不直观。针对这种情况,我们可以使用bind方法来永久地绑定函数的上下文,使其无论被谁调用,上下文都是固定的。

var someuser3 = {
    name: 'User3',
    display: function(words) {
        console.log(this.name + ' says ' + words);
    }
};

var foo4 = {
    name: 'foobar4'
}

someuser3.display.call(foo4, 'hellow,call!');

foo4.display = someuser3.display;
foo4.display('hello,from foo4'); //输出foobar4 says hello,from foo4

foo4.display2 = someuser3.display.bind(someuser3);
foo4.display2('hello,from foo4'); //输出User3 says hello,from foo4

使用bind绑定参数表

bind方法还有一个重要的功能:绑定参数表,如下例所示:

var person4 = {
    name: 'We',
    says: function(verb, obj) {
        console.log(this.name + ' ' + verb + ' ' + obj);
    }
};

person4.says('hello', 'world');

var personHello = person4.says.bind(person4, 'hello');
personHello('world');

这个特性可以用于创建一个函数的捷径.

原型

在JavaScript语言中,没有类的概念,对象由对象实例化。

function Person3() {
}
Person3.prototype.name = 'Hello';
Person3.prototype.showName = function() {
    console.log(this.name);
};

var person3 = new Person3();
person3.showName();

上面的代码使用了原型而不是构造函数初始化对象。这样做与直接在构造函数内定义属性有什么不同呢?
* 构造函数内定义的属性继承方式与原型不同,子对象需要显式调用父对象才能继承构造函数内定义的属性。
* 构造函数内定义的任何属性,包括函数在内都会被重复创建,同一个构造函数产生的两个对象不共享实例。
* 构造函数内定义的函数有运行时闭包的开销,因为构造函数内部的局部变量对其中定义的函数来说也是可见的。

下面代码可以说明上面的问题:

function Foo5() {
    var innerVar = 'hello';
    this.prop1 = 'Hello';
    this.func1 = function() {
        innerVar = '';
    }
}

Foo5.prototype.prop2 = 'World';
Foo5.prototype.func2 = function() {
    console.log(this.prop2);
}

var foo5_1 = new Foo5();
var foo5_2 = new Foo5();

console.log(foo5_1.func1 == foo5_2.func1); //输出false;
console.log(foo5_1.func2 == foo5_2.func2); //输出true;

尽管如此,并不是说在构造函数内创建函数不好,而是两者各有适合的范围。那么我们什么时候使用原型,什么时候使用构造函数内定义来创建属性呢?
* 除非必须用构造函数闭包,否则尽量用原型定义成员函数,因为这样可以减小开销。
* 尽量在构造函数内定义一般成员,尤其是对象或数组,因为用原型定义的成员是多个实例共享的。

原型链

JavaScript中有两个特殊的对象:Object与Function,它们都是构造函数,用于生成对象。Object.prototype是所有对象的祖先,Functcion.prototype是所有函数的原型,包括构造函数。
每个JavaScript对象都有一个__proto__属性,它指向该对象的原型。
构造函数有prototype属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的__proto__属性将会指向构造函数的prototype属性。
原型对象有constructor属性,指向它对应的构造函数。

我们通过下面的例子来理解原型:

function Foo6() {

}
Object.prototype.name = 'My Object';
Foo6.prototype.name = 'Bar6';

var obj6 = new Object();
var foo6 = new Foo6();
console.log(obj6.name); //输出My Object
console.log(foo6.name); //输出Bar6
console.log(foo6.__proto__); //输出Foo6 { name: 'Bar6' }
console.log(foo6.__proto__.name); //输出Bar6
console.log(foo6.__proto__.__proto__.name); //输出My Object
console.log(foo6.__proto__.constructor.prototype.name); //输出Bar6

我们看一下它们之间的关系:

foo6.__proto__ === Foo6.prototype
Foo6.prototype.__proto__ === Object.prototype

Foo6.prototype.constructor === Foo6
Foo6.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype

在JavaScript中,继承是依靠一套叫做原型链的机制实现的。属性继承的本质就是一个对象可以访问它的原型链上任何一个原型对象的属性。

对象的复制

JavaScript所有对象类型的变量都是指向对象的引用,两个变量之间赋值传递一个对象并不会对这个对象进行复制,而只是传递引用。

你可能感兴趣的:(JavaScript,书摘)