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