JavaScript中的黑魔法
Javascript的原型链模式的设计,让它隐藏了一些有趣的功能.
This
this是在运行时进行绑定的.this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
全局环境
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this
都指向全局对象。
1 | // 在浏览器中, window 对象同时也是全局对象: |
函数(运行内)环境
简单调用
因为下面的代码不在严格模式下,且 this
的值不是由该调用设置的,所以 this
的值默认指向全局对象。
1 | function f1(){ |
然而,在严格模式下,this
将保持他进入执行环境时的值,所以下面的this
将会默认为undefined
。
1 | function f2(){ |
call&apply
如果要想把 this
的值从一个环境传到另一个,就要用 call
或者apply
方法
1 | // 将一个对象作为call和apply的第一个参数,this会被绑定到这个对象。 |
bind
ECMAScript 5 引入了 Function.prototype.bind()
。调用f.bind(someObject)
会创建一个与f
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
1 | function f(){ |
箭头函数
在箭头函数中,this
与封闭词法环境的this
保持一致。在全局代码中,它将被设置为全局对象:
1 | var globalObject = this; |
作为对象的方法
当函数作为对象里的方法被调用时,它们的 this
是调用该函数的对象。
下面的例子中,当 o.f()
被调用时,函数内的this
将绑定到o
对象。
1 | var o = { |
作为构造函数
当一个函数用作构造函数时(使用new关键字),它的this
被绑定到正在构造的新对象。
1 | /* |
闭包
闭包是基于词法作用域书写代码时所产生的自然结果.
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
1 | function foo(){ |
函数bar
可以访问函数foo
的作用域.然后我们将bar()函数本身当作一个值类型进行传递。bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。
bar()依然持有对该作用域的引用,而这个引用就叫作闭包。
String
String(..)
String
函数可以将任意类型的值转化成字符串,转换规则如下。
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:
true
转为字符串"true"
,false
转为字符串"false"
。 - undefined:转为字符串
"undefined"
。 - null:转为字符串
"null"
。
1 | String(123) // "123" |
String
方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
1 | String({a: 1}) // "[object Object]" |
String
方法背后的转换规则,与Number
方法基本相同,只是互换了valueOf
方法和toString
方法的执行顺序。
- 先调用对象自身的
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果
toString
方法返回的是对象,再调用原对象的valueOf
方法。如果valueOf
方法返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果
valueOf
方法返回的是对象,就报错。
1 | String({ |
Number
Number(..)
使用Number
函数,可以将任意类型的值转化成数值。
下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。
原始类型值的转换规则如下。
1 | // 数值:转换后还是原来的值 |
Number
函数将字符串转为数值,要比parseInt
函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN
。
1 | parseInt('42 cats') // 42 |
上面代码中,parseInt
逐个解析字符,而Number
函数整体转换字符串的类型。
另外,parseInt
和Number
函数都会自动过滤一个字符串前导和后缀的空格。
简单的规则是,Number
方法的参数是对象时,将返回NaN
,除非是包含单个数值的数组。
1 | Number({a: 1}) // NaN |
Number处理对象执行顺序:
调用对象自身的
valueOf
方法。如果返回原始类型的值,则直接对该值使用Number
函数,不再进行后续步骤。如果
valueOf
方法返回的还是对象,则改为调用对象自身的toString
方法。如果toString
方法返回原始类型的值,则对该值使用Number
函数,不再进行后续步骤。如果
toString
方法返回的是对象,就报错。
1 | Number({ |
上面代码对三个对象使用Number
函数。第一个对象返回valueOf
方法的值,第二个对象返回toString
方法的值,第三个对象表示valueOf
方法先于toString
方法执行。
Number.prototype.toFixed()
toFixed
方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。在处理数值计算精度不对时经常用到.
1 | (10).toFixed(2) // "10.00" |
toFixed
方法的参数为小数位数,有效范围为0到20,超出这个范围将抛出 RangeError 错误。
Number.prototype.toExponential()
toExponential
方法用于将一个数转为科学计数法形式。
1 | (10).toExponential() // "1e+1" |
toExponential
方法的参数是小数点后有效数字的位数,范围为0到20,超出这个范围,会抛出一个 RangeError 错误。
Number.prototype.toPrecision()
toPrecision
方法用于将一个数转为指定位数的有效数字。
1 | (12.34).toPrecision(1) // "1e+1" |
toPrecision
方法的参数为有效数字的位数,范围是1到21,超出这个范围会抛出 RangeError 错误。
Number.prototype
所有的数字的原型链都指向Number.protytype
,可以在 Number.prototype
上写入一些常用的计算相关的方法,方便程序快速调用.
1 | Number.prototype.add = function (x) { |
Array
@@iterator
for..of
循环首先会向被访问对象请求一个迭代器(@@iterator
)对象,然后通过调用迭代对象的next()
方法来遍历所有返回值.
因为数值有内置的迭代器@@iterator
,因此for..of
可以直接应用在数组上.也可以手动调用迭代器遍历数组.
1 | var myArray=[1,2,3]; |
我们使用ES6中的符号Symbol.iterator来获取对象的@@iterator
内部属性。
普通的对象没有内置的@@iterator,无法直接用for..of
遍历.但我们可以手动给一个对象定义@@iterator
.
1 | var myObject={a:2,b:3}; |
Object
浅拷贝
ES6中定义了Object.assign(..)
方法实现浅拷贝. 第一个参数是目标对象,之后还可以跟一个或多个源对象.
它会遍历一个或多个源对象的所有可枚举(enumerable,参见下面的代码)的自有键(ownedkey,很快会介绍)并把它们复制(使用=操作符赋值)到目标对象,最后返回目标对象
1 | function anotherFunction(){/*..*/} |
属性描述
ES5提供了Object.defineProperty(..)
对对象的属性进行描述
1 | Object.defineProperty(myObject,"a",{value:2, |
⚠️在configurable:false
情况下,我们可以将writable
状态从true
改为false
.但不能从false
改为true
.
configurable:false
还不会禁止删除该熟悉.
禁止扩展对象
Object.preventExtensions(..)
可以禁止对象进行扩展.
1 | var myObject={a:2}; |
Object.seal(..)
Object.seal(..)会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用Object.preventExtensions(..)并把所有现有属性标记为configurable:false。所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。
Object.freeze(..)
Object.freeze(..)会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(..)并把所有“数据访问”属性标记为writable:false,这样就无法修改它们的值。
这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。
设置setter和getter操作符
1 | var myObject = { |
in
和hasOwnProperty
区别
in操作符会检查属性是否在对象及其[[Prototype]]原型链中(参见第5章)。相比之下,hasOwnProperty(..)只会检查属性是否在myObject对象中,不会检查[[Prototype]]链。
1 | var otherObject={b:1}; |
object.keys(..)
和object.getOwnPropertyNames(..)
Object.keys(..)会返回一个数组,包含所有可枚举属性,Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举。
1 | var myObject={}; |
修改对象的原型链方式
有两种较安全修改一个对象原型链的方式:Object.create(proto,[propertiesObject])
和Object.setPrototypeOf(obj,prototype)
1 | // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__ |
Object.prototype.valueOf()
valueOf
是对象默认拥有的方法,作用是返回一个对象的“值”,默认情况下返回对象本身。
1 | var obj = new Object(); |
valueOf
方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法
1 | var obj = new Object(); |
Object.prototype.toString()
toString
是对象默认拥有的方法,它的作用是对象的字符串形式.
1 | var o1 = new Object(); |
数组、字符串、函数、Date 对象都分别部署了自定义的toString
方法,覆盖了Object.prototype.toString
方法。
1 | [1, 2, 3].toString() // "1,2,3" |
上面代码中,数组、字符串、函数、Date 对象调用toString
方法,并不会返回[object Object]
,因为它们都自定义了toString
方法,覆盖原始方法。
所有的js对象都有toString
方法,并且调用它获得字符串值会不一样,可以它来作为对象类型的判断.
1 | Object.prototype.toString.call(2) // "[object Number]" |
Function
判断函数是否是构造函数执行
function
函数在JS可以作为普通的函数执行,也可以作为面向对象中的构造函数(构造器模式)执行。可以在函数体内通过new.target
方式判断当前执行的类型
1 | function fn() { |
作者: Fynn
链接: https://fynn90.github.io/2018/07/21/Javacript%E4%B8%AD%E7%9A%84%E9%BB%91%E9%AD%94%E6%B3%95/
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可