valueOf() 与 toString()
除 null,undefined, 所有的js 数据类型都有这两个方法。
- toString(): 将数据类型转化为string类型
- valueOf(): 将数据类型转化为自身,原来是什么类型,转化后还是什么类型,日期类型除外(日期转化为 UTC 毫秒数,类型为 number类型)
正常情况下和 对象转换时,优先调用 toString(), 有运算操作符和强转数字的情况下,优先调用valueOf().
console.log({}+[]); // [object Object]
//解析: JS代码在()里面被包裹,会变成表达式执行
//primitite-->valueOf()-->toString()
//{}-->[object Object]
//[]-->''
{}+[]; //0
[]+{}; //[object Object]
{}+{}; //[object Object][object Object]
//特别注意: 如果第一个是{} (在{}前面不存在任何操作符)时,后面加上其他的像数组、数字或字符串,这时候, {} 会被解析成 空区块语言,加号运算会直接变为一元正号运算,也就是强制转为数字的运算。这是个陷阱要小心。
console.log([]==false); //true []会被转换成""
console.log({}==false); //false {}-->[object Object]
if([]){
console.log([]==false) //还是会打印,true ,[]在ECMA规范中就是true,C++[]是存在的,因为占用了内存
};
类型隐式转换成 数值类型
('b'+'a'+ +'a'+'b'+'a').toLocaleLowerCase();
//bananba "a'+ +'a'"中间的2个加号必须存在空格,不然就会报错。'a'+ +'a' => 'a'+ (+'a')
Boolean("0")==Boolean(0); //false "0"是字符串 ,0会变成fasle
console.log(NaN==0); //false
console.log(NaN<=0); //false
console.log(NaN>=0); //false NaN与其他数值进行比较的结果总是不相等的,包括它自身在内
console.log(null<=0); //true Number(null) = 0
console.log(1+null); //1
var obj={yd:{name:'xxx'}};
var x =+obj.yd?.name??'超级简单'; // ?? 空值合并操作符,当其左侧为null和undefined时,才会返回右侧的数
console.log(x) // NaN
//+obj.yd 变成一个表达式,让它立即执行 ?. 问的是这里是否存在name属性
typeof 、 instanceof 、 Object.prototype.toString.call(obj)
- typeof: 操作符返回一个字符串,表示未经计算的操作数的类型。
typeof null // object
typeof function(){} // function
// 其他 任何对象都返回 object。 因此,typeof 一般用来检测 基本数据类型。
- instanceof: 用来检测
constructor.prototype是否存在于参数object的原型链上 用法:
object instanceof constructor
// object 实例对象
// constructor 构造函数
- Object.prototype.toString.call(obj): 返回一个表示该对象的字符串 用法:
var toString = Object.prototype.toString;
toString.call(new Date) // [object Date]
toString.call(null) // [object null]
toString.call(undefined) // [object undefined]
var id='超级简单';
console.log(typeof id); //String 这里会走一下装箱,拆箱过程
console.log(id instanceof String); //false //这个会直接走原型链
作用域与作用域链
1)作用域:
定义: 简单来说,就是变量和函数可访问的范围。js中一个函数的变量不可以在函数外访问。
2)全局作用域、局部作用域、块级作用域
-
全局作用域:任何地方都可以访问的变量
- 函数外定义的变量
var n = 1; function () { var a = 1; // 局部变量,函数外面无法访问 return a; } console.log(fn()) console.log(n) console.log(a) // Uncaught ReferenceError: a is not defined- 未定义直接赋值的变量会自动声明为全局变量
var n = 1; function () { a = 1; return a; } console.log(fn()) console.log(n) console.log(a)- window对象的属性
-
局部作用域:只在固定的代码片段中可以访问的变量
-
块级作用域:ES6引入的块级作用域,
let和const都涉及块级作用域。块级作用域允许声明函数只在使用大括号的情况下成立,如果未使用大括号,会报错。
3)作用域链
定义:
当执行函数时,先从函数内部查找变量。
如果内部没找到,则会向创建函数的作用域查找。
依次向上,直到全局作用域为止。
如果全局作用域中也没有找到,则认定该变量未声明(xxx is not defined)
闭包
1)定义:
闭包函数:声明在一个函数中的函数。
闭包:内部函数可以访问到其所在外部函数中声明的参数和变量,即使在其外部函数被返回之后。使用闭包主要是为了设计私有的方法和变量。
2)特点:
- 让外部访问函数内部变量成为可能
- 局部变量会常驻在内存中
- 可以避免使用全局变量,防止全局变量污染
- 会造成内存泄露(有一块内存空间被长期占用,而不被释放)
3)闭包的创建:
闭包就是创建一个独立的环境,每一个闭包里的环境都是独立,互不干扰。
注意:每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。
5)案例解释:
案例一:
function funA(){
var a = 10; // funA的活动对象之中;
return function(){ //匿名函数的活动对象;
alert(a);
}
}
var b = funA();
b(); //10
案例二:
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
案例三
var i = 0;
function outerFn(){
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 2 3 4
案例四
(function() {
var m = 0;
function getM() { return m; }
function seta(val) { m = val; }
window.g = getM;
window.f = seta;
})();
f(100);
console.info(g()); //100 闭包找到的是同一地址中父级函数中对应变量最终的值
案例五
function fun(n,o) {
console.log(o);
return {
fun:function(m) {
return fun(m,n);
}
};
}
var a = fun(0); //undefined 执行 fun(0)
a.fun(1); //0 执行 fun(1, 0)
a.fun(2); //0 执行 fun(2, 0)
a.fun(3); //0 执行 fun(3, 0)
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); //undefined 0 1 1