JavaScript 基础知识
8.JavaScript 基础知识
一、基础
1、如何理解闭包?
-
1)定义: 闭包就是能够读取其他函数内部变量的函数。简单说来,可以理解把闭包理解为“定义在一个函数内部的函数”
function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999 //1)上述代码中的 f2 函数就是一个闭包。 //2)由于链式作用域的原因,f2 可以访问 f1内定义的变量,反之则不行 //3)把f2作为返回值,就可以在f1外部读取它的内部变量。
-
2)闭包的用途:
-
可以读取函数内部的变量。
-
让这些变量始终保存在内存中。
function f1() { var n = 999; nAdd = function () { n += 1 } function f2() { console.log(n) } return f2; } var result = f1(); result(); // 999 nAdd(); result(); // 1000 //原因:f1中的局部变量n 一直在内存中,并没有在f1调用后被自动清除。 //因为f1是f2的父函数,而f2被赋给了一个全局变量,导致f2始终在内存中,而f2的存在依赖f1,因此f1也始终在内存中,最终都不会再调用结束被垃圾回收机制回收。 //此处注意,nAdd也是一个闭包,它被挂载在全局变量下。
-
-
3)使用闭包注意点:
- 不能滥用闭包,会造成性能问题,IE中可能导致内存泄漏。解决办法是在退出函数之前,将不用的局部变量全部删除。
- 如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
2、this 的用法
-
定义:
this
是JavaScript 语言的一个关键字,它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。 -
简单来讲:this 就是函数运行时所在的环境对象,即谁调用函数就指向谁。
-
案例:
// 1)this 代表全局对象,因为test 调用时挂载在全局对象下,故this代表全局对象 var x = 1; function test() { console.log(this.x); } test(); //1 // 2)对象方法调用, 此时this指向 obj,因为调用时是obj 在调用 function test() { console.log(this.x); } var obj = {} obj.x = 1; obj.m = test; obj.m(); // 1 // 3)构造函数调用, new时生成一个新对象,此时this就指向新对象 var x = 2; function test() { this.x = 1; } var obj = new test();// this会指向新对象,不会指向全局对象 console.log(x); // 2
-
apply调用: (
apply
是函数的一个方法,作用是改变函数的调用对象)用法:apply(thisObj, [args]); // [args] 是一个数组或类数组,是一个参数列表 //示例如下: var x = 0; function test() { console.log(this.x); } var obj = {}; obj.x = 1; obj.m = test; //apply无参,则代表指向全局对象 obj.m.apply()// 0 //指定绑定this到obj上 obj.m.apply(obj) //1
-
call调用改变this
用法: call(thisObj, arg1, arg2, arg3, arg4); //示例代码: var name = "Jerry"; var obj = { name: "Tom" }; function sayName() { return this.name; } console.log(sayName.call(this));//Jerry console.log(sayName.call(obj));//Tom
3、比较typeof 与 instanceof
- 相同点:JS中
typeof
和instanceof
常用来判断一个变量是否为空,或者什么类型的
1)typeof 的用法:
-
定义和用法:typeof返回值是一个字符串,用来说明变量的数据类型。
var a = 1; typeof a // "number"
-
细节:
- typeof 一般只能返回:number boolean string function object undefined - 对于Array, null 等特殊对象,使用 typeof 一律返回 object,这正是它的局限性。
2)instanceof的用法:
-
定义:instanceof用于判断一个变量是否属于某个对象的实例。
var a = new Array(); console.log(a instanceof Array); //true console.log(a instanceof Object);// true,因为 Array 是 Object 的子类 function test() { } var a = new test(); console.log(a instanceof test); //true
4、JS 中面向对象中继承实现
1)使用prototype方式
//继承方式,原型链形式,将方法挂载在 prototype 对象上
function teacher(name) { this.name = name; }
teacher.prototype.sayName = function () { console.log("name is" + this.name) }
var teacher1 = new teacher("xiaoming");
teacher1.sayName();
//子类继承父类
function student(name) { this.name = name; }
student.prototype = new teacher();
var student1 = new student("xiaohong");
student1.sayName();
2)使用 call() / apply() 方式
function teacher(name, age) {
this.name = name;
this.age = age;
this.sayHi = function () { console.log('Name:' + name + ", Age:" + age) };
}
function student() {
var args = arguments;
teacher.call(this, args[0], args[1]);
}
var teacher1 = new teacher('xiaoming', 30);
teacher1.sayHi();
var student1 = new student('xiaolan', 10);
student1.sayHi();
3)混合方法(prototype, call / apply
)
function teacher(name, age) {
this.name = name;
this.age = age;
}
teacher.prototype.sayName = function () { onsole.log('name:' + this.name); }
teacher.prototype.sayAge = function () { console.log('age:' + this.age); }
function student() {
var args = arguments;
teacher.call(this, args[0], args[1]);
}
student.prototype = new teacher();
var student1 = new student('xiaolin', 23);
student1.sayName();
student1.sayAge();
5、JS 中的强制类型转换
(1)显式转换
var a = "42";
var b = Number( a );
a; // "42"
b; // 42 -- 是个数字!
(2)隐式转换
var a = "42";
var b = a * 1; // "42" 隐式转型成 42
a; // "42"
b; // 42 -- 是个数字!
6、JS 中的相等性
-
(1)严格比较 (如
===
)在不允许强制转型的情况下检查两个值是否相等 -
(2)抽象比较 (如
==
)在允许强制转型的情况下检查两个值是否相等var a = "42"; var b = 42; a == b; // true a === b; // false
7、"use strict"的作用是什么?
说明: use strict
出现在JS代码的顶部,可以帮助你写出更安全的JS代码。如果你错误的创建了全局变量,他会抛出错误的方式来警告你
-
例如:如下代码因为x 没有被定义,并使用了全局作用域中的某个值对其进行赋值,加了 use strict 后就会报错。
function doSomething(val) { "use strict"; x = val + 10; }
-
将变量声明到函数内部,严格模式下不会报错,如下:
function doSomething(val) { "use strict"; var x = val + 10; }
8、null 和 undefined
首先声明: 他们都是JS的两种类型。
-
尚未初始化:
undefined
-
空值:
null
-
注意:
null
和undefined
是两个不同的对象,如下:null == undefined // true null === undefined // false
9、delete 操作符
-
说明:delete 操作符,只适用于删除对象的属性。
var a = {b:1} delete a.b; console.log(a) //object {}
-
如果想要删除对象:
a = null;
-
delete 删除变量不可行
var name = 'lily'; delete name; console.log(name); //lily
-
delete 删除不了原型链中的变量
fun.prototype.age = 18; delete obj.age; console.log(obj.age) //18
10、JavaScript 创建对象的几种方法
-
对象字面量法:
var obj = {}
-
工厂模式:
function Person(name, age) { var o = new Object(); o.name = name; o.age = age; o.say = function () { console.log(name); } return o; }
-
构造函数法:
//1 直接new Object 对象 var obj = new Object(); //构造函数 function Person(name, age) { this.name = name; this.age = age; this.say = function () { console.log(name); } } var person = new Person('hello', 18);
-
原型模式:
//实现方法与属性共享,可以动态添加对象的属性和方法。但是没法创建实例自己的属性和方法,也没办法传递参数 function Person() { Person.prototype.name = 'jerry'; Person.prototype.say = function () { console.log(this.name) } Person.prototype.friends = ['friend1']; } var person = new Person();
-
构造函数和原型结合:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.say = function () { console.log(this.name) } var person = new Person('hello');
二、Web 性能相关
1、前端性能优化的方法
- (1)减少 http 请求次数:JS、CSS 源码压缩、图片大小控制合适、网页Gzip CDN 托管,data 缓存,图片服务器等。
- (2)前端JS+数据,减少由HTML标签导致的宽带浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数。
- (3)用innerHTML代替DOM操作,优化JavaScript性能。
- (4)当需要设置的样式很多时,设置className 而不是直接操作style。
- (5)少用全局变量、缓存DOM节点查找结果。减少IO读取操作。
- (6)避免使用CSS 表达式,又称 动态属性(Dynamic properties)
- (7)图片预加载,将样式放到顶部,将脚本放在底部,加上时间戳。
- (8)避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示要比 DIV+CSS布局要慢。
- (9)CSS Sprites:合并 CSS图片,减少请求数的又一个好办法。
- (10)LazyLoad Images 在页面刚加载的时候可以只加载第一屏,当用户继续往后滚屏的时候才加载后续图片。
- (11)减少cookie传输,请求带大cookie会严重影响数据传输,对于静态资源如CSS、Script 传输cookie没有意义。
网站优化的思路:
对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。向前端优化指的是,在不影响功能和体验的情况下,
能在浏览器执行的不要在服务端执行,能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,
本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。
减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。
程序优化永远要优化慢的部分,换语言是无法“优化”的。
2、垃圾回收机制方式及内存管理
-
1)定义和用法: 垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。
-
2)原理: 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存,但是这个过程不是实时的,因为开销比较大,所以GC会按固定时间间隔周期执行。
function fn1() { var obj = { name: "jerry", age: 10 }; } function fn2() { var obj = { name: "jerry", age: 10 }; return obj; } var a = fn1();// fn1 方法中的局部变量会被GC回收 var b = fn2();// fn2 中的局部变量不会被GC回收,因为 obj 被挂载在了全局变量上
-
3)垃圾回收策略: 标记清除(较为常用)和引用计数
-
标记清除:
定义和用法:当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。 某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。 到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
-
引用计数:
定义和用法:引用计数是跟踪记录每个值被引用的次数。 基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收的对象。
-
-
4)内存管理
1、什么时候触发垃圾回收? 垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。 IE6的垃圾回收是根据内存分配量运行的,当环境中的变量,对象,字符串达到一定数量时触发垃圾回收。垃圾回收器一直处于工作状态,严重影响浏览器性能。 IE7中,垃圾回收器会根据内存分配量与程序占用内存的比例进行动态调整,开始回收工作。 2、合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。 3、GC缺陷:(1)、停止响应其他操作; 4、GC优化策略:(1)、分代回收(Generation GC);(2)、增量GC
三、Web 安全
必考:什么是跨域? 跨域请求的方法有哪些?
1、什么是跨域?
-
定义: 由于浏览器同源策略,凡是发送请求的URL
协议
、域名
、端口
三者之间任意一与前页面地址不同则为跨域。 -
存在跨域的情况
- 网络协议不同,如http协议访问https协议 - 端口不同,如80端口访问8080端口 - 域名不同,如taobao.com 访问 baidu.com - 子域名不同,如 abc.baidu.com 访问 def.baidu.com - 域名和域名对应ip,如www.a.com 访问 10.0.0.8
2、跨域请求资源的方法
-
(1)proxy代理
定义和用法:proxy代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求结果传回前端。 实现方法:通过nginx 代理。 注意点:1、如果你代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。
-
(2)CORS 【Cross-Origin Resource Sharing】
-
定义和用法:是现代浏览器支持跨域资源请求的一种最常用的方式。
-
使用方法: 使用方法:一般需要后端人员在处理请求数据的时候,添加允许跨域的相关操作。如下:
res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8", "Access-Control-Allow-Origin":'http://localhost', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type' });
-
-
(3)jsonp
-
定义和用法:通过动态插入一个script标签。浏览器对script的资源引用没有同源限制,同时资源加载到页面后会立即执行。
-
特点:通过动态创建script来读取他域的动态资源,获取的数据一般为json格式。
<script> function testjsonp(data) { console.log(data.name); // 获取返回的结果 } </script> <script> var _script = document.createElement('script'); _script.type = "text/javascript"; _script.src = "http://localhost:8888/jsonp?callback=testjsonp"; document.head.appendChild(_script); </script>
-
缺点:1、这种方式无法发送post请求。2、另外要确定jsonp的请求是否失败并不容易,大多数框架实现都是结合超时时间来判定的。
-
四、练习题
1、JavaScript 当中,如果检测一个变量时一个String 类型?
typeof(obj) === "string"
typeof obj === "string"
obj.constructor === String
2、请写出3个使用this的典型应用
-
在HTML 元素事件属性上使用
<input type="button" onclick="showInfo(this);" value=”点击一下”/>
-
构造函数
function Animal(name, color) { this.name = name; this.color = color; }
-
apply() / call()求数组最值
var numbers = [5, 458 , 120 , -215 ]; var maxInNumbers = Math.max.apply(this, numbers); console.log(maxInNumbers); // 458 var maxInNumbers = Math.max.call(this,5, 458 , 120 , -215); console.log(maxInNumbers); // 458
3、请用JS 去除字符串中的空格
-
方法一:使用replace正则匹配的方法
去除所有空格: str = str.replace(/\s*/g,""); 去除两头空格: str = str.replace(/^\s*|\s*$/g,""); 去除左空格: str = str.replace( /^\s*/, ""); 去除右空格: str = str.replace(/(\s*$)/g, "");
-
方法二:使用str.trim()方法
var str = " xiao ming "; var str2 = str.trim(); // xiao ming,str.trim() 的局限性,无法去除中间的空格
4、如何获取浏览器URL中查询字符串中的参数
-
思路:使用
window.location.href
,通过split
方法根据&
进行分割获取。function showWindowHref() { var sHref = window.location.href; var args = sHref.split('?'); if (args[0] == sHref) { return ""; } var arr = args[1].split('&'); var obj = {}; for (var i = 0; i < arr.length; i++) { var arg = arr[i].split('='); obj[arg[0]] = arg[1]; } return obj; } var href = showWindowHref(); // obj console.log(href['name']); // xiaoming
5、解释事件冒泡以及如何阻止它?
- 定义:事件冒泡是指嵌套最深的元素触发一个事件,然后这个事件顺着嵌套顺序在父元素上触发。
- 如何防止:使用
event.cancelBubble
或event.stopPropagation()
(低于IE9)
6、如何检查一个数字是否为正数?
-
思路:对1进行取模,看是否有余数。
function isInit(num) { return num % 1 === 0; } console.log(isInit(4));//true console.log(isInit(12.2));//false console.log(isInit(0.3));//false
7、解释JS "undefined" 和 "not defined"之间的区别
- not defined: 如果你试图使用一个不存在且尚未声明的变量,JS将报错:“Uncaught ReferenceError: xxx is not defined”
- undefined表示变量未赋值
aaa // Uncaught ReferenceError: aaa is not defined
typeof aaa// "undefined"
- 当我们试图访问一个被声明但未被定义的变量时,会出现undefined错误
var x; // 声明
typeof x === 'undefined' // true
8、匿名函数和命名函数有什么区别?
//(1)赋给变量foo 的匿名函数
var foo = function () { }
//(2)赋给变量x 的命名函数
var x = function bar() { }
9、如何在JS中创建私有变量?
-
将变量创建成局部变量,就是这个函数被调用,也无法在函数之外访问这个变量
function func() { var priv = "secret code"; } console.log(priv)// 抛异常 Uncaught ReferenceError: priv is not defined
-
要访问私有变量,需要借助闭包函数
//要想访问这个私有变量,可以通过闭包的方式,将闭包函数返回 function func() { var priv = "secret code"; return function () { return priv; } } var getPriv = func(); getPriv();//"secret code"
10、以下代码输出什么?
0.1 + 0.2 === 0.3 //false
//结果为false,这是由浮点数内部表示导致的。0.1+0.2并不刚好等于0.3,实际结果为 0.30000000000000004
11、如何向Array 对象添加自定义方法,让下面的代码可以运行?
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg);
思路: 用原型 prototype
,由于JS的每个对象都链接到另一个对象(也就是对象的原型),并继承原型对象的方法,你可以跟踪每个对象的原型链,
直到到达没有原型的null对象,我们需要通过修改Array原型来向全局Array对象添加方法。
Array.prototype.average = function () {
//计算sum 的值
var sum = this.reduce(function (prev, cur) { return prev + cur; });
//将sum除以元素个数并返回
return sum / this.length;
}
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg);//3
12、什么是window对象?什么是document对象?
window 对象代表浏览器中打开的一个窗口。
document对象代表整个HTML文档。实际上,document对象是window对象的一个属性。
13、AMD(Modules/Asynchronous-Definition) 和 CMD (Common Module Definition)规范区别?
-
说明:
AMD
是RequireJS
在推广过程中对模块定义的规范化产出。CMD
是SeaJS
在推广过程中对模块定义的规范化产出。 -
区别:
-
对于依赖的模块,AMD 是提前执行,CMD是延迟执行。不过
RequireJS
从2.0
开始,也改成延迟执行。 -
CMD 推崇依赖就近,AMD 推崇依赖强制。
-
AMD 的 API 默认是一个当多个用,CMD 的API 严格区分,推崇职责单一。
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() }) // AMD 默认推荐 define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething(); // 此处略去 100 行 b.doSomething(); })
-
14、JS延迟加载方式有哪些?
- JS 的延迟加载,有助于提高页面的加载速度。
defer 和 async 动态创建DOM方式(用的最多),按需异步载入JS。
defer:延迟脚本。立即下载,但延迟执行(延迟到整个页面都接卸完毕后再运行),按照脚本的先后顺序执行。
async:异步脚本。下载完立即执行,但不能保证按脚本出现的先后顺序执行。
15、下面代码输出什么?
var y = 1;
if (function f(){}) {
y += typeof f;
}
console.log(y); // 1undefined
16、写一个 mul 函数,使用方法如下:
console.log(mul(2)(3)(4)); // output : 24
console.log(mul(4)(3)(4)); // output : 48
答案:
//使用嵌套闭包即可
function mul(x) {
return function (y) {
return function (x) {
return x * y * z;
}
}
}
17、JavaScript 怎么清空数组
var arrayList = ['a','b','c','d','e','f'];
//方法1:
arrayList = [];
//方法2:
arrayList.length = 0;
//方法3:
arrayList.splice(0, arrayList.length);
18、怎么判断一个object是否是数组(array)?
-
方法一:使用 Object.prototype.toString 来判断
function isArray(obj){ return Object.prototype.toString.call( obj ) === '[object Array]'; }
-
方法二:使用原型链
function isArray(obj){ return obj.__proto__ === Array.prototype; }
-
方法三:利用JQuery
function isArray(obj){ return $.isArray(obj) }
19、下面代码输出什么?
var output = (function (x) {
delete x;
return x;
})(0)
console.log(output);//输出是 0。 delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。
20、下面代码输出什么?
var Employee = { company: 'xyz' }
var emp1 = Object.create(Employee);
delete emp1.company;
console.log(emp1.company); // xyz
//原因是 这里的 emp1 通过 prototype 继承了 Employee 的company。emp1 并没有自己的 company属性,此处delete 无效。
21、什么是undefined x 1?
说明:当我们使用delete 删除一个数组中的元素,这个元素的位置就会变成一个占位符,打印出来就是 undefined x 1
var trees = ["redwood","bay","cedar","oak","maple"];
delete trees[3];
console.log(trees); // ["redwood", "bay", "cedar", empty, "maple"]
console.log(trees[3]); //undefined
22、下面代码输出什么?
var trees = ["xyz","xxxx","test","ryan","apple"];
delete trees[3];
console.log(trees.length);//5 delete 操作符删完数组元素后,并不影响数组长度
23、下面代码输出什么?
var bar = true;
console.log(bar + 0);//1
console.log(bar + "xyz");//truexyz
console.log(bar + true); //2
console.log(bar + false); //1
可以对照如下操作:
Number + Number -> 加法
Boolean + Number -> 加法
Boolean + Boolean -> 加法
Number + String -> 连接
String + Boolean -> 连接
String + String -> 连接
24、下面代码输出什么?
var z = 1, y = z = typeof y;
console.log(y);
上面代码等价于
var z = 1
z = typeof y;
var y = z;
console.log(y);// undefined
25、下面代码输出什么?
var foo = function bar(){ return 12; };
typeof bar(); //抛异常, Uncaught ReferenceError: bar is not defined
如下修改不会抛异常
var bar = function(){ return 12; };
typeof bar();
//或者
function bar(){ return 12; };
typeof bar();
26、如下两种函数声明有什么区别?
//foo的定义是在运行时,这种声明方式会有变量提升问题
var foo = function(){}
function bar(){}
如下:使用匿名方式声明,定义的变量会导致变量提升,所以上述代码执行顺序如下
// foo bar的定义位置被提升
function bar(){};
var foo;
console.log(foo)// undefined
foo = function(){};
27、如下代码输出什么?
var salary = "1000$";
(function () {
console.log("Original salary was " + salary);
var salary = "5000$";
console.log("My New Salary " + salary);
})();
// 答案:
//Original salary was undefined
// My New Salary 5000$
考察变量提升,上述函数中的 console.log("Original salary was " + salary);
这行代码,先使用了 salary 这个变量,函数内部会有变量提升问题,等价于如下:
var salary ;
console.log("Original salary was " + salary);
28、如果我使用JS的“关联数组”,我们怎么计算“关联数组”的长度?
var counterArray = {
A : 3,
B : 4
};
counterArray["C"] = 1;
//答案:直接计算key 的数量就可以了
Object.keys(counterArray).length // Output 3
29、JavaScript 中有哪些弹出框?
Alert、Confirm 、Prompt
30、Void(0)
怎么用?
Void(0)用于防止页面刷新,并在调用时传递参数“zero”。
Void(0)用于调用另一种方法而不刷新页面。
31、如何创建通用对象?
var I = new object();