JavaScript 秘密花园

发布于 2023-05-11 20:26:02 字数 7955 浏览 63 评论 0

JavaScript 秘密花园由两位 Stack Overflow 用户伊沃·韦特泽尔(写作)和张易江(设计)完成,由三生石上翻译完成,内容短小精炼。这次温故知新,做一番总结。

对象

对象使用和属性

JavaScript 中所有变量都是对象,除了两个例外 nullundefined

JavaScript 解析器错误,试图将点操作符解析为浮点数字值的一部分。

2.toString()    // SyntaxError

2..toString()   // 第二个点号可以正常解析
2 .toString()   // 添加空格
;(2).toString() // 2 先被计算

删除属性

删除属性的唯一方法是使用 delete 操作符,设置属性为 undefined 或者 null 并不能真正的删除属性,而仅仅是移除属性和值的关联。

原型

实现传统的类继承模型很简单,但是实现 JavaScript 中的原型继承则困难的多。

It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.

function Foo() {
  this.value = 42
}

Foo.prototype = {
  method: function () {},
}

function Bar() {}

// 设置 Bar.prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo()
Bar.prototype.foo = 'Hello World'

// 修正 Bar.prototype.constructor 为 Bar 本身
Bar.prototype.constructor = Bar

/**
 * test [Bar的实例]
 *     Bar.prototype [Foo的实例]
 *       { foo: 'Hello World' }
 *       Foo.prototype
 *           { method: ... }
 *           Object.prototype
 *               { toString:... }
 **/

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto)指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__

但它不应该与构造函数 func 的 prototype 属性相混淆。被构造函数创建的实例对象的 [[prototype]] 指向 func 的 prototype 属性。 Object.prototype 属性表示 object 的原型对象。

hasOwnProperty 函数

hasOwnProperty 是 JavaScript 中处理属性并且不会遍历原型链的方法之一(另一种方法: Object.keys())。当检查对象某个属性是否存在时,hasOwnProperty 是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总使用 hasOwnProperty 方法。

函数

函数声明与表达式

函数声明会被提前解析(hoisted),但函数表达式不会。

// 函数声明
foo()
function foo() {}

// 函数表达式
bar // undefined
bar() // TypeError
const bar = function () {}

命名函数的赋值表达式

函数名在函数内总是可见的。

const foo = function bar() {
  bar() // 正常运行
}
bar() // 出错:ReferenceError

上面代码中,bar 函数声明外是不可见的,这是因为已经把函数赋值给了 foo;然而在 bar 内部依然可见。这是由于 JavaScript 的命名处理所致,函数名在函数内总是可见的。

this 的工作原理

JavaScript 有一套完全不同于其它语言的对 this 的处理机制。在 5 种不同的情况下,this 指向的各不相同。

  • 全局范围内:this 指向全局对象。但在严格模式下,不存在全局变量,this 将会是 undefined。
  • 函数调用:this 指向全局对象。
foo() // this 指向全局对象
  • 方法调用:this 指向调用方法的对象。
test.foo() // this 指向 test 对象
  • 调用构造函数:在构造函数内部,this 指向新创建的对象。
new foo() // this 指向返回的新对象
  • 显示设置 this:调用 call 或者 apply 方法时,函数内 this 将会被显式设置为函数调用的第一个参数。
Foo.method = function () {
  function test() {
    // this 将会被设置为全局对象
  }
  test()
}

为了在 test 中获取对 Foo 对象的引用,需要在 method 函数内部创建一个局部变量指向 Foo 对象。因为 JavaScript 中不可以对作用域进行引用或赋值,所以不可以在外部访问私有变量。

Foo.method = function () {
  const that = this
  function test() {
    // 使用 that 来指向 Foo 对象
  }
  test()
}

arguments 对象

arguments 对象为其内部属性以及函数形式参数创建 getter 和 setter 方法。因此,改变形参的值会影响到 arguments 对象的值,反之亦然。

function foo(a, b, c) {
  arguments[0] = 2
  console.log(a) // 2

  b = 4
  console.log(arguments[1]) // 4

  var d = c
  d = 9
  console.log(c) // 3
}
foo(1, 2, 3)

命名空间

只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript 中,这可以通过匿名包装器轻松解决。

;(function () {
  // 小括号内的函数首先被执行, 并且返回函数对象
  // 函数创建一个命名空间
  window.foo = function () {
    // 对外公开的函数,创建了闭包
  }
})() // 立即执行此匿名函数, 也就是函数对象

// 另外两种方式
;+(function () {})()
;(function () {})()

数组

数组遍历与属性

由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, 因此会比普通的 for 循环慢上好多倍。为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

length 属性

length 属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。

var foo = [1, 2, 3, 4, 5, 6]
foo.length = 3
foo // [1, 2, 3]

类型

相等与比较

JavaScript 是弱类型语言,这就意味着等于操作符会为了比较两个值而进行强制类型转换。

'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n' == 0 // true

typeof 操作符

typeof 操作符(和 instanceof 一起)或许是 JavaScript 中最大的设计缺陷,因为几乎不可能从它们那里得到想要的结果。

尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用,那便是用来检测一个对象是否已经定义或者是否已经赋值,而这个应用却不是用来检查对象的类型。

typeof foo !== 'undefined'

上面代码会检测 foo 是否已经定义,如果没有定义而直接使用会导致 ReferenceError 的异常。 这是 typeof 唯一有用的地方。除非为了检测一个变量是否已经定义,应尽量避免使用 typeof 操作符。

JavaScript 类型表格

ValueClassType
'foo'Stringstring
new String('foo')Stringobject
1.2Numbernumber
new Number(1.2)Numberobject
trueBooleanboolean
new Boolean(true)Booleanobject
new Date()Dateobject
new Error()Errorobject
[1,2,3]Arrayobject
new Array(1, 2, 3)Arrayobject
new Function('')Functionfunction
/abc/gRegExpobject
new RegExp('meow')RegExpobject
{}Objectobject
new Object()Objectobject

上面表格中,Type 一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 object

Class 一列表示对象的内部属性 [[Class]] 的值。

JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString

function is(type, obj) {
  var clas = Object.prototype.toString.call(obj).slice(8, -1)
  return obj !== undefined && obj !== null && clas === type
}

is('String', 'test') // true
is('String', new String('test')) // true

为了检测一个对象类型,推荐使 Object.prototype.toString 方法,因为这是唯一一个可依赖的方式。

instanceof 操作符

instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义,如果用来比较内置类型,将会和 typeof 操作符一样用处不大。

function Foo() {}
function Bar() {}
Bar.prototype = new Foo()

new Bar() instanceof Bar // true
new Bar() instanceof Foo // true

// 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
Bar.prototype = Foo
new Bar() instanceof Foo // false

// instanceof 比较内置类型
new String('foo') instanceof String // true
new String('foo') instanceof Object // true
'foo' instanceof String // false
'foo' instanceof Object // false

需要注意:instanceof 用来比较属于不同 JavaScript 上下文的对象(比如浏览器中不同的文档结构)时将会出错,因为它们的构造函数不会是同一个对象。

instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。正如 typeof 操作符一样,任何其它的用法都应该是避免的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
24 人气
更多

推荐作者

亽野灬性zι浪

文章 0 评论 0

少年亿悲伤

文章 0 评论 0

南七夏

文章 0 评论 0

qq_EJoXxu

文章 0 评论 0

17780639550

文章 0 评论 0

萌逼全场

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文