
「JavaScript灵魂之问」前端知识梳理之 JS 篇(中秋特别篇)
JS 错误信息类型
语法错误
1、SyntaxError 语法错误
1 | // 变量名不规范 |
SyntaxError: Unexpected number
意外的数字
1 | var 1ab = 1; |
SyntaxError: Invalid or unexpected token
其中token
指代标记的意思。
1 | // 关键字不可赋值 |
SyntaxError: Unexpected token '='
1 | // 基本语法错误 |
SyntaxError: Unexpected token ':'
引用错误
2、ReferenceError 引用错误、
1 | // 变量或函数未被声明 |
ReferenceError: test is not defined
1 | console.log(a); |
ReferenceError: a is not defined
1 | // 给无法被赋值的对象赋值的时候 |
SyntaxError: Invalid left-hand side in assignment
范围错误
3、RangeError 范围错误
1 | // 数组长度赋值为负数 |
RangeError: Invalid array length
1 | // 对象方法参数超出范围 |
RangeError: toFixed() digits argument must be between 0 and 100
类型错误
4、TypeError 类型错误
1 | // 调用不存在的方法 |
TypeError: 123 is not a function
1 | var obj = {}; |
TypeError: obj.say is not a function
1 | // 实例化原始值 |
TypeError: "string" is not a constructor
URI 错误
5、URIError URI错误
URI
: uniform resource identifier (统一资源标识符)
URL
:uniform resource locator(统一资源定位符)
URN
:uniform resource name(统一资源名称)
eval 函数执行错误
6、EvalError eval函数执行错误
这个就了解一下好了,在 es3
就不建议使用了。它的性能不太好,也不好调试,同时可能会引起安全性问题xss
攻击(例如 eval
和输入框 input
值相绑定时容易引起 xss
安全性问题 ),另外,可读性也不太好(在字符串中执行)。
1 | eval('var a=1; console.log(a);'); |
输出结果为 1
。
它神奇的地方在于,它可以将 json
数据转换成对象。
try / catch
1 | try { |
输出结果如下:
1 | 正常执行1 |
严格模式
从 ES5
开始有了两种模式,一种是严格模式,一种是正常模式。IE9及以下不支持严格模式
。
'use strict';
为什么要设置字符串,是因为字符串相当于表达式,在浏览器不会报错。并且书写的位置必须在最上面一行。
补充知识点:with()
语法就是括号里面填入哪个对象,就会找到对应作用域,也就是作用域链会被改变(严格模式下不能使用)
另外,arguments
下大部分属性严格模式不能使用,例如之前提到的 callee / caller
,但是 arguments
可以使用。
其次,严格模式下这种代码也会报错,说 b
没有声明:
1 | ; |
那我们看看下面会输出什么,是 1
嘛?
1 | function test(){ |
查看答案
结果如下,会打印包装类 Number
,因为 this
会指向对象,所以会进行一次包装。而在严格模式下就会输出 1
。
继续,再看看这份代码在 es5
严格模式下会输出什么?
1 | ; |
查看答案
答案是 2
,这个比较特殊,严格模式下不报错。
补充:在 es5
严格模式下,函数的参数名不能重复。
继续,再看看这份代码在 es5
严格模式下会输出什么?
1 | ; |
查看答案
答案是 1 报错
,因为严格模式下 eval
有自己独立作用域,不会将 var a
放入全局使用了。
垃圾回收机制
垃圾回收机制就是负责管理代码执行过程中使用的内存。
原理:
第一步,找出不再使用的变量
第二步,释放其占用内存
第三部,固定的时间间隔运行
重新探究 this 指向问题
第一题
开门见山,我们来看看下面会打印什么?
1 | function test(){ |
查看答案
答案是 window 1 1
,不难发现,这个 this
是指向 window
的。
第二题
继续,看看下面代码又会输出什么?
1 | var a = 1; |
查看答案
答案是 window 1
,和上题类似。
第三题
继续,又会有怎样的输出呢?
1 | function test(a){ |
查看答案
答案是 1 1
第四题
稍微改一下上面代码,又会输出什么呢?
1 | function test(a){ |
查看答案
答案是 1 undefined
,因为 window
底下并没有 a
这个变量。
第五题
接下来,我们再来看看这道题,会打印什么呢?
1 | function test(a){ |
查看答案
答案是 undefiend
,是不是很惊讶?为什么呢?我明明都访问了原型上的say
方法,而且全局都有 a
变量,为啥呢?
我们不妨打印一下 this
,看指向谁。
1 | function test(a){ |
然后发现在原型上打印 this
,发现指向的是那个函数,而函数如果没有返回值的话,默认会返回 undefined
,当然.a
也会打印 undefined
。
第六题
于是我们修改一下代码:
1 | function test(a){ |
查看答案
答案是 223
。
第七题
因此在函数没有实例化的时候,原型上 say
方法 this
还是会指向那个函数。那么我们再次修改一下,看看下面代码会输出什么
1 | function test(a){ |
查看答案
答案是 1
,实例化了之后,原型上 say
方法的 this
指向了实例对象。
defineProperty
1 | function defineProperty(){ |
查看答案
结果如下,它可以对一个对象赋予对应属性。并且该对象原本就是空的。
defineProperties
上述方式与我们直接通过 .
给对象赋值类似,那么怎么一下赋予多个属性呢,看如下代码:
1 | function defineProperty(){ |
有了前置知识后,我们继续修改一下上述代码,看看又会有怎样的变化:
1 | function defineProperty(){ |
查看答案
结果如下:
我们发现得到的 obj
属性值不可改变,属性也不可以枚举,并且属性也不可以被删除。
因此,总结一下,通过 Object.defineProperty
配置的对象默认不可修改,不可枚举,不可删除。
对于默认值,我们当然可以进行修改,查看下面代码注释处:
1 | function defineProperty(){ |
结果如下:
数据劫持引入
看这标题,是不是突然觉得高大上了,但实则不是,那我们看一下下面代码会输出什么吧:
1 | function defineProperty(){ |
查看答案
结果如下:
从上述结果我们可以总结归纳数据劫持的要点:
数据劫持,无非就是对待一个对象它的取值(get
)和设置值(set
)有一系列的配置和阻止的方法,这就是对一组数据属性的劫持。(即阻拦正常的输入输出)
对数组进行操作
我想你们应该都知道,defineProperty
没办法对数组进行劫持。
1 | function DataArr(){ |
查看答案
打印结果如下:
1 | A new value 123 hash been pushed to _arr |
Proxy
1 | let obj = new Proxy(target, handler); |
defineProperty
它可以对一个对象赋予对应属性。并且该对象原本就是空的。而 proxy
代理是操作原本就有属性的对象。
主要功能:自定义对象属性的获取、赋值、枚举、函数调用等
代理基础
那么,我们看看如下代码吧:
1 | var target = { |
查看答案
答案是 This is property value 1
。发现new
了以后产生了一个新的代理对象,并且自定义了对象属性的获取。
继续,我们看看 set
方法又是怎样的呢?
1 | var target = { |
查看答案
打印结果为 This is property value 3
和 { a: 3, b: 2 }
,解释一下,尽管操作的代理对象,但是如果我们对代理对象进行了修改,原对象也会跟着改。
操作函数
1 | let fn = function () { |
查看答案
打印结果为 123This is a Proxy return
。
操作数组
1 | let arr = [{ name: 'Chocolate', age: 21 }, { name: 'jack', age: 20 }]; |
查看答案
打印结果如下:
ES 14种操作对象的方法
获取原型
1 | var obj = {a:1,b:2}; |
打印结果如下:
设置原型
1 | var obj = { a: 1, b: 2 }; |
打印结果如下:
获取对象的可拓展性
1 | var obj = { a: 1, b: 2 }; |
打印结果为 true false
。
诶,从中我们发现了一个新东西 freeze
,作用就是冻结对象,与之相关的还有一个 seal
,叫做封闭对象,(简称自闭…开个玩笑^_^
),还是举一下例子,对比一下,先来介绍 seal
封闭对象:
1 | var obj = { a: 1, b: 2 }; |
打印结果如下,总结三点:不可修改、不可删除、可写。外加可读。
1 | var obj = { a: 1, b: 2 }; |
打印结果如下,总结三点:不可修改、不可删除、不可写,仅可读。
获取自有属性
1 | var obj = { a: 1, b: 2 }; |
答案是 [ 'a', 'b' ]
。
禁止拓展对象
1 | var obj = { a: 1, b: 2 }; |
答案是 { a: 1, b: 2 }
和 { b: 2 }
,我们无法对 obj
对象进行拓展,但是可以进行删除操作。简单来说就是禁止增加属性,但可删除属性。
拦截对象操作
1 | // var obj = { a: 1, b: 2 }; |
判断是否是自身属性
1 | var obj = { a: 1, b: 2 }; |
答案是 true
。
获取对象属性
1 | var obj = { a: 1, b: 2 }; |
打印结果如下:
1 | false |
设置对象属性
1 | var obj = { a: 1, b: 2 }; |
答案{ a: 3, b: 4 }
.
删除对象属性
1 | var obj = { a: 1, b: 2 }; |
答案是 { b: 2 }
。
枚举对象属性
1 | var obj = { a: 1, b: 2 }; |
1 | 1 |
获取键集合
1 | var obj = { a: 1, b: 2 }; |
答案 [ 'a', 'b' ]
调用函数
1 | // 13.调用函数 |
new实例化对象
1 | // 14.实例化对象 |
自己实现一个 Proxy
其实,有了前置知识,我们不难发现,Proxy
和 Object.defineProperty
实现效果是一样的,但其实两个原理上是有挺大差别的。
defineProperty
操作的是空对象,而proxy
操作的是现有对象。defineProperty
原本目的是对一个对象赋予对应属性,而proxy
代理是操作原本就有属性的对象。其主要功能是自定义对象属性的获取、赋值、枚举、函数调用等
那么,我们首先看看 Proxy
是怎样使用的,看一下下面这个例子,然后我们再用 defineProperty
自己实现一个 Proxy
。
1 | let target = { |
打印结果如下:
1 | Get:a=1 |
1 | let target = { |
打印结果如下,和上述源代码结果一样。
1 | Get:a=1 |
这里再进行梳理一下,MyProxy
实现原理是首先,先拷贝一份原对象,因为原本的 Proxy
就是返回了一个代理对象,而我们先深拷贝一份对象,然后遍历这个拷贝对象,依次让对象的属性通过 Object.defineProperty
来实现数据劫持的效果,里面用到了 get
和 set
方法,而get
和 set
时直接操作我们的原对象就是,这样当我们对代理对象(即我们一开始拷贝的对象)操作时,就会劫持我们的 get
和 set
方法,这样就能直接操作原对象了。
最终效果就是我们自定义了对象属性的获取、赋值的方式,不直接操作原对象,而是操作这个代理对象即可。
学习 Reflect
Reflect
是 ES6
中出版的一个内置对象,也叫作反射。由于我们很多对象的方法都是放在 Object
上的,但是我们有时候并不是一直操作 Object
,还有可能是函数或者数组等。这种情况下,我们放入 Object
里面就不太合理。因此,ES6
推出了Reflect
,它是一个方法集合的容器。
而原本我们 get
函数里面就是一个取值的方法,而并不是一个函数式的来操作对象,而 Reflect
里面就有 get
方法来做,同时也有对应的 set
方法,这样就是用底层方法来做了,而不是用一个等式来做,并且它是有返回值的(Boolean类型),可以确保我们 set
成功。下面,我们来看一下下面这个经典例子吧:
1 | let target = { |
打印结果如下:
1 | 1 |
接下来,就是对于 Reflect
的整理归纳:
Reflect
可以看做是一个新的方法容器,未来许多方法都会逐步放入Reflect
中,而Object
主要用来构造原型、构造对象的,并且Object
里面方法是杂多的。Object
返回值往往会抛出异常,例如像defineProperty
,in
这样,通常会抛出异常。而Reflect
方法一般都有返回值true / false
,操作性更加合理。Reflect
里面操作对象的方法和handler
几乎一致,除开枚举。Reflect
属于ES6
中全局内置对象,在哪都可以访问,直接保存静态方法,例如get
、set
、has
,不需要进行实例化操作。例如平常使用的Math.random
,也是可以直接使用的。