本文参照大神文章,也是自己复习准备面试的小笔记总结
本文主要内容:
1、js的数据类型
2、变量提升
3、作用域和作用域链
4、柯里化
5、深克隆、浅克隆
JS数据类型
js的数据类型分为基本数据类型和引用数据类型
基本数据类型:String、number、Boolean、undefined、null、Symbol(es6d的新类型)
引用数据类型:Object function Array Date RegExp
- 各个数据类型常用的方法有哪些?
- String:
split(切割,不改变原值)、substring(截取)、slice(截取,不改变原值)
- 数据类型判断
-
typeof方法可以检测number String Boolean undefined 这四种基本数据类型,还可以检测出object和function两种引用数据类型
-
null, String(null)
-
检测object的方法,Object.prototype.toString.call(obj).slice(8,-1).toLocaleLowerCase()
- 数据类型转换
-
-、*、/、%:一律转换成数值型再计算
-
+: 数字 + 字符串 = 字符串
var w = 1 + 'aaa' // "1aaa"
-
. 数字 + 对象
var w = 2 + {a: 1} // "2[object Object]"
数字 + Boolean/null
w = 1 + false
//1
w = 1 + true
//2
w = 1 + null
//1
数字 + undefined
w = 1 + undefined
//NaN
其他类型的数据转换
[1,2,3].toString()
//"1,2,3"
Object.prototype.toString.call({x:'a'})
//"[object Object]"
NaN !== NaN //true
变量提升
变量提升:提升变量和函数的定义,并没有提升变量的赋值。在提升变量定义的时候先给设置一个初始值undefined。
showName()
console.log(myname)
var myname = '变量'
function showName() { console.log('函数showName被执行');}
执行结果:
作用域/作用域链
在讲作用域概念之前,先了解一个概念,执行上下文环境(Execution Context)
- 执行上下文环境(Execution Context)
包含三部分:
- 变量对象:执行上下文的一部分,可以抽象为一种数据作用域,存储该执行上下文中的所有变量和函数声明(不包含函数表达式)。
活动对象(AO):当变量对象处于active EC的时候,称其为活动对象。 说到活动对象,又要讲一下js的词法执行顺序: 每个函数执行的瞬间,就会生成一个对象,即活动对象AO 1. 先分析参数: 函数先接收形参,将这些形参赋值给AO的属性,初始值都为undefined 然后接受实参,将实参赋值给属性; 2.分析变量的声明: 如果AO里边没有这个属性,则新增该属性;若存在,则不作任何处理; 3.分析函数 如果AO属性里边没有该属性,则新增属性;若存在,则被新值覆盖;
function t(age) {
console.log(age);//1
var age = 99;
console.log(age);//2
function age() {
}
console.log(age);//3
}
t(5);
结果:
1:function age(){}
2: 99
3: 99
分析:
这时候还涉及到一个变量提升的问题;在同一作用域,变量和函数优先声明,但是赋值不优先;
按照上边的过程一步步分析:
1. 在t函数被调用的瞬间会生成一个活动变量AO,分析形参,将age付给AO的一个属性,并且赋值为5;
2. 在函数内部分析变量,age变量和函数的声明都提升到函数的最上边,也就是1的上边;
当声明变量的时候,发现AO的属性里有age这个属性,不做任何操作;
然后声明函数,声明函数的时候,发现有该属性,则把函数赋值给该属性;
所以在1打印的时候AO.age为age函数
3.执行到age = 99的时候,又将AO.age重新赋值给99
所以2打印为99,2-3之间没有做任何改变值的操作,所以3处也为99;
2. 作用域链
当使用变量时,会在执行上下文访问到
3. this指向
类型:
-
全局执行上下文
-
函数执行上下文
-
eval执行上下文
代码执行过程:
-
创建全局上下文(global EC)
-
全局执行上下文(caller)逐行自上而下执行,遇到函数时,函数执行上下文被push到执行栈的顶层
-
函数执行上下文被激活,成为active EC,开始执行函数中的代码,caller被挂起
-
函数执行完,callee被pop移除出执行栈,控制权交换给全局上下文(caller),继续执行
- 作用域是什么?
含义:该上下文中声明的变量和函数声明的作用范围。
分类:块级作用域和函数作用域
这时候
var a = 1;
function b() {
console.log(a);
}
function c() {
var a = 3;
b();
}
c();
- 闭包
可以理解为:父函数被销毁的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。
闭包由于存在局部变量中一直不能被释放,所以对性能有负面影响,所以能不用闭包尽量不用。
function test(){
for(var i = 0;i < 5; i++) {
setTimeout(() => {
console.log(i);
},10)
}
}
test();
输出:5,5,5,5,5
因为setTimeout 是一个异步操作,当settimeout的回调被执行的时候,i已经为5了
为了解决这种问题,可以选择闭包
function test(){
for(var i = 0;i < 5; i++) {
(function(val){
setTimeout(() => {
console.log(i);
},10)
})(i);
}
}
test();
另外举一个闭包的例子
function count() {
var count = 0;
var add = function() {
count++;
console.log(count);
}
return add;
}
var add = count();
add();// 1
add();// 2
柯里化
简单来说,就是一个函数本来需要传多个参数,通过柯里化的实现,每次只需要传部分参数。
举例:
// say anyword to anyone
// 正常情况是
function Hello(word, name) {
console.log(name + word);
}
Hello('hi','andy');
柯里化之后的代码:
function Hello(word) {
return function(name) {
console.log(name + word);
}
}
var sayHi = Hello('hi');
sayHi('Jack');
深克隆,浅克隆
浅克隆和深克隆的区别就是,如果属性值为引用数据类型的时候,没有改变指针;
一个简单的深度克隆代码:
function deepClone(obj) {
let copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (let i = 0, len = obj.length; i < len; i++) {
copy[i] = deepClone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (let attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
一些读代码的题目
分析下边代码,写出执行结果:
var t = true;
window.setTimeout(function (){
t = false;
},1000);
while (t){}
alert('end');
while(true){} 为死循环,所以永远没有alert, 也不会执行setTimeout
var city = "Rome"+function() {
console.log(city);
var city = "Paris";
}();
//undefined 执行console.log的时候,只有city的生命,还没有赋值;所以打印结果为undefined 所谓变量的提升,是指变量声明的提升,而变量赋值并没有提升。
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()());
// The Window
4.当点击button#2的时候,console中会打印什么?
var nodes = document.getElementsByTagName('button');
// assume there is totally 5 nodes
for (var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', function() {
console.log('You clicked element #' + i);
});
}
结果为'you clicked element #5'。
怎样解决这种问题呢?
最简单的方式是将var 改成let生成块级作用域。
生成局部作用域的方式还有一种就是封装函数
var parentClassEle = document.getElementsByClassName('parent');
for(var i = 0; i < parentClassEle.length; i++) {
addClick(i);
}
function addClick(i) {
var j = i;
parentClassEle[i].addEventListener('click', function() {
console.log(i)
})
}
5.考察原型原型链
function Mammal(name) {
this.name = name;
this.offspring = [];
}
Mammal.prototype.haveABaby = function () {
var newBaby = new Mammal("Baby " + this.name);
this.offspring.push(newBaby);
return newBaby;
}
Mammal.prototype.toString = function () {
return '[Mammal "' + this.name + '"]';
}; // 到目前为止,这 是一个Mammal对象的实现。
// 将Employee的原型指向Person的一个实例
// 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。
Cat.prototype = new Mammal();
//修正constructor
Cat.prototype.constructor = Cat;
function Cat(name) {
this.name = name;
}
Cat.prototype.toString = function () {
return '[Cat "' + this.name + '"]';
} // 到目前为止,这是Mammal的一个子类Cat。
var someAnimal = new Mammal('Mr. Biggles');
var myPet = new Cat('Felix');
alert(someAnimal);
alert(myPet);
myPet.haveABaby();
alert(myPet.offspring.length);
alert(myPet.offspring[0]);
一些手写代码题
1.写一个方法,测试数组的每个元素是不是比2大?
function ArrayItemBig2(arr) {
arr.every(val => {
return val > 2;
})
}
得到一个新的数组,得到比2大的元素
function ArrayFilterBiger2(arr) {
var newArr = [];
arr.forEach(element => {
if(Number(element) > 2) {
newArr.push(element);
}
});
return newArr;
}
和对象相关的题目:
-
生成对象的方式有几种?
- 字面量 var obj = {}
- new的方式 var obj = new Object()
- 定义原型对象 var obj = Object.create({a: 1}) //obj.proto = {a: 1}
-
写一个构造函数
function Person(name) { this.name = name; }
Person.prototype = { getName: function() { console.log(thit.name) } }
-
什么是prototype?和__proto__有什么区别
prototype是构造函数的一个属性, 只有函数才有prototype属性; __proto__是对象的一个属性,每一个Object都有__proto__属性; 原型链是取决于__proto__的,而非prototype。
-
new一个对象都发生了哪些操作?
例: var child = new Person(); 1.先创建一个空对象child = {} 2.将child.proto = Person.prototype 3.将this指向改为child
-
constructor是什么?
constructor就是原型的构造函数
下边可以通过这个图深刻理解一下prototype、__proto__、 constructor、之间的关系
-
instanceof
例: child instanceof Person 为了检测构造函数的prototype属性是否出现在该对象的原型链中
作用域相关的问题:
作用域分为局部作用域和全局作用域。