目录
JS 变量按照存储方式区分为哪些类型,并描述其特点,区分值类型与引用类型的区别
基础题目
JS 变量按照存储方式区分为哪些类型,并描述其特点,区分值类型与引用类型的区别
值类型:
引用类型:
可以看出, 值类型把每一个值都放在一个内存上,引用类型改变了存放值的指向,如图此时a和b同时指向了该内存位置。
引用类型包括:对象、数组、函数,引用类型是为了让数据共用内存空间,减少内存占用
JS 中使用 typeof 能得到哪些类型
总结:可以得到 typeof 可以得到的类型有以下6种
- undefined
- number
- string
- boolean
- object
- function
注意:1.typeof null 返回的值是object 2.typeof 可以细致的区分值类型,而引用类型却不能细分,例如:数组和对象返回的都是object。
强制类型转换,何时使用 === 何时 使用 ==
以下4种情况会发生强制类型转换
- 字符串拼接
var a = 10 + 10; //20
var b = 10 + '10'; //'1010'
- == 运算符
100 == '100' //true
0 == '' //true
null == undefined //true
使用 == 时候会发生强制类型转换。 第一行:将100 =》'100' 第二行:0 =》fasle ' ' =》fasle 第三行同第二行
- if 语句
var a = true
var b = 10
var c = ''
if(a){} //true
if(b){} //true
if(c){} //false
- 逻辑运算
10 && 0 //0
''|| 'abc' //'abc'
!window.abc //true
将一个常量转为true的方法。例:var a = 11; var b = !!a; 此时b的值就是true
何时使用 === 和 ==
当判断出现
if(obj.a == null){
//此时括号里的内容相当于 obj.a === null || obj.a === undefined
//其余情况均匀用 ===
}
jquery源码推荐
JS 中有哪些内置函数
- Object
- Array
- Boolean
- Number
- String
- Function
- Date
- RegExp
- Error
如何理解 JSON
是javascript 中的一个对象而已,可以看作是一种数据格式
常用JSON.stringify({a:10,b:20}) <=> JSON.parse('{"a" : 10 , "b": 20}') 进行字符串《=》对象转换
原型和原型链
涉及知识点
参考文章 : javascript王国的一次旅行
- 构造函数
在命名规则上,构造函数一般是首字母大写,普通函数则是遵照小驼峰式命名法。
函数调用时,构造函数内部会创建一个实例,调用普通函数时则不会创建新的对象,构造函数内部的this指向是新创建的函数实例,而普通函数内部的this指向调用函数的对象(如果没有对象调用,默认为window)
- 原型规则和实例
所有的引用类型(数组、对象、函数),都具有对象特性(即可以自由拓展属性)
所有的引用类型(数组、对象、函数),都有一个__proto__属性(可以称之为‘隐式原型’),属性值是一个普通的对象
所有的函数,都有一个prototype属性(可以称之为‘显式原型’),属性值也是一个普通的对象
所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数“ prototype ”属性值
当试图得到一个对象的属性时,如果这个对象没有这个属性,那么会去找它的__proto__(即它的构造函数 prototype )中寻找
- 原型链
在使用New方法初始化函数的时候(详细点击查看new的深度理解)得到的新对象的__proto__属性会指向函数对象的原型对象,而函数对象的原型对象又继承至原始对象。所以呈现以下结构
function fn(){}; var test = new fn();
把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。
原型链实际上就是js中数据继承的继承链
- instanceof
用于判断引用类型属于哪个构造函数的方法
如何准确判断一个变量是数组类型
var arr = [];
arr instanceof Array; // 正确答案,如果是数组,则会返回一个true
typeof arr; // 错误答案,不管这个arr是数组还是对象,都会返回 object
写一个原型链基础的例子
function Book () {
this.use = function () {
console.log('read');
}
}
function Mybook(){
this.name = function(){
console.log('css3权威指南');
}
}
Mybook.prototype = new Book(); //指定Boox的继承函数为Mybook
var css3 = new Mybook();
css3.use(); //返回 read 说明Mybook已经继承了Book的.use方法
js中的基础,可以用原型链来解释,因为原型链的存在,寻找函数总是通过__proto__属性来向上寻找,只要把这个属性更改为想要继承的函数,就可以继承该函数。
描述new一对象的过程
function Foo(name, age) {
this.name = name;
this.age = age;
this.calss = 'calss_1';
// return this; 这一行是自带的
}
var f = new Foo('Jack',12);
- 创建一个新对象
- this 指向这个新对象
- 执行代码,即对this赋值
- 返回 this
写一个封装DOM查询的例子
function Elem(id) {
this.elem = document.getElementById(id);
}
Elem.prototype.html = function(val) {
var elem = this.elem;
if(val) {
elem.innerHTML = val;
return this; //用来返回该对象进行链式操作
}
else {
return elem.innerHTML;
}
}
Elem.prototype.on = function(type,fn) {
var elem = this.elem;
elem.addEventListener(type,fn);
return this; //用来返回该对象进行链式操作
}
var div1 = new Elem('div1');
console.log(div1.html());
div1.on('click',function() {
alert(1);
})
作用域和闭包
相关知识点
- 执行上下文
第一种情况
console.log(a); //undefined
var a = 20;
这里我们先输出a,在定义a,输出值为undefined。
解释:js在运行时,先将a放到前面,此时赋值步骤是未执行得,所以a用undefined来代替,然后再输出,输出值则为undefined
第二种情况
fn('java')
function fn(name) {
age = 20;
console.log(name,age); //java 20
var age;
}
可以发现,先执行函数,再定义函数是可行的。在函数中,先赋值,再定义也是可行的。这种情况称之为执行上下文
每一个函数都有自己的执行上下文EC(执行环境 execution context),并且每个执行上下文中都有它自己的变量对象VO(Variable object),用于存储执行上下文中的变量 、函数声明 、函数参数,这解释了js如何找到我们定义的函数和变量。并且函数是js中唯一一个能创建出作用域的,注意:for,if()else()不能创建作用域。
- this
this要再执行时才能确认值,定义时无法确认
var a = {
name: "A",
fn:function () {
console.log(this.name);//如果这里内容错误,不执行得情况下,并不会报错,而执行时就会报错
}
}
//在这行之前,a处于定义状态,此时的this不能定值,只有执行时,才能确定
a.fn(); //此时this === a;
a.fn.call({name:"B"}); //此时this === {name:"B"}
var fn1 = a.fn;
fn1(); //此时的this === window
以下是4种执行情况
1、作为构造函数执行
2、作为对象属性执行
3、作为普通函数执行
4、call apply bind
function Foo(name) {
this.name = name;
}
var f = Foo("zhangsan"); //作为构造函数执行
var obj = {
name: 'name',
myName: function () {
console.log(this.name);
}
}
obj.myName(); //作为对象属性执行
function fn() {
console.log(this);
}
fn();
function fn1(name,age) {
console.log(name);
console.log(this);
}
fn1.call({x:100},"zhangsan",20); //这里的{x:100}成了该函数的this
- 作用域
javascript没有块级作用域(也就是for、if没有作用域的原因),它只有函数跟全局作用域
var a = 1;//这是全局
function fn() {
var a = 2; //这是函数作用域
}
- 作用域链
什么是自由变量
var a = 1;
function fn() {
var b = 2;
console.log(a); //此时在当前作用域内这个a没有被定义(此时它是一个全局变量),那么称这个a为自由变量
console.log(b);
}
fn();
var a = 1;
function f1() {
var b = 2;
function f2() {
var c = 3;
console.log(a); //a是自由变量
console.log(b); //b是自由变量
console.log(c);
}
f2();
}
f1();
上述代码中,可以看出,在f2中,a和b都是自由变量
而函数执行时,寻找变量是先从当前作用域(f2)找,没有的话向父级作用域(f1)中寻找,依然没有,继续向上寻找直到全局作用域,这样的过程我们称之为“作用域链”
- 闭包
一个重要的一点:函数的父级作用域是在定义时就决定了,而不是在执行的时决定的(从某些方面看来和this的机制相反)
function fn() {
var a = 1;
return function () {
console.log(a);
}
}
var f1 = fn();
var a = 2;
f1(); //输出 1
上端代码中,f1()执行是在全局执行的,他的父级作用域中重新给a赋值了2,但输出时,a仍然等于1
原因:在fn定义时,它的子函数的父级作用域定义了a=1,不管执行时函数在哪,此时的a永远等于1
闭包:在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁
闭包的使用场景
1、函数作为返回值
2、函数作为参数来传递
说一下对变量提升的理解
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
说明this几种不同的使用场景
1、作为构造函数执行
2、作为对象属性执行
3、作为普通函数执行
4、call apply bind
创建10个<a>标签,点击的时候弹出来对应的序号
第一张图的错误原因其实是因为js中的for不像java、c#那样有块级作用域,i是作为全局变量来使用的,所以第二张图加了一个闭包,把i限制在一个作用域里,这样alert出来的i就是正确的值
如何理解作用域
三大要点
自由变量
作用域链,即自由变量的查找过程
闭包的2个使用场景
实际开发中闭包的应用
在实际开发中,闭包主要是用来封装变量,收敛权限
function isFirstLoad(){
var list=[];
return function(option){
if(list.indexOf(option)>=0){ //检测是否存在于现有数组中,有则说明已存在
console.log('已存在')
}else{
list.push(option);
console.log('首次传入'); //没有则返回true,并把这次的数据录入进去
}
}
}
var ifl=isFirstLoad();
ifl("zhangsan");
ifl("lisi");
ifl("zhangsan");
可以看到,如果外界想访问_list变量,只能通过我定义的函数isFirstLoad来进行访问,我对想访问_list的外界只提供了isFirstLoad这一个接口。至于怎么操作_list,我已经定义好了,外界能做的就只是使用我的函数,然后传几个不同的参数罢了。