初学JS

初次接触JS,对一些基本概念进行探究。


this与优先级

this是一个完全根据调用点(函数是如何被调用的)而为每次函数调用建立的绑定。
  从优先级最低的默认绑定开始说起。

  • 默认绑定

function foo()
{
    console.log(this.a);
}
var a = 2;
foo(); //2

默认的绑定规则,当且仅当其他任何绑定方式都不起作用时才适用。

  • 隐含绑定

function foo()
{
    console.log(this.a);
}
var obj = 
{
    a: 2,
    foo: foo
};
obj.foo(); //2

调用点使用obj环境来引用函数,此时this.a为obj.a的同义词。需要注意的是,只有对象属性引用链的最后一层是影响调用点的。

  • 明确绑定

function foo()
{
    console.log(this.a);
}
var obj = 
{
    a: 2,
};
foo.call(obj); //2

通过call函数(或apply、bind函数)制定this的作用点。
  一个更加强硬的手段为:将该绑定进行包装,成为硬绑定。

function foo()
{
    console.log(this.a);
}
var obj = 
{
    a: 2,
};
var bar = function()
{
    foo.call(obj);
};
bar(); //2
bar.call(window); //2
  • new绑定

在任何函数作为new表达式的一部分被调用时,它是一个构造器:它初始化这个新创建的对象。
  在函数钱加入new调用时,一下事项会自动完成:
1.一个全新的对象会被构建
2.这个新构建的对象会被接入原型链
3.这个新构建的对向被设置为函数调用的this绑定
4.除非这个函数返回一个它自己的其他对象,这个new调用的函数将自动返回这个新构建的对象*
  用这个方法可以用new进行this绑定。

function foo()
{
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2

至此,一般函数遵守的this规则介绍完毕,接下来考察词法this的使用。

  • 箭头函数绑定

在ES6中引用了一种特殊规则函数:箭头函数。

function foo()
{
    return(a) =>
    {
        console.log(this.a);
    };
}
var obj1 = 
{
    a: 2
};
var obj2 = 
{
    a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); //2,不是3!

箭头函数从封闭它的作用域采用this绑定。箭头函数的词法绑定是无法被覆盖的。
You Don't Know JS 02 Chapter2


closure(闭包)

JS里的function能访问它们的:

  • 参数
  • 局部变量或函数
  • 外部变量(环境变量),包括:全局变量、 外部函数的变量或函数

如果一个函数访问了它的外部变量,那么它就是一个闭包。

注意,外部函数不是必需的。通过访问外部变量,一个闭包可以维持(keep alive)这些变量。在内部函数和外部函数的例子中,外部函数可以创建局部变量,并且最终退出;但是,如果任何一个或多个内部函数在它退出后却没有退出,那么内部函数就维持了外部函数的局部数据。
  从技术上来讲,在JS中,每个function都是闭包,因为它总是能访问在它外部定义的数据。
  闭包经常用于创建含有隐藏数据的函数(但并不总是这样)。

var db = (function() 
{
    // 创建一个隐藏的object, 这个object持有一些数据
    // 从外部是不能访问这个object的
    var data = {};
    // 创建一个函数, 这个函数提供一些访问data的数据的方法
    return function(key, val) 
    {
        if (val === undefined) { return data[key] } // get
        else { return data[key] = val } // set
    }
    // 我们可以调用这个匿名方法
    // 返回这个内部函数,它是一个闭包
})();

db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员

博客园:JavaScript闭包
You Don't Know JS 03 Chapter5


scope(作用域)与hoisting(声明提升)

scope(作用域)

作用域为定义如何在某些位置存储变量,以及如何在稍后找到这些变量的规则。
  在实际运用过程中,作用域往往是嵌套的(值得庆幸的是,一个作用域不能同时嵌套在两个作用域中)。以下图展示函数嵌套产生的多层作用域。



  图中:

  • 气泡1为全局作用域,其中只有一个标识符:foo
  • 气泡2为作用域foo,其中有三个标识符:a,b,bar
  • 气泡3为作用域bar,其中只有一个标识符:c
      JavaScript中的作用域规则很简单:引擎从当前执行的作用域开始查找变量,如果没有找到,就向上走一级继续查找,以此类推。如果到了最外层的作用域,那么查找就会停止,无论它是否找到了变量。
      在以上代码中,console.log从bar中找a和b,但是并未找到,因此它继续在foo中找到了a和b并输出了。对于c,console.log在bar中就找到了它,因此无需继续在foo中寻找它了(即使可能存在全局变量c)。

hoisting(声明提升)

为了介绍声明提升,这里给出一个奇怪的例子。

foo();
function foo()
{
    console.log(a); //undefined
    var a = 2;
}

这段令人浑身难受的代码并不会报错。相反,它会输出undefined。
  产生这样的结果正是由于JS在解析过程中的声明提升特性:它会将var等定义语句(包括函数)提升至所在作用域最上方,以免出现以上代码中的先使用后定义的情况。
  以上代码的实际运行过程如下。

function foo()
{
    var a;
    console.log(a); //undefined
    a = 2;
}
foo();

然而以下代码会报错。

foo(); //TypeError
var foo = function bar()
{
    console.log(a);
    var a = 2;
}

这是由于只有bar函数的声明被提升了,其对于foo的引用赋值并未提升,导致报错。
  另外需要注意的一点是,函数提升优先于变量提升。考虑以下例子。

foo();
var foo;
function foo(){}

这段代码不会报错,是因为它在提升操作后变为如下形式。

function foo(){}
var foo;
foo();

第二行对foo的重复定义被无视了,因此对于foo函数的运行不会报错。
You Don't Know JS 02 Chapter4


对于一些量的TF判别

使用代码探究JS的正误判别。

console.log(!!0); //false
console.log(!!1); //true
console.log(!!0.1); //true
console.log(!!100); //true
console.log(!!-1); //true
console.log(!!""); //false
console.log(!!"0"); //true
console.log(!!"ab"); //true
console.log(!!null); //false
console.log(!!undefined); //false
console.log(!!NaN); //false
console.log(!!new Number()); //true

可以看到,值为false的量仅有0、“”、null、undefined、NaN。
接下来介绍两个常用的比较运算符。

相等运算符(==)

如果操作数具有相同的类型,则判断其等同性,如果两个操作数的值相等,则返回true,否则返回false。
  如果操作数的类型不同,则按照这样的情况来判断:

  • null和undefined相等
  • 其中一个是数字,另一个是字符串,则将字符串转换为数字,再做比较
  • 其中一个是true,先转换成1(false转换成0)在做比较
  • 如果一个值是对象,另一个是数字/字符串,则将对象转换为原始值(通过toString()或者valueOf()方法)
  • 其他情况,直接返回false

等同运算符(===)

如果操作数的类型不同,则不进行值得判断,直接返回false。
  如果操作数的类型相同,分下列情况来判断:

  • 都是数字的情况,如果值相同,则两者等同(有一个例外,NaN与其本身也不等同),否则不等同
  • 都是字符串的情况,与其他程序设计语言一样,如果串的值不等,则不等同,否则等同
  • 都是布尔值,且值均为true/false,则等同,否则不等同
  • 如果两个操作数引用同一个对象(数组,函数),则两者完全等同,否则不等同
  • 如果两个操作数均为null/undefined,则等同,否则不等同
    JavaScript核心概念及实践 第二章

类型的判断

JS有基本类型:Number、String、Boolean、Undefined、Null,以及对象类型Object。
  对于基本类型,除了null,都可以用typeof进行类型判断。

var a = 1;;
var b = "1";
var c = true;
var d = null;
var e = undefined;
console.log(typeof a); //number
console.log(typeof b); //string
console.log(typeof c); //boolean
console.log(typeof d); //object
console.log(typeof e); //undefined

对于null,通常用上一部分种的等同符号判定。
  对于Object类型,可以使用instanceof来进行判断。

var a = [];
console.log(a instanceof Array); //true
console.log(a instanceof Object); //true
var b = Function()
console.log(b instanceof Function); //true
console.log(b instanceof Object); //true
console.log(b instanceof Array); //false

尽管有些偏题,还是附上一些typeof的判断结果,以此感受JS内部的new原理。

var a = "123";
console.log(typeof a) //string

var b = new String();
b = 123
console.log(typeof b) //number

console.log(typeof new String()) //object


console.log(typeof new Array()) //object
console.log(typeof new Number()) //object
console.log(typeof new Function()) //function

JavaScript核心概念及实践 第二章


对于类的模拟

用JavaScript去模拟类是一个比较头疼的问题。
  通常而言,一个标准的类应有public、private和protected成员。一般思维下,我们通过闭包来阻止对于某变量的访问。然而如果为了继承而使用了prototype,那么闭包这条路就走不通了。在这种情况下,只能使用Object.defineProperty了。该函数可以设定获取属性时返回的值,也可以设定更改属性时设置的值。有了这个函数,我们可以随时跟踪到某个属性是不是在被获取,或者是否在被更改。我们还需要一个开关,我们在类内部的方法调用时,把这个开关打开,表明是在内部运行,方法调用结束后将开关关闭,表明回到外部运行状态。有了这两个状态,我们就可以跟踪private和protected属性和方法了,一旦他们在开关关闭的时候被使用,就终止这个属性或方法的获取或设置。
  详细源代码及说明请参照原作者文章:
JavaScript实现类的private、protected、public、static以及继承
开源库件jpp.js

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容