变量类型
基本类型:Number、String、Boolean、Null、Undefined
引用类型:Array、Object
ES6新数据类型:Symbol
声明变量
var、let、const、function
不使用关键字,全局变量
window.a = 1,全局变量
判断变量类型
typeof
typeof返回的是类型字符串,它的返回值有6种:
1234567typeof 3; // "number"typeof "abc"; // "string"typeof true; // "boolean"typeof undefined; // "undefined"typeof null; // "object"typeof {}; // "object"typeof function(){}; // "function"instanceof
检测引用类型是具体什么类型的对象时使用instaceof,本质是检查某个对象的原型链是否包含某个构造函数的prototype属性。instanceof是通过原型链来检查类型的,所以适用于任何object的类型检查。instanceof对基本数据类型不起作用,因为基本数据类型没有原型链。
1234567[1, 2, 3] instanceof Array // true/abc/ instanceof RegExp // true({}) instanceof Object // true(function(){}) instanceof Function // true/*原型*/function Animal(){ }(new Animal) instanceof Animal // trueconstructor
constructor属性返回一个指向创建了该对象原型的函数引用(即函数本身)。与instanceof不同的是,在访问基本类型的constructor时,JS会自动调用其构造函数来生成一个对象。但是无法判断null和undefined。
1234567(3).constructor === Number // truetrue.constructor === Boolean // true'abc'.constructor === String // true[1,2,3].constructor === Array // true(function(){}).constructor === Function // truenull.constructor // TypeError!undefined.constructor // TypeError!Object.prototype.toString
toString会将当前对象转换成字符串输出。可以检查所有ECMA标准中内置的所有类型,但是无法检测用户自定义类型。
123456789101112toString = Object.prototype.toString;toString.call(new Date); // [object Date]toString.call('abc'); // [object String]toString.call(3); // [object Number]toString.call([]); // [object Array]toString.call({}); // [object Object]toString.call(undefined); // [object Undefined]toString.call(null); // [object Null]/*无法检测自定义类型*/function Animal(){};var ani = new Animal();toString.call(ani); // [object Object]
基本类型这里有点需要注意:
当基本字符串需要调用一个字符串对象才有的方法时,js会自动将基本字符串转化为字符串对象并且调用相应的方法。
ECMAScript中字符串是不可变的,一旦创建,值就不能改变。
看控制台的测试:
咦?那为啥字符串拼接就可以修改啊
拼接过程实际是:创建一个新字符串,填充’k’和’ad’,然后销毁原来的字符串’k’和’ad’.在旧版本的浏览器中,拼接字符串速度很慢。
记得剑指offer上说提高时间效率中,就有不要多次用String的+运算符来拼接字符串,因为会产生很多String的临时实例,造成空间和时间的浪费,更好的办法就是用StringBuffer的append方法来完成字符串的拼接。
那么JS中,现代浏览器已经解决了+运算符拼接低效的问题(怎么解决的啊。。。),还可以通过数组来实现字符串拼接:
|
|
而且通过new String(‘kad’)这种方式创建的字符串,虽然类型是object,但是依然不可改变值。Object.getOwnPropertyDescriptor(str, 0)可以输出Object的属性类型,发现writable为false。
基础类型和引用类型的存储:
基本类型就是保存在栈内存中的简单数据段,在内存中占有固定大小的空间,他们的值保存在栈空间中,按值访问。
而引用类型的对象,栈内存中存放该对象在堆内存中的访问地址(内存地址大小固定),由堆内存分配空间。当查询引用类型的变量时,先从栈中读取内存地址,然后通过地址找到堆中的值,也就是按引用访问。
为什么会有栈内存和堆内存之分?
与垃圾回收机制有关,为了使程序运行时占用的内存最小。
每个函数执行时都会建立自己的内存栈,调用结束就释放内存。堆内存中的对象不会随着调用的结束而销毁,只有当一个对象没有任何引用变量引用时,系统的垃圾回收机制才会回收它。
内存释放
引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收。
垃圾收集机制的原理:找出那些不再继续使用的变量,然后释放其占用的内存。垃圾收集器会按照固定的时间间隔周期性地执行这一操作。
JS中最常用的垃圾收集方式是标记清除。销毁带标记的值并回收所占用的内存空间。
还有一种垃圾收集策略叫引用计数,跟踪记录每个值被引用的次数。但无法解决循环引用的问题。
有关闭包的内存问题
闭包是否一定会造成内存泄漏?不一定。
闭包在使用之后没有将其引用设为null,GC就会认为这部分资源还在使用,所以GC就不会对其进行回收。由于闭包的作用域链可能会很长,所以GC会认为这条作用域链还有可能会再次使用,所以整个链的资源都不会回收,这种情况引起的内存泄漏比不清除引用更严重。
|
|
闭包引起的内存泄漏可以通过这个将变量设为null来解除对对象的引用,确保正常回收其占用的内存。
面试题:局部变量与全局变量的存储
全局变量存放在静态区(static),程序运行开始时为其分配内存,程序运行结束后该内存才被释放。
局部变量存储在栈(stack)中,随函数调用被申请和释放。
函数的参数传递
这个问题上,网上的说法不一,以红宝书的标准来: JS中所有函数的参数都是按值传递的。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量,用ECMAScript的概念来说就是arguments对象中的一个元素。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
证明一下对象是按值传递的(准确的说是call-by-sharing)
|
|
如果对象是按引用传递的,person应该被修改为指向name为’lz’的对象。实际上,在函数中重写obj时,这个变量引用的就是一个局部对象了,这个局部对象在函数执行完毕后立即被销毁。
那么,如何将值类型的变量以引用的方式传递:将基础类型包装成对象。
再来说说引用类型的拷贝
这种直接赋值,obj1和obj2指向的是堆内存中的同一个对象
数组的深拷贝:
- newArr = oldArr.slice(0, oldArr.length);//第二个参数可省略
- newArr = oldArr.concat();
对象的深拷贝
把对象属性遍历一遍赋给新对象
1234567function deepCopy(obj){var newObj = {};for(key in obj){newObj[key] = typeof obj[key] === 'Object' ? deepCopy(obj[key]) : obj[key];}return newObj;}Object.create(),需要传入对象原型
关于引用,来看题:
== 的 === 的区别
那么[1] == [1]的结果是什么
应该是false,不要被自己对于 == 和 === 的结论影响然后得出错误的结论。因为是两个不同的对象在比较。
关于==类型转换记住是优先转数字,然后就很好理解true==’true’结果为false了。

面试题:JS中判断是否为数组
Array.isArray(arr)
arr instanceof Array
arr.__proto__.constructor === Array
说到引用,不可不提c++中指针和引用的区别,ps:C语言中没有引用:
指针是一个实体,引用只是个别名
引用只能在定义时初始化一次,之后引用所指向的内容不变;指针可变。
引用不能为空,指针可以为空。
sizeof引用拿到的事所指向变量的大小,而sizeof指针拿到的是指针本身(所指向变量的地址)的大小。
