严格模式(Strict mode)
-
严格模式开启检测和一些其他措施,使JavaScript变成更整洁的语言。推荐使用严格模式。为了开启严格模式,只需在JavaScript文件或script标签第一行添加如下语句:
'use strict';
你也可以在每个函数上选择性开启严格模式,只需将上面的代码放在函数的开头:
function functionInStrictMode() { 'use strict';}
1.全局变量显式声明!:在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。
"use strict";
v = 1; // 报错,v未声明
for(i = 0; i < 2; i++) { // 报错,i未声明
}
2.增强的安全措施(禁止this关键字指向全局对象)
// 正常模式
function f(){
return this;
}
// “this”指向全局对象
// 严格模式
function f(){
"use strict";
return this;
}
// 严格模式下,this的值为undefined
//使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错。
function f2(){
"use strict";
this.a = 1;
};
f2();// 报错,this未定义
//禁止在函数内部遍历调用栈
function f3(){
"use strict";
f3.caller; // 报错
f3.arguments; // 报错
}
f3();
3.禁止删除变量(严格模式下只有configurable设置为true的对象属性,才能被删除。)
"use strict";
var x;
delete x; // 语法错误
var o = Object.create(null, {
x: {
value: 1,
configurable: true
}
});
delete o.x; // 删除成功
4.显式报错
// 严格模式下,设置字符串的length属性,会报错。
'use strict';
'abc'.length = 5;
//正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。
var o = {};
Object.defineProperty(o, "v", { value: 1, writable: false });
o.v = 2; // 报错
//严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。
var p = {
get v() { return 1; }
};
p.v = 2; // 报错
//严格模式下,对禁止扩展的对象添加新属性,会报错。
var o = {};
Object.preventExtensions(o);
o.v = 1; // 报错
// 严格模式下,删除一个不可删除的属性,会报错。
delete Object.prototype; // 报错
5.重名错误
(1)对象不能有重名的属性
正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。
"use strict";
var o = {
p: 1,
p: 2
}; // 语法错误
(2)函数不能有重名的参数
正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。
"use strict";
function f(a, a, b) { // 语法错误
return ;
}
6.禁止八进制表示法
正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。
"use strict";
var n = 0100; // 语法错误
7.arguments对象的限制(arguments是函数的参数对象,严格模式对它的使用做了限制。)
(1)不允许对arguments赋值
"use strict";
arguments++; // 语法错误
var obj = { set p(arguments) { } }; // 语法错误
try { } catch (arguments) { } // 语法错误
function arguments() { } // 语法错误
var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误
(2)arguments不再追踪参数的变化
function f(a) {
a = 2;
return [a, arguments[0]];
}
f(1); // 正常模式为[2,2]
function f(a) {
"use strict";
a = 2;
return [a, arguments[0]];
}
f(1); // 严格模式为[2,1]
(3)禁止使用arguments.callee
"use strict";
var f = function() { return arguments.callee; };
f(); // 报错
8.函数必须声明在顶层
将来JavaScript的新版本会引入“块级作用域”。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。
以下代码在if代码块和for代码块中声明了函数,在严格模式下都会报错。
"use strict";
if (true) {
function f1() { } // 语法错误
}
for (var i = 0; i < 5; i++) {
function f2() { } // 语法错误
}
9.保留字
为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。使用这些词作为变量名将会报错。
ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。
function package(protected) { // 语法错误
"use strict";
var implements; // 语法错误
}
内置类型
JS 中分为八种内置类型null,undefined,boolean,number,string,symbol,BigInt,Object 。注意:对象和数组都属于Object 类型
Typeof
typeof 的详细判断
typeof "John" // 返回 string
typeof 3.14 // 返回 number
typeof NaN // 返回 number
typeof false // 返回 boolean
typeof [ 1,2,3,4] // 返回 object
typeof {name: 'John', age:34} // 返回 object
typeof new Date() // 返回 object
typeof function () {} // 返回 function
typeof myCar // 返回 undefined (if myCar is not declared)
typeof null // 返回 object
判断JS数据类型的四种方法
1、typeof
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。
缺陷:typeof 操作符会返回一些令人迷惑但技术上却正确的值:
- 对于基本类型,除 null 以外,均可以返回正确的结果。
- 对于引用类型,除 function 以外,一律返回 object 类型。
- ( [ ] 和 { } )均返回object类型
typeof ''``;``// string 有效
typeof 1;``// number 有效
typeof Symbol();``// symbol 有效
typeof true``;``//boolean 有效
typeof undefined;``//undefined 有效
typeof null``;``//object 无效
typeof [] ;``//object 无效
typeof new Function();``// function 有效
typeof new Date();``//object 无效
typeof new RegExp();``//object 无效
2、instanceof
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型,我们用一段伪代码来模拟其内部执行过程
function instanceof(Self, Target) {
// 获得(目标类型)的原型
var prototype = Target.prototype
// 获得(自身对象)的原型
Self = Self.__proto__
// 判断(自身对象)的类型 是否等于(目标类型)的原型
while (true) {
if (Self === null) return false
if (prototype === left) return true
left = left.__proto__
}
}
instanceof 只能用来判断两个对象是否属于实例关系**, 而不能判断一个对象实例具体属于哪种类型。因为如果别人如果更(改了原型)就会导致类型判断错误**
3、constructor
当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。如下所示:
可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上。接下来我们使用 constructor 来进行类型判断
var a = "iamstring.";
var b = 222;
var c= [1,2,3];
var d = new Date();
var e = function(){alert(111);};
var f = function(){this.name="22";};
alert(a.constructor === String) ----------> true
alert(b.constructor === Number) -----------> true
alert(c.constructor === Array) ----------> true
alert(d.constructor === Date) -----------> true
alert(e.constructor === Function) -------> true
// 注意: constructor 在类继承时会出错
function A(){};
function B(){};
A.prototype = new B(); //A继承自B
var aObj = new A();
alert(aobj.constructor === B) -----------> true;
alert(aobj.constructor === A) -----------> false;
// 在new 的时候constructor 指向了实例所以出错。需要手动设置回来才能用 constraint 判断
aObj.prototype.constructor = A // 将自己的类赋值给对象的constructor属性
alert(aobj.constructor === A) -----------> true;
alert(aobj.constructor === B) -----------> false;
可以看到 constraint 在类继承时就会报错,这种方法似乎也不完美。
4、Object.prototype.toString.call():(建议使用):通用但很繁琐的方法
var a = "iamstring.";
var b = 222;var c= [1,2,3];
var d = new Date();
var e = function(){alert(111);};
var f = function(){this.name="22";};
alert(Object.prototype.toString.call(a) === '[object String]') // --> true;
alert(Object.prototype.toString.call(b) === '[object Number]') // --> true;
alert(Object.prototype.toString.call(c) === '[object Array]') // --> true;
alert(Object.prototype.toString.call(d) === '[object Date]') // --> true;
alert(Object.prototype.toString.call(e) === '[object Function]') // --> true;
alert(Object.prototype.toString.call(f) === '[object Function]') // --> true;
// 获取类型的方法使用 slice 截取一下。前面的object 总是不会变
var type = Object.prototype.toString.call(a).slice(8,-1); // -->"String"
对象转基本类型
对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。并且这两个方法你是可以重写的。
var a = {
valueOf() {
return 0
}
}
当然你也可以重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。
var a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
1 + a // => 3
'1' + a // => '12'
隐式类型转换
大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:
1.字符串连接符与算术隐式转换规则混淆
console.log(1 + true); //2
console.log(1 + "true"); //"1true"
console.log(1 + undefined); //NaN
console.log(1 + null); //1
2.比较运算符会把其他数据类型转换number数据类型后再比较
-
字符串与数字比较时:字符串会先被转换成数字(注意:如果碰到转换不了的为NaN)
-
字符串跟字符串比较时:会先调用.charCodeAt()方法。这时比对的是字符串的第一个值,如果相等则继续比对第二第三个以此类推。
console.log("2" > 10); //false console.log("a" > 10); //false "a" --> Nan console.log(10 > "a"); //false console.log(Number("a")); //NaN console.log("2" > "10"); //true '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true console.log(false == 0); //true console.log(false == ""); //true console.log(Number(false)); //0 console.log(NaN == NaN); //false console.log(undefined == null); //true console.log(Number(NaN)); //NaN console.log(Number(undefined)); //NaN console.log(Number(null)) //0
3.(引用)类型的隐式转换
先使用 valueOf() 方法获取原始值,在使用 toString() 方法进行类型转换
console.log([1,2] == '1,2'); // true [1,2].valueOf().toString() --> '1,2'
console.log([1,2] + '2'); // "1,22" [1,2].valueOf().toString() --> '1,2'
console.log([1,5] + 2); // "1,52" [1,5].valueOf().toString() --> '1,5'
console.log([] == 0); // true [].valueOf().toString() --> '0'
console.log([] === 0); // false [].valueOf().toString() --> '0'
var a = {};
console.log(a == 0); // false a.valueOf().toString() --> "[object Object]"
console.log(a + 2); // "[object Object]2"
//大坑
console.log ( [] == 0 ); //true [].valueOf().toString() --> ""
console.log ( [] === 0 ); //false [].valueOf().toString() --> ""
console.log ( ![] == 0 ); //true ![] --> false
//神坑
console.log ( [] == ![] ); //true ''==false 所以为true
console.log ( [] == [] ); //false (注意:这里比对的是引用内存空间)
//史诗级坑
console.log({} == !{}); //false {} --> "[object Object]" !{} --> false
console.log({} == {}); //false (注意:这里比对的是引用内存空间)
var a = {}
a == "[object Object]" // true a.valueOf().toString() --> "[object Object]"
a + 3 // "[object Object]3"
a.valueOf = function(){
return 3
}
a == "[object Object]" // false 注意重写了valueOf()方法。所以最后结果返回 3
为什么会是6?而不是'33'? 因为如果是 + 两个方法或只有valueOf()重写了 只会调用valueOf()方法
a + 3 // 6 a.valueOf() --> 3
浏览器本地存储
cookie,localStorage,sessionStorage,indexDB
特性
cookie
localStorage
sessionStorage
indexDB
数据生命周期
一般由服务器生成,可以设置过期时间
除非被清理,否则一直存在
页面关闭就清理
除非被清理,否则一直存在
数据存储大小
4K
5M
5M
无限
与服务端通信
每次都会携带在 header 中,对于请求性能影响
不参与
不参与
不参与
从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。
var obj = new Base(); js中的new()到底做了些什么??
-
创建一个新对象
-
将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
-
执行构造函数中的代码(为这个新对象添加属性);
-
返回新对象。
(1) 创建一个新对象; var obj = {}; (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) obj.proto = Base.prototype;
(3) 执行构造函数中的代码(为这个新对象添加属性) Base.call(obj); (4) 返回新对象。 return obj;
JavaScript 的继承
圣杯模式缺陷
-
挂载在父级的私有属性没有赋值过来,只继承了原型链里面的方法。
-
覆盖了子级的原型方法
-
当 for in 的时候 会出现异常, 多了一个 继承的 constructor 可枚举属性
function extend(Target,Father){ function F(){}; //F是中间层的继承 而Origin是真正的被继承的类, 是超类 F.prototype=Father.prototype; Target.prototype=new F(); Target.prototype.constructor=Target;//归位!把丢失的 constructor 重新赋值回来} function Father(){ this.age = 18; }
function Son(){ }
Son.prototype.getAge = function(){
}
extend(Son,Father);
var Example =new Son();
// Uncaught TypeError: Example.getAge is not a function at 子级的原型方法丢失 // Example.getAge();
// 当 for in 的时候 会出现异常, 多了一个 继承的 constructor 可枚举属性 for (var i in Example) {
console.log( Example[i] ); }
ES6(Babel编译后的继承代码逻辑)
-
重写原型 prototype和构造器 constructor,设置__proto__的执向到父级
-
重写原型 prototype 和构造器 constructor 后原有的child方法丢失,重新赋值上去。
-
父级的私有数据通过利用 Super.apply(this, arguments);拷贝过来,最后返回。
-
具体实现链接:juejin.cn/post/699112…
js基础实战
模仿call
-
第一个参数为
null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如String、Number、Boolean -
将函数作为传入的上下文(
context)属性执行 -
函数执行完成后删除该属性
-
返回执行结果
Function.prototype.myCall = function(context) { context = context || window var key = "fn" context[key] = this var result = contextkey delete context[key] return result }
模仿apply
Function.prototype.myApply = function(context) {
context = context || window
const key = "fn"
context[key] = this
const result= context[key](arguments[1])
delete context[key]
return result
}
模仿bind
-
使用
call / apply指定this -
返回一个绑定函数
Function.prototype.myBind = function(context) { var fn = context || window; var _this = this; var bindFn = function () { return _this.call(fn, arguments[1]);
} return bindFn }
模仿instanceof
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
function instanceof(Self, Target) {
// 获得(目标类型)的原型
var prototype = Target.prototype
// 获得(自身对象)的原型
Self = Self.__proto__
// 判断(自身对象)的类型 是否等于(目标类型)的原型
while (true) {
if (Self === null) return false
if (prototype === left) return true
left = left.__proto__
}
}
分解URL参数
使用正则表达式。
var URL = 'http://weibo.com/?c=spr_qdhz_bd_360jsllqcj_weibo_001&key2=JavaScript';
function queryURLParameter(URL){ //使用正则分解URL参数
var reg = /([^&?=]+)=([^&?=]+)/g,
obj = {};
URL.replace(reg,function() {
obj[arguments[1]] = arguments[2];
console.log(arguments)
});
return obj;
}
console.log(queryURLParameter(URL));
使用字符串附加在原型链身上。
Object.defineProperty(String.prototype, 'queryURLParameter', {
value: function() {
var obj = {};
if(this.indexOf('?')<0) return obj; //如果没有找到(?) 侧把(obj)原封不动返回出去
var URLDATA = this.split('?'); //用(?)分割路劲
var URL = URLDATA[1]; //此步骤把URL地址(?)右边的参数赋值下来
URLDATA = URL.split('&'); //用(&)把参数分割成数组 如( ["data"="1","data2"="2"] )
for (var i = 0; i < URLDATA.length; i++) {
var array = URLDATA[i].split('='); //把数组每一项用(=)分割出来
obj[array[0]] = array[1]; //把(array[0])当作对象(obj的键名) 把(array[1])当作obj的键值 赋值给obj
}
return obj;
},
configurable: true, //是否可以删除属性,是否可以修改属性的 writable 、 enumerable 、 configurable 属性。
enumerable: false, //是否可枚举
writable: true //是否能重写 默认为false
});
console.log(URL.queryURLParameter());
封装cookie
var Cookie = (function(){ //使用闭包的方式 向外暴漏封装的方法
function setCookie(key,value,t){ //添加一个Cookie
var oDate=new Date();
oDate.setDate(oDate.getDate()+5); //得到一个对象的时间
// oDate.toGMTString(); //为了兼容ie 将时间对象转化为字符串
oDate.setDate(oDate.getDate()+t); //得到一个对象的时间,(设置未来过期天数)
document.cookie = key+'='+value+';expire='+oDate.toGMTString();
}
function getCookie(key){ //获取Cookie的值
var arr = document.cookie.split(';');//获取到字符串Cookie 以;号分割为数组
for( var i=0; i<arr.length;i++){
var arr2 = arr[i].split('=');
if(arr2[0]==key){//如果与传进来的(key)相同
return decodeURI(arr2[1]);//返回解码后的(value)值
}
}
}
function removeCookie(key){ //删除一个Cookie
setCookie(key,'',-1); //把值赋值为空 把过期时间设置为-1(让Cookie过期)
}
return {
set:setCookie,
get:getCookie,
remove:removeCookie
}
})();
// console.log(Cookie.set("name","wangsheng",3));
// console.log(Cookie.get("name"));
// console.log(Cookie.remove("name"));
多维数组去重
Object.defineProperty(Array.prototype, 'heavy', {
//在数组的原型上(添加一个)公有的( 数组去重方法 ),传入true代表多维数组去重
value: function(data) {
function checkedType(Target){ // 检测数据类型功能函数
// 返回的是 【Object Array】 类型这样的检测数据类型
return Object.prototype.toString.call(Target).slice(8,-1);
}
for (var i = 0; i < this.length; i++) {
if( checkedType(this[i])== "Array"&&data ){ // 判断是否需要N维数组去重
this[i].heavy(true);
}
//console.log(this.indexOf(this[i])!=i);
//indexOf如果找不到相同元素就会返回(-1)indexOf找到相同的之后就会返回相应下标
if(this.indexOf(this[i])!=i){
this.splice(i,1); //删除数组元素后数组长度减1后面的元素前移
i--; //数组下标回退
}
}
return this; //把(去重后的数组)重新返回出去
},
configurable: true, //是否可以删除,修改 writable 、 enumerable 、 configurable 属性。
enumerable: false, //是否可枚举
writable: true //是否能重写 默认为false
});
// var q1 = [5,"1aa",1,2,3,4,2,1,3,3,2,"1aa",[1,2,3,1,2,[1,2,[1,2,3,1,2],3,1,2]]];
// q1.heavy(true);
// console.log(q1);
N维数组去重
Object.defineProperty(Array.prototype, 'max', {
value: function() { // N维数组求最大值
function checkedType(Target){ // 检测数据类型功能函数
// Object.prototype.toString.call(Target) 返回的是 【Object Array】 类型这样的检测数据类型
return Object.prototype.toString.call(Target).slice(8,-1);
}
var arr = 0;
for(var key in this){
if(!this.hasOwnProperty(key)) continue; //如果不是 for in 遍历出来的不是自身属性 则跳出当前循环
// console.log(this.hasOwnProperty(key),this[key]);
if( checkedType(this[key])=="Array"){ //如果遍历的是数组
// console.log(this[key],typeof this[key]);
if(arr<=this[key].max()){
arr = this[key].max();
}
}else if(checkedType(this[key])=="Object"){ //如果遍历的是对象
for(var key2 in this[key]){
// console.log(this[key],this[key][key2]);
if( checkedType(this[key][key2])=="Array"||checkedType(this[key][key2])=="Object" ){
// console.log(this[key],typeof this[key]);
if(arr<=this[key].max()){
arr = this[key].max();
}
}else if( arr<=this[key][key2] ){
arr = this[key][key2]
}
}
}
else if(arr<=this[key]){
arr=this[key];
}
}
return arr;
},
configurable: true, //是否可以删除属性,是否可以修改属性的 writable 、 enumerable 、 configurable 属性。
enumerable: false, //是否可枚举
writable: true //是否能重写 默认为false
});
深拷贝克隆
var DeepClone = (function(){ // 深度克隆方法
function checkedType(Target){ // 检测数据类型功能函数
return Object.prototype.toString.call(Target).slice(8,-1);
}
function clone(Target){
var data,TargetType = checkedType(Target);
// 第一步根据传入的数据类型(初始化数据类型)
if( TargetType === 'Object' ) data = {};
else if( TargetType === "Array" ) data = [];
else return Target;
// 第二步遍历数据
for( var key in Target ){
var value = Target[key]; //获取遍历数据结构的每一项
if( checkedType(value)==='Object'||checkedType(value)==='Array' ){
data[key] = clone(value);
} else data[key] = value;
}
// 第三步把data数据返回出去
return data;
}
return function(Target){
return clone(Target);
}
})();
// var arr3 = [1,2,[3,4,{name:"wang2",age:19}],{ name:"wang",age:18 },2];
// var arr4 = DeepClone( arr3 );
发布订阅模式
var Emitter = function(){
var listener = {}
function subscribe(event,fn){
if(listener[event]&&listener[event]!==undefined){
listener[event]=[...listener[event],fn];
}else{
listener[event] = []
listener[event].push(fn)
}
return ()=>{
listener[event]=listener[event].filter(item=>item!=fn)
}
}
function emit(event,...args){
if(!listener[event]||listener[event]===undefined){
console.log(listener[event],1);
return false
}
listener[event].forEach(item=>item(...args));
}
return { subscribe,emit };
}
var emitter = Emitter();
var sub1 = emitter.subscribe('click',(...args)=>{console.log(...args,"sub1")});
var sub2 = emitter.subscribe('click',(...args)=>{console.log(...args,"sub2")});
emitter.emit('click',1,2,3);
sub1(); //释放
emitter.emit('click',1,2,3);
实现一个消息队列,题目如下:
var array = {
// [time]:[]
};
var time = null;
function start(number){
setTimeout(function(){
array[number+""].forEach(item=>{
item();
});
},0);
}
function LazyMan(str){
time = new Date().getTime()+Math.random();
array[time+""] = [];
array[time+""].push(function(){
console.log("Hi! This is "+str+"!");
});
start(time);
return this;
}
function sleep(number){
array[time+""].push(function(){
number = number*1000;
var start = (new Date()).getTime()+number;
while(new Date().getTime() < start ) {
continue;
}
});
return this;
}
function sleepFirst(number){
array[time+""].unshift(function(){
number = number*1000;
var start = (new Date()).getTime()+number;
while(new Date().getTime() < start ) {
continue;
}
});
return this;
}
function eat(str){
array[time+""].push(function(){
console.log("Eat " + str);
});
return this;
}
LazyMan("Hank").sleep(2).eat("dinner");
LazyMan("Hank2").sleepFirst(2).eat("supper");