JS 错误信息类型 语法错误 1、SyntaxError 语法错误
SyntaxError: Unexpected number
意外的数字
SyntaxError: Invalid or unexpected token
其中token
指代标记的意思。
SyntaxError: Unexpected token '='
SyntaxError: Unexpected token ':'
引用错误 2、ReferenceError 引用错误、
ReferenceError: test is not defined
ReferenceError: a is not defined
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 类型错误
TypeError: 123 is not a function
TypeError: obj.say is not a function
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);
补充:在 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();
第三题 继续,又会有怎样的输出呢?
function test (a ) { this .a = a; console .log(this .a); console .log(window .a); } test(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();
第七题 因此在函数没有实例化的时候,原型上 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 };var proto = Object .getPrototypeOf(obj);console .log(proto);console .log(obj.__proto__);console .log(Object .prototype);
打印结果如下:
设置原型 var obj = { a : 1 , b : 2 };Object .setPrototypeOf(obj, { c : 3 , d : 4 });console .log(obj);
打印结果如下:
获取对象的可拓展性 var obj = { a : 1 , b : 2 };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 };Object .setPrototypeOf(obj, { c : 3 , d : 4 });console .log(Object .getOwnPropertyNames(obj));
答案是 [ 'a', 'b' ]
。
禁止拓展对象 var obj = { a : 1 , b : 2 };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 };console .log(obj.hasOwnProperty('a' ));
答案是 true
。
获取对象属性 var obj = { a : 1 , b : 2 };console .log('c' in obj);console .log('a' in obj);console .log(obj.a);
打印结果如下:
设置对象属性 var obj = { a : 1 , b : 2 };obj.a = 3 ; obj['b' ] = 4 ; console .log(obj);
答案{ a: 3, b: 4 }
.
删除对象属性 var obj = { a : 1 , b : 2 };delete obj.a;console .log(obj);
答案是 { b: 2 }
。
枚举对象属性 var obj = { a : 1 , b : 2 };for (var k in obj) { console .log(obj[k]); }
获取键集合 var obj = { a : 1 , b : 2 };console .log(Object .keys(obj));
答案 [ 'a', 'b' ]
调用函数 var obj = { a : 1 , b : 2 };function test ( ) { }test(); obj.test = function ( ) { } obj.test();
new实例化对象 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 ;
打印结果如下:
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 ;
打印结果如下,和上述源代码结果一样。
这里再进行梳理一下,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 ;
打印结果如下:
接下来,就是对于 Reflect
的整理归纳:
Reflect
可以看做是一个新的方法容器,未来许多方法都会逐步放入 Reflect
中,而 Object
主要用来构造原型、构造对象的,并且 Object
里面方法是杂多的。
Object
返回值往往会抛出异常,例如像 defineProperty
,in
这样,通常会抛出异常。而 Reflect
方法一般都有返回值 true / false
,操作性更加合理。
Reflect
里面操作对象的方法和 handler
几乎一致,除开枚举 。
Reflect
属于 ES6
中全局内置对象,在哪都可以访问,直接保存静态方法,例如 get
、set
、has
,不需要进行实例化操作。例如平常使用的 Math.random
,也是可以直接使用的。