JS面试题四

JS

  1. js的==基本==数据类型。

答:

1
Undefined、Null、Boolean、Number、String、Symbol(创建后独一无二且不可变的数据类型,ES6新增)

  1. js有哪些内置对象?

答:

1
2
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error

  1. JavaScript原型,原型链?(参考链接https://www.cnblogs.com/shuiyi/p/5305435.html)

答:

一. prototype和proto的区别

image

==prototype是函数才有的属性==

1
2
3
4
5
6
7
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object {}
var b = function(){}
console.log(b.prototype); //b {}
console.log(b.__proto__); //function() {}

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*1、字面量方式*/
var a = {};
console.log(a.__proto__); //Object {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*2、构造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}
console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外情况)

image

1
2
3
4
5
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
  1. 如何将字符串转化为数字,例如’12.3b’?

答:

1
parseFloat('12.3b');

  1. 如何将浮点数点左边的数每三位添加一个逗号(参考https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString)。

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
一.
var num=123456789;
num.toLocaleString(); // 123,546,879 前面最好加上parseFloat, parseFloat(num).toLocaleString();
var num=123456789;
num.toLocaleString('zh-Hans-CN-u-nu-hanidec', { useGrouping: false });
// "一二三四五六七八九" 因为 useGrouping 默认为 true, 所以分组。
二.
参考:http://www.tuicool.com/articles/ArQZfui
function commafy(num) {
return num && num
.toString()
.replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
return $1 + ",";
});
}
console.log(commafy(1234567.90)); //1,234,567.90

  1. 如何实现数组的随机排序?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
方法一:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort1(arr){
for(var i = 0,len = arr.length;i < len; i++ ){
var rand = parseInt(Math.random()*len);
var temp = arr[rand];
arr[rand] = arr[i];
arr[i] = temp;
}
return arr;
}
console.log(randSort1(arr));
方法二:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){
var mixedArray = [];
while(arr.length > 0){
var randomIndex = parseInt(Math.random()*arr.length);
mixedArray.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return mixedArray;
}
console.log(randSort2(arr));
方法三:
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random() - 0.5;
})
console.log(arr);

  1. Javascript如何实现继承?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1、构造继承
2、原型继承
3、实例继承
4、拷贝继承
原型prototype机制或apply和call方法去实现较简单,建议使用构造函数与原型混合方式。
function Parent(){
this.name = 'wang';
}
function Child(){
this.age = 28;
}
Child.prototype = new Parent();//继承了Parent,通过原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被继承的属性
  1. JavaScript继承的几种实现方式?

答:

参考阮一峰博客:构造函数的继承

参考阮一峰博客:非构造函数的继承

  1. javascript创建对象的几种方式?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
javascript创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON;但写法有很多种,也能混合使用。
1、对象字面量的方式
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
2、用function来模拟无参的构造函数
function Person(){}
var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Class
person.name="Mark";
person.age="25";
person.work=function(){
alert(person.name+" hello...");
}
person.work();
3、用function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)
function Pet(name,age,hobby){
this.name=name;//this作用域:当前对象
this.age=age;
this.hobby=hobby;
this.eat=function(){
alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
}
}
var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象
maidou.eat();//调用eat方法
4、用工厂方式来创建(内置对象)
var wcDog =new Object();
wcDog.name="旺财";
wcDog.age=3;
wcDog.work=function(){
alert("我是"+wcDog.name+",汪汪汪......");
}
wcDog.work();
5、用原型方式来创建
function Dog(){}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
alert(this.name+"是个吃货");
}
var wangcai =new Dog();
wangcai.eat();
6、用混合方式来创建
function Car(name,price){
this.name=name;
this.price=price;
}
Car.prototype.sell=function(){
alert("我是"+this.name+",我现在卖"+this.price+"万元");
}
var camry =new Car("凯美瑞",27);
camry.sell();

  1. null,undefined 的区别?(参考阮一峰博客:http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html)

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
null 表示一个对象是“没有值”的值,也就是值为“空”;
undefined 表示一个变量声明了没有初始化(赋值);
undefined不是一个有效的JSON,而null是;
undefined的类型(typeof)是undefined;
null的类型(typeof)是object;
Javascript将未赋值的变量默认值设为undefined;
Javascript从来不会将变量设为null。它是用来让程序员表明某个用var声明的变量时没有值的。
typeof undefined
//"undefined"
undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;
例如变量被声明了,但没有赋值时,就等于undefined
typeof null
//"object"
null : 是一个对象(空对象, 没有任何属性和方法);
例如作为函数的参数,表示该函数的参数不是对象;
注意:
在验证null时,一定要使用 === ,因为 == 无法分别 null 和 undefined
null == undefined // true
null === undefined // false

  1. 什么是闭包(closure),为什么要用它?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
闭包的特性:
1.函数内再嵌套函数
2.内部函数可以引用外层的参数和变量
3.参数和变量不会被垃圾回收机制回收
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() {
alert(num);
}
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert() //执行结果应该弹出的667
执行say667()后,say667()闭包内部变量会存在,而闭包内部函数的内部变量不会存在使得Javascript的垃圾回收机制GC不会收回say667()所占用的资源
因为say667()的内部函数的执行需要依赖say667()中的变量
这是对闭包作用的非常直白的描述

  1. javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么?

答:

1
2
3
4
5
6
7
8
use strict是一种 ECMAscript5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行
使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
默认支持的糟糕特性都会被禁用,比如不能用with,也不能在意外的情况下给全局变量赋值;
全局变量的显示声明,函数必须声明在顶层,不允许在非函数代码块内声明函数,arguments.callee也不允许使用;
消除代码运行的一些不安全之处,保证代码运行的安全,限制函数中的arguments修改,严格模式下的eval函数的行为和非严格模式的也不相同;
提高编译器效率,增加运行速度;
为未来新版本的Javascript标准化做铺垫。

  1. new操作符具体干了什么呢?

答:

1
2
3
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。

  1. Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

答:

1
2
3
4
5
6
7
8
9
10
11
hasOwnProperty
javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。
使用方法:
object.hasOwnProperty(proName)
其中参数object是必选项。一个对象的实例。
proName是必选项。一个属性名称的字符串值。
var demo = {pro: 1};
demo.hasOwnProperty('pro') // true

  1. JSON 的了解?

答:

1
2
3
4
5
6
7
8
9
10
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
如:{"age":"12", "name":"back"}
JSON对象转换为JSON字符串:
var obj = {"age":"12", "name":"back"};
var str = JSON.stringify(obj);
JSON字符串转换为JSON对象:
var obj2 = JSON.parse(str);

  1. Ajax 是什么? 如何创建一个Ajax?

答:

1
2
3
4
5
6
7
8
9
10
ajax的全称:Asynchronous Javascript And XML。
异步传输+js+xml。
所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
(3)设置响应HTTP请求状态变化的函数
(4)发送HTTP请求
(5)获取异步调用返回的数据
(6)使用JavaScript和DOM实现局部刷新

  1. Ajax 解决浏览器缓存问题?

答:

1
2
3
4
5
6
7
8
9
1、在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
2、在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
3、在URL后面加上一个随机数: "fresh=" + Math.random();。
4、在URL后面加上时间戳:"nowtime=" + new Date().getTime();。
5、如果是使用jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

  1. 跨域的几种解决方案

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
(1)document.domain + iframe
  (2)动态创建script
  (3)window.name + iframe
  (4)window.postMessage
  (5)CORS
  (6)JSONP
  (7)nginx代理(服务器代理)

  1. AMD(Asynchronous Modules Definition)、CMD(Common Module Definition)规范区别?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Asynchronous Module Definition,异步模块定义,所有的模块将被异步加载,模块加载不影响后面语句运行。所有依赖某些模块的语句均放置在回调函数中。
AMD CMD区别:
1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
2. CMD 推崇依赖就近,AMD 推崇依赖前置。
// 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()
// ...
})

  1. documen.write和 innerHTML的区别.

答:

1
2
3
document.write只能重绘整个页面
innerHTML可以重绘页面的一部分

  1. .call() 和 .apply() 的区别?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
1.apply和call的区别在哪里
2.什么情况下用apply,什么情况下用call
3.apply的其他巧妙用法(一般在什么情况下可以使用apply)
apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性.(改变this指向)
Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)
call:和apply的意思一样,只不过是参数列表不一样.
Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表
代码示例:
1. apply示例
/*定义一个人类*/
function Person(name,age) {
this.name=name;
this.age=age;
}
/*定义一个学生类*/
functionStudent(name,age,grade) {
Person.apply(this,arguments);
this.grade=grade;
}
//创建一个学生类
var student=new Student("qian",21,"一年级");
//测试
alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);
// 大家可以看到测试结果 name:qian age:21 grade:一年级
// 学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.
2. call示例
在Studen函数里面可以将apply中修改成如下:
Person.call(this,name,age);
3. 什么情况下用apply,什么情况下用call?
在给对象参数的情况下,如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用apply,如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));
4. apply的一些其他巧妙用法
细心的人可能已经察觉到,在我调用apply方法的时候,第一个参数是对象(this), 第二个参数是一个数组集合,在调用Person的时候,他需要的不是一个数组,但是为什么他给我一个数组我仍然可以将数组解析为一个一个的参数,
这个就是apply的一个巧妙的用处,可以将一个数组默认的转换为一个参数列表([param1,param2,param3] 转换为 param1,param2,param3) 这个如果让我们用程序来实现将数组的每一个项,来装换为参数的列表,可能都得费一会功夫,借助apply的这点特性,所以就有了以下高效率的方法:
a)Math.max 可以实现得到数组中最大的一项
因为Math.max 参数里面不支持Math.max([param1,param2]) 也就是数组
但是它支持Math.max(param1,param2,param3…),所以可以根据刚才apply的那个特点来解决 var max=Math.max.apply(null,array),这样轻易的可以得到一个数组中最大的一项
(apply会将一个数组装换为一个参数接一个参数的传递给方法)
这块在调用的时候第一个参数给了一个null,这个是因为没有对象去调用这个方法,我只需要用这个方法帮我运算,得到返回的结果就行,.所以直接传递了一个null过去
var a = [1,2,3,4,5,6,7,8,9,10];
var max = Math.max.apply(null,a);
同理: var min=Math.min.apply(null,array);
b)Array.prototype.push 可以实现两个数组合并
同样push方法没有提供push一个数组,但是它提供了push(param1,param,…paramN) 所以同样也可以通过apply来装换一下这个数组,即:
var arr1=new Array("1","2","3");
var arr2=new Array("4","5","6");
Array.prototype.push.apply(arr1,arr2);
arr1; // [1,2,3,4,5,6]
arr2; // [4,5,6]
也可以这样理解,arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合.
一般在目标函数只需要n个参数列表,而不接收一个数组的形式([param1[,param2[,…[,paramN]]]]),可以通过apply的方式巧妙地解决这个问题!

  1. jquery 中如何将数组转化为json字符串,然后再转化回来?

答:

1
2
3
4
5
6
7
8
9
10
jQuery中没有提供这个功能,所以你需要先编写两个jQuery的扩展:
$.fn.stringifyArray = function(array) {
return JSON.stringify(array)
}
$.fn.parseArray = function(array) {
return JSON.parse(array)
}
$("").stringifyArray(array); // 调用

  1. 如何判断当前脚本运行在浏览器还是node环境中?(阿里)

答:

1
2
3
this === window ? 'browser' : 'node';
通过判断Global对象是否为window,如果不为window,当前脚本没有运行在浏览器中

  1. 那些操作会造成内存泄漏?

答:

1
2
3
4
5
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。
setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)

  1. JQuery一个对象可以同时绑定多个事件,这是如何实现的?

答:

1
2
3
4
5
6
7
* 多个事件同一个函数:
$("div").on("click mouseover", function(){});
* 多个事件不同函数
$("div").on({
click: function(){},
mouseover: function(){}
});

  1. Webpack热更新实现原理?

答:

1
2
3
4
5
6
1. Webpack编译期,为需要热更新的 entry 注入热更新代码(EventSource通信)
2. 页面首次打开后,服务端与客户端通过 EventSource 建立通信渠道,把下一次的 hash 返回前端
3. 客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash
4. 修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
5. 客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
6. hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。

  1. Object.is() 与原来的比较操作符“ ===”、“ ==”的区别?

答:

1
2
3
4
5
6
7
两等号判等,会在比较时进行类型转换;
三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false);
Object.is 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,
但 Object.is(NaN, NaN) 会返回 true.
Object.is 应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。

  1. 什么叫优雅降级和渐进增强?

答:

1
2
3
4
5
优雅降级:Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会针对旧版本的IE进行降级处理了,使之在旧式浏览器上以某种形式降级体验却不至于完全不能用。
如:border-shadow
渐进增强:从被所有浏览器支持的基本功能开始,逐步地添加那些只有新版本浏览器才支持的功能,向页面增加不影响基础浏览器的额外样式和功能的。当浏览器支持时,它们会自动地呈现出来并发挥作用。
如:默认使用flash上传,但如果浏览器支持 HTML5 的文件上传功能,则使用HTML5实现更好的体验;

  1. WEB应用从服务器主动推送Data到客户端有那些方式?

答:

1
2
3
4
html5提供的Websocket
不可见的iframe
WebSocket通过Flash
XHR长时间连接

  1. 你有用过哪些前端性能优化的方法?
    答:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    (1) 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。
    (2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
    (3) 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
    (4) 当需要设置的样式很多时设置className而不是直接操作style。
    (5) 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。
    (6) 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
    (7) 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
    (8) 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。
    对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。向前端优化指的是,在不影响功能和体验的情况下,能在浏览器执行的不要在服务端执行,能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。程序优化永远要优化慢的部分,换语言是无法“优化”的。
  2. http状态码有那些?分别代表是什么意思?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。
400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁止访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
500 Internal Server Error 最常见的服务器端错误。
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。

  1. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?(流程说的越详细越好)

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注:这题胜在区分度高,知识点覆盖广,再不懂的人,也能答出几句,
而高手可以根据自己擅长的领域自由发挥,从URL规范、HTTP协议、DNS、CDN、数据库查询、
到浏览器流式解析、CSS规则构建、layout、paint、onload/domready、JS执行、JS API绑定等等;
详细版:
1、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
2、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
3、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
4、进行HTTP协议会话,客户端发送报头(请求报头);
5、进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等服务器;
6、进入部署好的后端应用,如 PHP、Java、JavaScript、Python 等,找到对应的请求处理;
7、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
8、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
9、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
10、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。
简洁版:
浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求;
服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等);
浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTML的DOM);
载入解析到的资源文件,渲染页面,完成。

  1. 第一次访问页面中时弹出引导,用户关闭引导,之后再次进入页面时不希望出现引导,如何实现?

答:

1
localStorage

JS面试题三

HTML

  1. 页面导入样式时,使用link和@import有什么区别?

答:

1
2
3
4
5
(1)link属于XHTML标签,除了加载CSS外,还能用于定义RSS, 定义rel连接属性等作用;而@import是CSS提供的,只能用于加载CSS;
(2)页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载;
(3)import是CSS2.1 提出的,只在IE5以上才能被识别,而link是XHTML标签,无兼容问题;

  1. 简述一下你对HTML语义化的理解?

答:

1
2
3
4
5
1. 用正确的标签做正确的事情。
2. html语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;
3. 即使在没有样式CSS情况下也以一种文档格式显示,并且是容易阅读的;
4. 搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO;
5. 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

  1. 浏览器是怎么对HTML5的离线储存资源进行管理和加载的呢?

答:

1
2
在线的情况下,浏览器发现html头部有manifest属性,它会请求manifest文件,如果是第一次访问app,那么浏览器就会根据manifest文件的内容下载相应的资源并且进行离线存储。如果已经访问过app并且资源已经离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的manifest文件与旧的manifest文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,那么就会重新下载文件中的资源并进行离线存储。
离线的情况下,浏览器就直接使用离线存储的资源。

  1. 请描述一下 cookies,sessionStorage 和 localStorage 的区别?

答:

1
2
3
4
5
6
7
8
9
10
11
12
cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
存储大小:
cookie数据大小不能超过4k。
sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
有期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除。
cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

  1. iframe有那些缺点?

答:

1
2
3
4
5
6
*iframe会阻塞主页面的Onload事件;
*搜索引擎的检索程序无法解读这种页面,不利于SEO;
*iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。
使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
动态给iframe添加src属性值,这样可以绕开以上两个问题。

  1. Label的作用是什么?是怎么用的?

答:

1
2
3
4
5
6
label标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。
<label for="Name">Number:</label>
<input type=“text“name="Name" id="Name"/>
<label>Date:<input type="text" name="B"/></label>

  1. 如何在页面上实现一个圆形的可点击区域?

答:

1
2
3
1、map + area 或者 svg
2、border-radius
3、纯js实现 需要求一个点在不在圆上简单算法、获取鼠标坐标等等

  1. 实现不使用 border 画出1px高的线,在不同浏览器的标准模式与怪异模式下都能保持一致的效果

答:

1
<div style="height:1px;overflow:hidden;background:red"></div>

CSS

  1. 介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?

答:

1
2
3
(1)有两种: IE 盒子模型、W3C 盒子模型;
(2)盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
(3)区 别: IE的content部分把 border 和 padding计算了进去;

  1. CSS选择符有哪些?哪些属性可以继承?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
1. id选择器( # myid)
2. 类选择器(.myclassname)
3. 标签选择器(div, h1, p)
4. 相邻选择器(h1 + p)
5. 子选择器(ul > li)
6. 后代选择器(li a)
7. 通配符选择器( * )
8. 属性选择器(a[rel = "external"])
9. 伪类选择器(a:hover, li:nth-child)
可继承的样式: font-size font-family color text-indent text-align line-height visibility cursor
不可继承的样式:border padding margin width height display vertical-align text-shadow background float overflow z-index

  1. 如何居中div?

答:

  • 水平居中:给div设置一个宽度,然后添加margin:0 auto属性

    1
    2
    3
    4
    div {
    width:200px;
    margin:0 auto;
    }
  • 让绝对定位的div居中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    div {
    position: absolute;
    width: 300px;
    height: 300px;
    margin: auto;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background-color: pink; /* 方便看效果 */
    }
  • 水平垂直居中(一)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    确定容器的宽高 宽500 高 300 的层
    设置层的外边距
    div {
    position: relative; /* 相对定位或绝对定位均可 */
    width:500px;
    height:300px;
    top: 50%;
    left: 50%;
    margin: -150px 0 0 -250px; /* 外边距为自身宽高的一半 */
    background-color: pink; /* 方便看效果 */
    }
  • 水平垂直居中(二)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    未知容器的宽高,利用 `transform` 属性
    div {
    position: absolute; /* 相对定位或绝对定位均可 */
    width:500px;
    height:300px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: pink; /* 方便看效果 */
    }
  • 水平垂直居中(三)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    利用 flex 布局
    实际使用时应考虑兼容性
    .container {
    display: flex;
    align-items: center; /* 垂直居中 */
    justify-content: center; /* 水平居中 */
    }
    .container div {
    width: 100px;
    height: 100px;
    background-color: pink; /* 方便看效果 */
    }
  1. position的值relative和absolute定位原点是?

答:

1
2
3
4
5
6
7
8
9
10
absolute
生成绝对定位的元素,相对于值不为 static的第一个父元素进行定位。
fixed (老IE不支持)
生成绝对定位的元素,相对于浏览器窗口进行定位。
relative
生成相对定位的元素,相对于其正常位置进行定位。
static
默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right z-index 声明)。
inherit
规定从父元素继承 position 属性的值。

  1. CSS3有哪些新特性?

答:

1
新增各种CSS选择器/圆角/阴影/文字特效/线性渐变/旋转/缩放/倾斜/动画等

  1. 用纯CSS创建一个三角形的原理是什么?

答:

1
2
3
4
5
6
7
8
把上、左、右三条边隐藏掉(颜色设为 transparent)
.demo {
width: 0;
height: 0;
border-width: 20px;
border-style: solid;
border-color: transparent transparent red transparent;
}

  1. 一个满屏’品’字布局 如何设计?

答:

1
2
3
4
5
简单的方式:
上面的div宽100%,
下面的两个div分别宽50%,
然后用float或者inline使其不换行即可

  1. 请解释一下为什么需要清除浮动?清除浮动的方式

答:

1
2
3
4
5
6
7
8
9
10
11
12
清除浮动是为了清除使用浮动元素产生的影响。浮动的元素,高度会塌陷,而高度的塌陷使我们页面后面的布局不能正常显示。
1. 父级div定义 height
缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题
2. 结尾处加空div标签 clear:both
缺点:不少初学者不理解原理;如果页面浮动布局多,就要增加很多空div,让人感觉很不好(增加无语义标签)
3. 父级div定义 伪类:after 和 zoom
.clearfloat:after{display:block;clear:both;content:"";visibility:hidden;height:0}
.clearfloat{zoom:1}
缺点: 代码多、不少初学者不理解原理,要两句代码结合使用才能让主流浏览器都支持
4. 父级div定义 overflow:hidden
原理:必须定义width或zoom:1,同时不能定义height,使用overflow:hidden时,浏览器会自动检查浮动区域的高度
缺点:不能和position配合使用,因为超出的尺寸的会被隐藏。
  1. 什么是外边距合并?

答:

1
2
外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。
合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。
  1. 如何修改chrome记住密码后自动填充表单的黄色背景 ?

答:

1
2
3
4
5
input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill {
background-color: rgb(250, 255, 189); /* #FAFFBD; */
background-image: none;
color: rgb(0, 0, 0);
}
  1. 如果需要手动写动画,你认为最小时间间隔是多久,为什么?(阿里)

答:

1
多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms

  1. display:inline-block 什么时候会显示间隙?(携程)

答:

1
移除空格、使用margin负值、使用font-size:0、letter-spacing、word-spacing

  1. 什么是CSS 预处理器 / 后处理器?

答:

1
2
3
4
5
- 预处理器例如:LESS、Sass、Stylus,用来预编译Sass或less,增强了css代码的复用性,
还有层级、mixin、变量、循环、函数等,具有很方便的UI组件模块化开发能力,极大的提高工作效率。
- 后处理器例如:PostCSS,通常被视为在完成的样式表中根据CSS规范处理CSS,让其更有效;目前最常做的
是给CSS属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。

JS面试题二

前端开发所需掌握知识点概要

  • HTML&CSS:
    对Web标准的理解(结构、表现、行为)、浏览器内核、渲染原理、兼容性、CSS语法、层次关系,常用属性、布局、选择器、权重、盒模型、
    Hack、CSS预处理器、CSS3、Flexbox、CSS Modules、Document flow、BFC、HTML5(离线 & 存储、Histoy,多媒体、WebGL\SVG\Canvas);
  • JavaScript:
    数据类型、运算、对象、Function、继承、闭包、作用域、事件、Prototype、RegExp、JSON、Ajax、DOM、BOM、
    内存泄漏、跨域、异步请求、模板引擎、模块化、Flux、同构、算法、ECMAScript6、Nodejs、HTTP、

  • 其他:
    主流MVVM框架(React\Vue\Angular)、Hybrid App\React Native\Weex、TypeScript、RESTFul、WEB安全、前端工程化、依赖管理、性能优化、
    重构、团队协作、可维护、易用性、SEO、UED、前端技术选型、快速学习能力等;


1. 如何添加、移除、移动、复制、创建和查找节点等。

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
document.createElement("div"); // 创建新元素
document.createTextNode("这是新文本"); // 创建新文本
appendChild(); // 添加
removeChild(); // 移除
replaceChild(); // 替换
insertBefore(); // 之前插入
insertAfter(); // 之后插入
cloneNode(); // 复制
document.getElementsByTagName(""); // 通过标签名称查找
document.getElementsByName(""); // 通过元素的Name属性的值查找
document.getElementById(""); // 通过元素Id,唯一性查找
document.getElementsByClassName(""); // 通过类查找
document.querySelector(""); // h5 新方法


2. 如何使用事件?以及IE和标准DOM事件模型之间存在的差别?

答:

  • 关于事件流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    1.1 IE的事件流:事件冒泡
    由事件的目标(event.srcElement)接受事件,然后逐级向上(例:下一个为包含event.srcElement的节点传递事件,直到文档节点document;
    1.2 其他浏览器支持的另一种事件流:事件捕获
    与事件冒泡正好相反,文档节点document先监听到事件,然后把事件逐级向下传递事件,直到目标节点event.target;
    1.3 DOM事件流
    DOM2级事件规范的事件流综合了以上两种,把事件流分为了以下三个阶段:
    1.3.1 事件捕获阶段
    不涉及事件目标,或者说这个阶段在目标之前就结束了;
    1.3.2 处于目标阶段
    被看作冒泡阶段的一部分,所以可以在冒泡阶段在目标事件上操作事件;
    1.3.3 事件冒泡阶段
    1.4 规范和浏览器实现的差别
    DOM2级事件规范的捕获阶段,事件从文档节点document开始传播,现代浏览器大多数都是从window对象开始传播事件的;
    DOM2级事件规范捕获阶段不涉及事件目标,现代浏览器大多数都在这个阶段包含事件目标。
  • 如何使用事件?以下是几种用来响应事件的事件处理程序
    HTML事件处理程序|DOM0级事件处理程序|DOM2级事件处理程序|IE的事件处理程序(IE11以下)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    2.1 HTML事件处理程序
    2.1.1 指定事件:<button type="button" onclick="alert('响应事件的处javascript代码,可以用全局的函数')">Click Me!</button>
    2.1.2 优缺点:简单,但是与HTML代码紧密耦合,更改不方便;
    2.1.3 删除事件:同DOM0的删除事件处理方式;
    2.2 DOM0级事件处理程序
    2.2.1 指定事件:document.onclick = function(){alert("document has been clicked")};
    2.2.2 删除事件:document.onclick = null;
    2.2.3 优缺点:简单且跨浏览器
    2.2.4 实质: 为元素指定方法(栗子中为document指定onclick方法),移除方法,所以其处理程序是在元素的作用域运行的;
    2.3 DOM2级事件处理程序
    2.3.1 指定事件:addEventListener("引号括起来的事件名", 触发事件后调用的事件处理程序, 是否在捕获节点调用时间处理程序的布尔值)
    栗子:var funA = function(){alert(" DOM2级事件处理程序")};
    document.addEventListener("click", funA, false);
    2.3.2 删除事件: document.removeEventListener("click", funA, false);
    如果指定的处理程序是匿名函数则不能删除,因为没有函数名;
    2.3.3 优缺点: 可以添加多个监听事件,缺点见2.3.2;
    2.4 IE的事件处理程序(IE11以下,IE11及Edge用的DOM2级事件处理程序)
    2.4.1 指定事件: attachEvent("onclick", function(){alert("is no longer supported in ie11")});
    2.4.2 删除事件: detachEvent("onclick", funA);
    如果指定的处理程序是匿名函数则不能删除,因为没有函数名;
    2.4.3 优缺点:可以添加多个监听事件,缺点见2.4.2;
  • IE和标准DOM事件模型之间存在的差别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    3.1 这里的IE是IE11以下;
    3.2 参数的差别: attachEvent()的第一个参数比addEventListener()的事件名多一个"on",
    且没有第三个参数,因为IE事件模型只支持冒泡事件流;
    3.3 事件处理函数作用域的区别: IE中事件处理程序处于全局作用域,其内的this会指向window;
    而用DOM(0或2)级事件的事件处理程序的作用域是元素作用域,其内的this指向其所属的元素
    例: document.addEventListener("click", function(){
    if(this == document){
    alert("此时this指向document");
    }
    }, false);
    3.4 事件对象event的属性方法的差别
    IE DOM
    cancelBubble = true stopPropagation() //停止冒泡
    returnValue = false preventDefault() //阻止元素默认事件
    srcEelement target //事件目标

3. XMLHttpRequest —— 这是什么、怎样完整地执行一次GET请求、怎样检测错误。

答:

  • 关于XMLHttpRequest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    1.1 作用: 提供了网页加载后在后台与服务器通信的方法;实现AJAX通信;
    1.2 创建: var xhr = new XMLHttpRequest();//不适用于IE7之前的版本,其他低版本IE可通过 ActiveXObject构造;
    1.3 对象属性: readyState,可取的值见图-1;
    1.4 对象事件: readyState的值的改变会触发readyStatechange事件;错误会触发error事件;
    1.3 优缺点: 在不重新加载页面的情况下更新网页;
    1.4 XMLHttpRequest 2级:
    FromData对象:用来序列化表单或者创建与表单格式相同的数据;其实例可直接传给seng()方法使用;
    栗子: var form1 = document.forms[0];
    xhr.send(new FormData(form1));
  • 一次GET请求分析,并检测执行过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    2.1 异步GET
    var xhr = new XMLHttpRequest();
    xhr.onerror = function(){alert("我出错啦")};
    xhr.onreadystatechange = function(){
    switch(xhr.readyState){
    case 0 : alert("未初始化,及还未调用open方法");
    break;
    case 1 : alert("启动,未调用send方法");
    break;
    case 2 : alert("发送,未收到响应");
    break;
    case 3 : alert("接受,取得部分数据");
    break;
    case 4 : if((xhr.status >=200 && xhr.status < 300) || xhr.status ==304){
    alert(xhr.responceText);
    };
    break;
    default : alert("are you kidding?");
    }
    };
    xhr.open("get", "url", true)//启动一个请求,未发送
    xhr.send(null);//发送请求. 如果该请求是异步模式(默认),该方法会立刻返回. 相反,如果请求是同步模式,则直到请求的响应完全接受以后,该方法才会返回

4. 盒模型 —— 外边距、内边距和边框之间的关系,及IE8以下版本的浏览器中的盒模型

  • W3C盒模型

image

  • IE8以下版本的浏览器中的盒模型

image


5. 行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

答:

1
2
3
4
5
(1)CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,比如div
默认display属性值为“block”,成为“块级”元素;span默认display属性值为“inline”,是“行内”元素。
(2)行内元素有:a b span img input select strong(强调的语气) 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4…p
(3)知名的空元素:<br><hr><img><input><link><meta>
鲜为人知的是:<area><base><col><command><embed><keygen><param><source><track><wbr>


6. 浮动元素 —— 怎么使用它们、它们有什么问题以及怎么解决这些问题。

答:

1
2
3
4
5
6
7
8
9
需要浮动的元素可使用CSS中float属性来定义元素的浮动位置,left:往左浮动,right:往右浮动
浮动元素引起的问题:
(1)父元素的高度无法被撑开,影响与父元素同级的元素
(2)与浮动元素同级的非浮动元素会跟随其后
(3)若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构
解决方法:
使用CSS中的clear:both;属性来清除元素的浮动可解决2、3问题,对于问题1,添加如下样式,给父元素添加clearfix样式:
.clearfix:after{content: ".";display: block;height: 0;clear: both;visibility: hidden;}
.clearfix{display: inline-block;}


7. HTML与XHTML——二者有什么区别,你觉得应该使用哪一个并说出理由。

答:

1
2
3
4
5
6
7
8
9
10
XHTML 元素必须被正确地嵌套
XHTML 元素必须被关闭,空标签也必须被关闭,如 <br> 必须写成 <br />
XHTML 标签名必须用小写字母
XHTML 文档必须拥有根元素
XHTML 文档要求给所有属性赋一个值
XHTML 要求所有的属性必须用引号""括起来
XHTML 文档需要把所有 < 、>、& 等特殊符号用编码表示
XHTML 文档不要在注释内容中使“--”
XHTML 图片必须有说明文字
XHTML 文档中用id属性代替name属性


8. JSON —— 作用、用途、设计结构

答:

1
2
3
4
5
6
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。
JSON建构于两种结构:
“名称/值”对的集合(A collection of name/value
pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表
(hash table),有键列表(keyed list),或者关联数组 (associative array)。
值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

非原创,参考博客园、简书等。

JS面试题一

1. 重复输出一个给定的字符串

重复输出一个给定的字符串( str 第一个参数)n 次 ( num 第二个参数),如果第二个参数 num 小于0的时候,返回空字符串。

答:三种方法

1.使用循环 2.使用递归 3.ES6 repeat方法

方法1:通过while循环重复输出一个字符串

1
2
3
4
5
6
7
8
9
function repeatStringNumTimes(str, num) {
var repeatedStr = "";
while (num > 0) {
repeatedStr += str;
num--;
}
return repeatedStr;
}
repeatStringNumTimes("abc", 3);

同理for循环也一样

1
2
3
4
5
6
7
8
function repeatStringNumTimes(str, num) {
var repeatedStr = "";
for(var i = 0; i < num ;i++) {
repeatedStr += str;
}
return repeatedStr;
}
repeatStringNumTimes("abc", 3)

方法2:通过条件判断和递归重复输出一个字符串

1
2
3
4
5
6
7
8
9
10
11
function repeatStringNumTimes(str, num) {
if(num < 0) {
return "";
}
if(num == 1) {
return str;
} else {
return str + repeatStringNumTimes(str, num - 1);
}
}
repeatStringNumTimes("abc", 3);

方法3:使用ES6 repeat() 方法重复输出一个字符串

1
2
3
4
5
6
7
8
function repeatStringNumTimes(str, num) {
if (num > 0) {
return str.repeat(num);
} else {
return "";
}
}
repeatStringNumTimes("abc", 3);

1
2
3
4
5
// 上面👆转成三元表达式
function repeatStringNumTimes(str, num) {
num > 0 ? str.repeat(num) : "";
}
repeatStringNumTimes("abc", 3);

2. JS查找字符串中出现次数最多的字符

JS查找字符串中出现次数最多的字符

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var str = "jialuchunzheshiceshi";
var o = {};
for (var i = 0, i < str.length; i++) {
var char = str.charAt(i);
if (o[char]) {
o[char]++; // 次数加1
} else {
o[char] = 1; // 若第一次出现,次数记为1
}
}
console.log(o); // 输出的是完整的对象,记录着每一个字符及其出现的次数
// 遍历对象,找到出现次数最多的字符的次数
var max = 0;
for (var key in o) {
if (max < o[key]) {
max = o[key]; // max始终储存次数最大的那个
}
}
for (var key in o) {
if (o[key] == max) {
// console.log(key);
console.log("最多的字符是" + key);
console.log("出现的次数是" + max);
}
}

JS易错知识点

变量作用域

1
2
3
4
5
6
var a = 1;
function test() {
var a = 2;
console.log(a); // 2
}
test();
1
2
3
4
5
6
var a = 1;
function test() {
console.log(a); // undefined
var a = 2;
}
test();
1
2
3
4
5
6
var a = 1;
function test() {
console.log(a); // 1
a = 2;
}
test();
1
2
3
4
5
6
let b = 1;
function test() {
console.log(b); // b is not defined
let b = 2;
}
test();
1
2
3
4
5
6
7
8
function test() {
let a = 1;
{
let a = 2;
}
console.log(a); // 1
}
test();

类型比较

1
2
3
var arr = [],
arr2 = [1];
console.log(arr === arr2); // false
1
2
3
var arr = [],
arr2 = [];
console.log(arr === arr2); // false
1
2
3
var arr = [],
arr2 = {};
console.log(typeof(arr) === typeof(arr2)); // true
1
2
3
var arr = [];
console.log(arr instanceof Object); // true
console.log(arr instanceof Array); // true

this指向

1
2
3
4
5
6
7
var obj = {
name: 'xiaoming',
getName: function () {
return this.name
}
};
console.log(obj.getName()); // 'xiaoming'
1
2
3
4
5
6
7
8
var obj = {
myName: 'xiaoming',
getName: function () {
return this.myName
}
};
var nameFn = obj.getName;
console.log(nameFn()); // undefined
1
2
3
4
5
6
7
8
9
10
11
var obj = {
myName: 'xiaoming',
getName: function () {
return this.myName
}
};
var obj2 = {
myName: 'xiaohua'
};
var nameFn = obj.getName;
console.log(nameFn.apply(obj2)); // 'xiaohua'

函数参数

1
2
3
4
5
function test6() {
console.log(Array.prototype.slice.call(arguments)); // [1, 2]
}
test6(1, 2);
// 上方利用函数中的arguments类数组对象获取传入函数的参数数组,所以输出数组[1, 2]。
1
2
3
4
5
6
7
function test7 () {
return function () {
console.log(Array.prototype.slice.call(arguments)); // 未执行到此,无输出
}
}
test7(1, 2);
// 上方同样利用arguments获取参数,但因test7(1, 2)未执行return中的函数,所以无输出。若执行test7(1, 2)(3, 4)则会输出[3, 4]。
1
2
3
4
5
6
7
var args = [1, 2];
function test9() {
console.log(Array.prototype.slice.call(arguments)); // [1, 2, 3, 4]
}
Array.prototype.push.call(args, 3, 4);
test9(...args);
// 上方利用Array.prototype.push.call()方法向args数组中插入了3和4,并利用ES6延展操作符(...)将数组展开并传入test9,所以console为[1, 2, 3, 4]。

闭包问题

1
2
3
4
5
6
var elem = document.getElementsByTagName('div'); // 如果页面上有5个div
for(var i = 0; i < elem.length; i++) {
elem[i].onclick = function () {
alert(i); // 总是5
};
}
1
2
3
4
5
6
7
8
9
10
var elem = document.getElementsByTagName('div'); // 如果页面上有5个div
for(var i = 0; i < elem.length; i++) {
(function (w) {
elem[w].onclick = function () {
alert(w); // 依次为0,1,2,3,4
};
})(i);
}
// 在绑定点击事件外部封装一个立即执行函数,并将i传入该函数即可。
// 另外一种方法 var 换成 let

对象拷贝与赋值

1
2
3
4
5
6
7
8
var obj = {
name: 'xiaoming',
age: 23
};
var newObj = obj;
newObj.name = 'xiaohua';
console.log(obj.name); // 'xiaohua'
console.log(newObj.name); // 'xiaohua'
1
2
3
4
5
6
7
8
9
var obj2 = {
name: 'xiaoming',
age: 23
};
var newObj2 = Object.assign({}, obj2, {color: 'blue'});
newObj2.name = 'xiaohua';
console.log(obj2.name); // 'xiaoming'
console.log(newObj2.name); // 'xiaohua'
console.log(newObj2.color); // 'blue'
1
2
3
4
5
6
7
8
var obj3 = {
name: 'xiaoming',
age: 23
};
var newObj3 = Object.create(obj3);
newObj3.name = 'xiaohua';
console.log(obj3.name); // 'xiaoming'
console.log(newObj3.name); // 'xiaohua'

参考劳卜文章

BOM

BOM

  • 理解 window 对象——BOM 的核心
  • 控制窗口、框架和弹出窗口
  • 利用 location 对象中的页面信息
  • 使用 navigator 对象了解浏览器

    window对象

    BOM 的核心对象是window,它表示浏览器的一个实例。在浏览器中,window 对象有双重角色, 它既是通过 JavaScript 访问浏览器窗口的一个接口,又是 ECMAScript 规定的 Global 对象。这意味着在网页中定义的任何一个对象、变量和函数,都以 window 作为其 Global 对象,因此有权访问 parseInt()等方法。

    location 对象

    location 对象是很特别的一个对象,因为它既是 window 对象的属性,也是 document 对象的属性;换句话说,window.location 和 document.location 引用的是同一个对象。location对象的用处不只表现在它保存着当前文档的信息,还表现在它将 URL解析为独立的片段,让开发人员可以通过不同的属性访问这些片段。
属 性 名 例 子 说 明
hash “#contents” 返回URL中的hash(#号后跟零或多个字符),如果URL 中不包含散列,则返回空字符串
host “www.wrox.com:80” 返回服务器名称和端口号(如果有)
hostname “www.wrox.com” 返回不带端口的服务器名称
href http://www.wrox.com 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值
pathname “/WileyCDA/“ 返回URL中的目录和(或)文件名
port “8080” 返回URL中指定的端口号。如果URL中不包含端口号,则 这个属性返回空字符串
protocol “http:” 返回页面使用的协议。通常是http:或https:
search “?q=javascript” 返回URL的查询字符串。这个字符串以问号开头

查询字符串参数

可以像下面这样创建一个函数,用以解析查询字符串,然后返回包含所有参数的一个对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getQueryStringArgs(){
//取得查询字符串并去掉开头的问号
var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
//保存数据的对象
args = {},
//取得每一项
items = qs.length ? qs.split("&") : [],
item = null,
name = null,
value = null,
//在 for 循环中使用
i = 0,
len = items.length;
//逐个将每一项添加到 args 对象中
for (i=0; i < len; i++){
item = items[i].split("=");
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if (name.length) {
args[name] = value;
}
}
return args;
}

1
2
3
4
// 假设查询字符串是?q=javascript&num=10
var args = getQueryStringArgs();
alert(args["q"]); // "javascript"
alert(args["num"]); // "10"

位置操作

使用 replace()方法。这个方法只接受一个参数,即要导航到的 URL;结果虽然会导致浏览器位置改变,但不会在历史记录中生成新记录。在调用 replace()方法之后,用户不能回到前一个页面。

与位置有关的最后一个方法是 reload(),作用是重新加载当前显示的页面。如果调用 reload() 时不传递任何参数,页面就会以最有效的方式重新加载。也就是说,如果页面自上次请求以来并没有改 变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,则需要像下面这样为该方法 传递参数 true。

1
2
location.reload(); //重新加载(有可能从缓存中加载)
location.reload(true); //重新加载(从服务器重新加载)

navigator 对象,已经成为识别客户端浏览器的事实标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function getBrowserInfo(){
var agent = navigator.userAgent.toLowerCase() ;
var regStr_ie = /msie [\d.]+;/gi ;
var regStr_ff = /firefox\/[\d.]+/gi
var regStr_chrome = /chrome\/[\d.]+/gi ;
var regStr_saf = /safari\/[\d.]+/gi ;
//IE
if(agent.indexOf("msie") > 0){
return agent.match(regStr_ie) ;
}
//firefox
if(agent.indexOf("firefox") > 0){
return agent.match(regStr_ff) ;
}
//Chrome
if(agent.indexOf("chrome") > 0){
return agent.match(regStr_chrome) ;
}
//Safari
if(agent.indexOf("safari") > 0 && agent.indexOf("chrome") < 0){
return agent.match(regStr_saf) ;
}
}
// 然后获取版本号
var browser = getBrowserInfo() ;
//alert(browser);
var verinfo = (browser+"").replace(/[^0-9.]/ig,"");
// 获取浏览器
var browserName = (browser+"").replace(/[^a-z]/ig,"");

screen对象(略)

JavaScript 中有几个对象在编程中用处不大,而 screen 对象就是其中之一。screen 对象基本上只 用来表明客户端的能力,其中包括浏览器窗口外部的显示器的信息,如像素宽度和高度等。每个浏览器 中的 screen 对象都包含着各不相同的属性。

history对象

history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。因为 history 是 window 对象的属性,因此每个浏览器窗口、每个标签页乃至每个框架,都有自己的 history 对象与特定的 window 对象关联。出于安全方面的考虑,开发人员无法得知用户浏览过的 URL。不过,借由用户访问 过的页面列表,同样可以在不知道实际 URL 的情况下实现后退和前进。

使用 go()方法可以在用户的历史记录中任意跳转,可以向后也可以向前。这个方法接受一个参数, 表示向后或向前跳转的页面数的一个整数值。负数表示向后跳转(类似于单击浏览器的“后退”按钮), 正数表示向前跳转(类似于单击浏览器的“前进”按钮)。

1
2
3
//后退一页 history.go(-1);
//前进一页 history.go(1);
//前进两页 history.go(2);

小结

浏览器对象模型(BOM)以 window 对象为依托,表示浏览器窗口以及页面可见区域。同时,window 对象还是 ECMAScript 中的 Global 对象,因而所有全局变量和函数都是它的属性,且所有原生的构造 函数及其他函数也都存在于它的命名空间下.

  • 在使用框架时,每个框架都有自己的 window 对象以及所有原生构造函数及其他函数的副本。 每个框架都保存在 frames 集合中,可以通过位置或通过名称来访问。
  • 有一些窗口指针,可以用来引用其他框架,包括父框架。
  • top 对象始终指向最外围的框架,也就是整个浏览器窗口。
  • parent 对象表示包含当前框架的框架,而 self 对象则回指 window。
  • 使用 location 对象可以通过编程方式来访问浏览器的导航系统。设置相应的属性,可以逐段
    或整体性地修改浏览器的 URL。
  • 调用 replace()方法可以导航到一个新 URL,同时该 URL 会替换浏览器历史记录中当前显示
    的页面。
  • navigator 对象提供了与浏览器有关的信息。到底提供哪些信息,很大程度上取决于用户的浏
    览器;不过,也有一些公共的属性(如 userAgent)存在于所有浏览器中。

js 函数表达式

函数表达式

  • 函数表达式的特征
  • 使用函数实现递归
  • 使用闭包定义私有变量

定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明:

1
2
3
function functionName(arg0, arg1, arg2) {
//函数体
}

Firefox、Safari、Chrome 和 Opera 都给函数定义了一个非标准的 name 属性,通过这个属性可以访问到给函数指定的名字。这个属性的值永远等于跟在 function 关键字后面的标识符。
//只在 Firefox、Safari、Chrome 和 Opera 有效

1
alert(functionName.name); //"functionName"

==函数声明的一个重要特征就是函数声明提升。==

函数表达式:

1
2
3
var functionName = function(arg0, arg1, arg2){
//函数体
};

递归

递归函数是在一个函数通过名字调用自身的情况下构成的:

1
2
3
4
5
6
7
function factorial(num){
if (num <= 1){
return 1;
} else { 6
return num * factorial(num-1);
}
}

这是一个经典的递归阶乘函数。虽然这个函数表面看来没什么问题,但下面的代码却可能导致它出错。

1
2
3
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

以上代码先把factorial()函数保存在变量anotherFactorial 中,然后将 factorial 变量设置为 null,结果指向原始函数的引用只剩下一个。但在接下来调用 anotherFactorial()时,由于必 须执行 factorial(),而 factorial 已经不再是函数,所以就会导致错误。在这种情况下,使用 arguments.callee 可以解决这个问题。
arguments.callee 是一个指向正在执行的函数的指针

1
2
3
4
5
6
7
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}

在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。
但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果。

1
2
3
4
5
6
7
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
});

闭包

闭包与变量

1
2
3
4
5
6
7
8
9
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数 返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中 都保存着 createFunctions()函数的活动对象,所以它们引用的都是同一个变量 i。当 createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量 对象,所以在每个函数内部 i 的值都是 10。但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期

1
2
3
4
5
6
7
8
9
10
11
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}

关于this对象

1
2
3
4
5
6
7
8
9
10
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函 数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了.

1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"

模仿块级作用域

JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的

1
2
3
4
5
6
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //计数
}

1
2
3
4
5
6
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();

小结

在 JavaScript 编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名, 从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用 JavaScript 函数的强大方式。以下总结 了函数表达式的特点。

  • 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表 达式也叫做匿名函数。
  • 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
  • 递归函数应该始终使用arguments.callee来递归地调用自身,不要使用函数名——函数名可能会发生变化。

当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理
如下。

  • 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
  • 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

使用闭包可以在 JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。

  • 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
  • 结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(即外
    部作用域)中的变量。

闭包还可以用于在对象中创建私有变量,相关概念和要点如下。

  • 即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公 有方法可以访问在包含作用域中定义的变量。
  • 有权访问私有变量的公有方法叫做特权方法。
  • 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。

js 面向对象的程序设计

面向对象的程序设计

  • 理解对象属性
  • 理解并创建对象
  • 理解继承

    1.1 理解对象

    创建自定义对象的最简单方式就是创建一个Object的实例,然后再为它添加属性和方法
    1
    2
    3
    4
    5
    6
    7
    var person = new Object();
    person.name = "Nicholas";
    person.age = 29;
    person.job = "Software Engineer";
    person.sayName = function(){
    alert(this.name);
    };

现在对象字面量成为创建这种对象的首选模式

1
2
3
4
5
6
7
8
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};

1.1.1 属性类型

ECMAScript中有两种属性:数据属性和访问器属性。

  • 数据属性(数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的
    特性。)
  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
  • [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
  • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。

对于像前面例子中那样直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被设置为 true,而[[Value]]特性被设置为指定的值.

要修改属性默认的特性,必须使用 ECMAScript5的Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属 性必须是:configurable、enumerable、writable和value。设置其中的一或多个值,可以修改对应的特性值。

1
2
3
4
5
6
7
8
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"

  • 访问器属性(访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的)。在读取访问器属性时,会调用 getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下 4 个特性。)
  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
  • [[Enumerable]]:表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为true。
  • [[Get]]:在读取属性时调用的函数。默认值为 undefined。
  • [[Set]]:在写入属性时调用的函数。默认值为 undefined。

访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。

1.2 创建对象

虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体。

1. 工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程,考虑到在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。随着 JavaScript 的发展,又一个新模式出现了。

2. 构造函数模式

可以使用构造函数模式将前面的例子重写

1
2
3
4
5
6
7
8
9
10
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

区别

  • 没有显式地创建对象;
  • 直接将属性和方法赋给了 this 对象;
  • 没有 return 语句。
    还应该注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他语言,主要是为了区别于ECMAScript中的其他函数;==因为构造函数本身也是函数,只不过可以用来创建对象而已。==

要创建Person的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4 个步骤:

  • 创建一个新对象;
  • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  • 执行构造函数中的代码(为这个新对象添加属性);
  • 返回新对象。

person1 和 person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向 Person.

1
2
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true

对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是 instan- ceof 操作符要更可靠一些。我们在这个例子中创建的所有对象既是Object的实例,同时也是Person 的实例。

1
2
3
4
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。在这个例子中,person1和person2 之所以同时是Object的实例,是因为所有对象均继承自Object

2.1. 将构造函数当作函数

构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样.

1
2
3
4
5
6
7
8
9
// 当作构造函数使用
var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
// 作为普通函数调用
Person("Greg", 27, "Doctor"); // 添加到window window.sayName(); //"Greg"
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"

2.2. 构造函数的问题

构造函数模式虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个 实例上重新创建一遍。在前面的例子中,person1 和 person2 都有一个名为sayName()的方法,但那 两个方法不是同一个Function的实例。不要忘了ECMAScript 中的函数是对象,因此每定义一个 函数,也就是实例化了一个对象。

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

在这个例子中,我们把sayName()函数的定义转移到了构造函数外部。而在构造函数内部,我们 将 sayName 属性设置成等于全局的sayName函数。这样一来,由于 sayName 包含的是一个指向函数 的指针,因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这样做确实解决了两个函数做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方 法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在, 这些问题可以通过使用原型模式来解决。

3. 原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

3.1 理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype属性所在函数的指针。就拿前面的例子来说, Person.prototype.constructor指向Person。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。

创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中 没有标准的方式访问[[Prototype]],但Firefox、Safari 和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
以前面使用 Person 构造函数和 Person.prototype 创建实例的代码为例,下图展示了各个对 象之间的关系。

来源红皮书
上图展示了 Person 构造函数、Person 的原型属性以及 Person 现有的两个实例之间的关系

Person.prototype指向了原型对象,而Person.prototype.constructor 又指回了Person。原型对象中除了包含constructor 属性之外,还包括后来添加的其他属性。Person的每个实例—— person1和person2都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法,但我们却可以调用person1.sayName()。这是通过查找对象属性的过程来实现的。

原型最初只包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问。

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
alert(person2.name); //"Nicholas"——来自原型

使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(不要忘了它是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true。来看下面这个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty("name")); //false

3.2 原型与 in 操作符

有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

3.3 更简单的原型语法

用一个包含所有属性和方法的对象字面量来重写整个原型对象.

1
2
3
4
5
6
7
8
9
10
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};

用 instanceof 操作符测试 Object 和 Person 仍然返回 true,但 constructor 属性则 等于 Object 而不等于 Person 了。如果 constructor 的值真的很重要,可以像下面这样特意将它设 置回适当的值。

1
2
3
4
5
6
7
8
9
10
11
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};

以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。

3.4 原型的动态性

由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上 反映出来——即使是先创建了实例后修改原型也照样如此。

1
2
3
4
5
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)

尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重 写整个原型对象,那么情况就不一样了。我们知道,调用构造函数时会为实例添加一个指向最初原型的 [[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。 请记住:实例中的指针仅指向原型,而不指向构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error

1.3 继承

许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链 来实现的。

1.3.1 原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的 原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数 的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实 例与原型的链条。这就是所谓原型链的基本概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true

3.1.1 别忘记默认的原型

所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是 Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。

3.1.2 确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系。第一种方式是使用 instanceof 操作符,只要用 这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。以下几行代码就说明了这 一点。

1
2
3
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

第二种方式是使用isPrototypeOf() 方法。同样,只要是原型链中出现过的原型,都可以说是该 原型链所派生的实例的原型,因此 isPrototypeOf()方法也会返回 true

1
2
3
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

3.1.3 原型链的问题

原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引 用类型值的原型。想必大家还记得,我们前面介绍过包含引用类型值的原型属性会被所有实例共享;而 这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原 型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

1.3.1 借用构造函数

有时也叫伪造对象或经典继承,这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象, 因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"

代码中加粗的那一行代码“借调”了超类型的构造函数。通过使用 call()方法(或apply()方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新SubType对象上执行 SuperType()函数中定义的所有对象初始化代码。结果, SubType 的每个实例就都会具有自己的colors属性的副本了。

  • 传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了 SuperType,同时还传递了参数 SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29

以上代码中的 SuperType只接受一个参数name,该参数会直接赋给一个属性。在SubType构造函数内部调用SuperType 构造函数时,实际上是为 SubType 的实例设置了 name 属性。为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中 定义的属性。

  • 借用构造函数的问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

1.3.2 组合继承

组合继承,有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方 法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
instance1.sayName();//"Nicholas";
instance1.sayAge();//29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);//"red,blue,green"
instance2.sayName();//"Greg";
instance2.sayAge();//27

SuperType 构造函数定义了两个属性:name 和 colors。SuperType 的原型定义 了一个方法 sayName()。SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着 又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型 上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继 承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。

1.3.3 原型式继承(略)

1.3.4 寄生式继承(略)

1.3.5 寄生组合式继承

前面说过,组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是 在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子 类型构造函数时重写这些属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);//第二次调用SuperType()
this.age = age;
}
SubType.prototype = new SuperType();//第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};

在第一次调用SuperType构造函数时,SubType.prototype 会得到两个属性:name和colors;它们都是 SuperType 的实例属性,只不过 现在位于SubType 的原型中。当调用SubType构造函数时,又会调用一次 SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性.

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型 原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

1
2
3
4
5
function inheritPrototype(subType,superType){
var prototype=object(superType.prototype);//创建对象
prototype.constructor = subType;//增强对象
subType.prototype = prototype;//指定对象
}

这个示例中的 inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两 个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二 步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。 最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用 inherit- Prototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}

这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

小结

ECMAScript 支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象。

  • 工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来 被构造函数模式所取代。
  • 构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用 new 操作符。不 过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局 限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
  • 原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造 函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。

JavaScript 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函
数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。 原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借 用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的 属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用 原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。

此外,还存在下列可供选择的继承模式。

  • 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
  • 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
  • 寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。

js 引用类型

引用类型

  • 使用对象
  • 创建并操作数组
  • 理解基本的JavaScript类型
  • 使用基本类型和基本包装类型

    1.1 Object类型

    ==创建Object实例的方式有两种==。
  • 第一种是使用 new 操作符后跟 Object 构造函数

    1
    2
    3
    var person = new Object();
    person.name = "Nicholas";
    person.age = 29;
  • 另一种方式是使用对象字面量表示法

    1
    2
    3
    4
    var person = {
    name : "Nicholas",
    age : 29
    };

1.2 Array类型

==创建数组的基本方式有两种==。

  • 第一种是使用Array构造函数

    1
    2
    3
    4
    5
    var colors = new Array();
    var colors = new Array(20);
    var colors = new Array("red", "blue", "green");
    var colors = Array(3); // 创建一个包含 3 项的数组
    var names = Array("Greg"); // 创建一个包含 1 项,即字符串"Greg"的数组
  • 使用数组字面量表示法

    1
    2
    3
    var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
    var names = []; // 创建一个空数组
    var values = [1,2,]; // 不要这样!这样会创建一个包含 2 或 3 项的数组

1.2.1 检测数组

  • instanceof 操作符
    1
    2
    3
    if (value instanceof Array){
    //对数组执行某些操作
    }

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实 际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自 不同的构造函数。

  • Array.isArray()
    1
    2
    3
    if (Array.isArray(value)){
    //对数组执行某些操作
    }

这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

1.2.2 转换方法

所有对象都具有toLocaleString()、toString()和 valueOf()方法。

  • 调用数组的toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。
  • valueOf()返回的还是数组

数组继承的toLocaleString()、toString()和 valueOf()方法,在默认情况下都会以逗号分隔的字符串的形式返回数组项。而如果使用 join()方法,则可以使用不同的分隔符来构建这个字符串。

1.2.3 栈方法

栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最早被移除。

ECMAScript为数组专门提供了push()和pop()方法,以便实现类似栈的行为。

  • push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并==返回修改后数组的长度==。
  • pop()方法则从数组末尾移除最后一项,减少数组的 length值,然后==返回移除的项==。

    1.2.4 队列方法

    队列数据结构的访问规则是FIFO(First-In-First-Out, 先进先出)。

结合使用 shift()和push()方法,可以像使 用队列一样使用数组。

同时使用unshift()和pop()方法,可以从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项

1.2.5 重排序方法

  • reverse()
  • reverse()方法会反转数组项的顺序
  • sort()
  • sort()方法按升序排列数组项。
  • sort()方法会调用每个数组项的toString()转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值,sort()方法比较的也是字符串。这种排序方式在很多情况下都不是最佳方案。
    1
    2
    3
    var values = [0, 1, 5, 10, 15];
    values.sort();
    alert(values); //0,1,10,15,5

==解决排序方案:==
sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。

该比较函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数a和b,其返回值如下:

  • 若a小于b,在排序后的数组中a应该出现在 b 之前,则返回一个小于0的值。
  • 若a等于b,则返回 0。
  • 若a大于b,则返回一个大于0的值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function compare(value1, value2) {
    if (value1 < value2) {
    return -1;
    } else if (value1 > value2) {
    return 1;
    } else {
    return 0;
    }
    }

这个比较函数可以适用于大多数数据类型,只要将其作为参数传递给 sort()方法即可.

1
2
3
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15

对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可.

1
2
3
function compare(value1, value2){
return value2 - value1;
}

1.2.6 操作方法

  • concat()
  • concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数 添加到这个副本的末尾,最后返回新构建的数组。
  • slice()
  • 它能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项— —==但不包括结束位置的项==。注意,==slice()方法不会影响原始数组==。
  • 如果结束位置小于起始位置,则返回空数组。

    1
    2
    3
    4
    5
    var colors = ["red", "green", "blue", "yellow", "purple"];
    var colors2 = colors.slice(1);
    var colors3 = colors.slice(1,4);
    alert(colors2); //green,blue,yellow,purple
    alert(colors3); //green,blue,yellow
  • [x] splice()

  • splice()的主要用途是向数组的中部插入项:删除、插入、替换
  • 删除:可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。 例如,splice(0,2)会删除数组中的前两项。
  • 插入:可以向指定位置插入任意数量的项,只需提供 3个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,”red”,”green”)会从当前数组的位置 2 开始插入字符串”red”和”green”。
  • 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起 始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如, splice (2,1,”red”,”green”)会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串 “red”和”green”。
  • ==splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何 项,则返回一个空数组)。==
  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var colors = ["red", "green", "blue"];
    var removed = colors.splice(0,1); // 删除第一项
    alert(colors); // green,blue
    alert(removed);//red,返回的数组中只包含一项
    removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项
    alert(colors); // green,yellow,orange,blue
    alert(removed); // 返回的是一个空数组
    removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
    alert(colors); //green,red,purple,orange,blue
    alert(removed); //yellow,返回的数组中只包含一项

1.2.7 位置方法

indexOf()和 lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1

在比较第一个参数与数组中的每一项时,会使用全等操作符。

  • indexOf()
  • indexOf()方法从数组的开头(位置0)开始向后查找
  • lastIndexOf()
  • lastIndexOf()方法可返回一个指定的字符串值==最后出现的位置==,在一个字符串中的指定位置从后向前搜索。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var numbers = [1,2,3,4,5,4,3,2,1];
    alert(numbers.indexOf(4)); //3
    alert(numbers.lastIndexOf(4)); //5
    alert(numbers.indexOf(4, 4)); //5
    alert(numbers.lastIndexOf(4, 4)); //3
    var person = { name: "Nicholas" };
    var people = [{ name: "Nicholas" }];
    var morePeople = [person];
    alert(people.indexOf(person)); //-1
    alert(morePeople.indexOf(person)); //0

1.2.8 迭代方法

以下方法都不会修改数组中的包含的值。

  • every()
  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true
  • some()
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。
  • filter()
  • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach()
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map()
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。

==every()、some()==

1
2
3
4
5
6
7
8
9
10
11
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
return (item > 2);
});
alert(everyResult); //false
var someResult = numbers.some(function(item, index, array){
return (item > 2);
});
alert(someResult); //true

==filter()==

1
2
3
4
5
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]

==map()==

1
2
3
4
5
var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]

==forEach()==

1
2
3
4
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
//执行某些操作
});

1.2.9归并方法

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。传给 reduce()和 reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。

  • reduce()
  • reduce()方法从数组的第一项开始,逐个遍历到最后
  • reduceRight()
  • reduceRight()则从数组的最后一项开始,向前遍历到第一项。
    1
    2
    3
    4
    5
    var values = [1,2,3,4,5];
    var sum = values.reduce(function(prev, cur, index, array){
    return prev + cur;
    });
    alert(sum); //15

第一次执行回调函数,prev 是 1,cur 是 2。第二次,prev 是 3(1 加 2 的结果),cur 是 3(数组 的第三项)。这个过程会持续到把数组中的每一项都访问一遍,最后返回结果。
reduceRight()的作用类似,只不过方向相反而已。

1.3 Date类型

常用的日期方法:

1
2
3
4
5
6
7
8
9
10
11
Date(); // 返回当日的日期和时间。
Data.now();//返回表示调用这个方法时的日期和时间的毫秒数
var myDate=new Date(); //创建日期对象
getTime();//返回 1970 年 1 月 1 日至今的毫秒数。
getFullYear();//从 Date 对象以四位数字返回年份。
getMonth();//从 Date 对象返回月份 (0 ~ 11)。
getDate();//从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay();//从 Date 对象返回一周中的某一天 (0 ~ 6)。
getHours();//返回 Date 对象的小时 (0 ~ 23)。
getMinutes();//返回 Date 对象的分钟 (0 ~ 59)。
getSeconds();//返回 Date 对象的秒数 (0 ~ 59)。

1.4 RegExp类型

  • 字面量形式
    1
    var expression = / pattern / flags ; //语法

模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、 向前查找以及反向引用。

每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。正则表达式的匹配模式支持下列 3 个标志。

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*
    *匹配字符串中所有"at"的实例
    */
    var pattern1 = /at/g;
    /*
    *匹配第一个"bat"或“cat”,不区分大小写
    */
    var pattern2 = /[bc]at/i;
    /* 10
    * 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写
    */
    var pattern3 = /.at/gi;

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:==( [ { \ ^ $ | ) ? * + .]}==

这些元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,就必须对它们进行转义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
*匹配第一个“bat”或“cat”,不区分大小写
*/
var pattern1 = /[bc]at/i;
/*
* 匹配第一个" [bc]at",不区分大小写
*/
var pattern2 = /\[bc\]at/i;
/*
* 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写
*/
var pattern3 = /.at/gi;
/*
* 匹配所有".at",不区分大小写
*/
var pattern4 = /\.at/gi;

  • RegExp构造函数形式

它接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。

1
var pattern2 = new RegExp("[bc]at", "i");

  • RegExp实例方法
  • exec()
  • test()

    1.5 Function类型

    每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。

    1.5.1 函数声明与函数表达式

    解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

    1.5.2 作为值的函数

    因为 ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function callSomeFunction(someFunction,someArgument){
    return someFunction(someArgument);
    }
    function add10(num){
    return num + 10;
    }
    var result1 = callSomeFunction(add10, 10);
    alert(result1); //20
    function getGreeting(name){
    return "Hello, " + name;
    }
    var result2 = callSomeFunction(getGreeting,"Nicholas");
    alert(result2); //"Hello, Nicholas"

callSomeFunction()函数是通用的,即无论第一个参数中传递进来的是什么函数,它都会返回执行第一个参数后的结果。

1.5.3 函数内部属性

在函数内部,有两个特殊的对象:arguments 和 this。
虽然arguments的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

1
2
3
4
5
6
7
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}

定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee。

1
2
3
4
5
6
7
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}

在这个重写后的factorial()函数的函数体内,没有再引用函数名 factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。

1
2
3
4
5
6
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0

在此,变量 trueFactorial获得了factorial的值,实际上是在另一个位置上保存了一个函数的指针。然后,我们又将一个简单地返回0的函数赋值给factorial变量。如果像原来的 factorial() 那样不使用arguments.callee,调用 trueFactorial()就会返回0。可是,在解除了函数体内的代 码与函数名的耦合状态之后,trueFactorial()仍然能够正常地计算阶乘;至于factorial(),它现在只是一个返回 0 的函数。

this 引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时, this 对象引用的就是 window)。

1
2
3
4
5
6
7
8
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"

1.5.4 函数属性和方法

每个函数都包含两个属性:length和prototype。其中,length 属性表示函数希望接收的命名参数的个数。

1
2
3
4
function sum(num1, num2){
return num1 + num2;
}
alert(sum.length); //2

prototype 是保存它们所有实例方法的真正所在。换句话说,诸如 toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访 问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要的。在ECMAScript5中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。

每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。

call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来

1
2
3
4
5
6
7
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20

在使用 call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用apply()没有什么不同。至于是使用 apply()还是call(),完全取决于你采取哪种给函数传递参数的方式最方便。 如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply() 肯定更方便;否则,选择 call()可能更合适。

传递参数并非 apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

1
2
3
4
5
6
7
8
9
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其 this 值会被绑 定到传给 bind()函数的值。

1
2
3
4
5
6
7
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

1.5.6 基本包装类型

为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型:Boolean、Number 和 String。

==每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们 能够调用一些方法来操作这些数据。==

1
2
3
var s1 = "some text";
var s2 = s1.substring(2);
s2; // "me text"

基本类型值不是对象,因而从逻辑上讲它们不应该有方法。其实,为了让我们实现这种直观的操作, 后台已经自动完成了一系列的处理。

  • 创建 String 类型的一个实例;
  • 在实例上调用指定的方法;
  • 销毁这个实例。

可以将以上三个步骤想象成是执行了下列 ECMAScript 代码。

1
2
3
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。来看下面的例子:

1
2
3
var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

Boolean类型

Boolean 类型的实例重写了valueOf()方法,返回基本类型值 true 或 false;重写了 toString() 方法,返回字符串”true”和”false”。可是,Boolean 对象在 ECMAScript 中的用处不大,因为它经 常会造成人们的误解。其中最常见的问题就是在布尔表达式中使用 Boolean 对象。

1
2
3
4
5
6
7
var falseObject = new Boolean(false);
var result = falseObject && true;
alert(result); //true
var falseValue = false;
result = falseValue && true;
alert(result); //false

基本类型与引用类型的布尔值还有两个区别。首先,typeof 操作符对基本类型返回”boolean”,而对引用类型返回”object”。其次,由于Boolean对象是 Boolean 类型的实例,所以使用instanceof操作符测试 Boolean对象会返回true,而测试基本类型的布尔值则返回 false。

1
2
3
4
alert(typeoffalseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean);//true
alert(falseValue instanceof Boolean);//false

建议是永远不要使 用 Boolean 对象。

Number类型

Number 类型也重写了valueOf()、toLocaleString()和 toString()方法。重写后的valueOf()方法返回对象表示的基本类型的数值,另外两个方法则返回字符串形式的数值。

可以为 toString()方法传递一个表示基数的参数,告诉它返回几进制数值的字符串形式

1
2
3
4
5
6
var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"

除了继承的方法之外,Number类型还提供了一些用于将数值格式化为字符串的方法。其中,toFixed()方法会按照指定的小数位返回数值的字符串表示

1
2
var num = 10;
alert(num.toFixed(2)); //"10.00"

我们仍 然不建议直接实例化Number类型,而原因与显式创建Boolean对象一样。具体来讲,就是在使用 typeof 和instanceof操作符测试基本类型数值与引用类型数值时,得到的结果完全不同。

String类型

String 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的 valueOf()、toLocale- String()和 toString()方法,都返回对象所表示的基本字符串值。

String 类型的每个实例都有一个length属性,表示字符串中包含多个字符。

String 类型提供了很多方法,用于辅助完成对 ECMAScript中字符串的解析和操作.

1. 字符方法

两个用于访问字符串中特定字符的方法是:charAt()和 charCodeAt()。

1
2
3
4
5
var stringValue = "hello world";
alert(stringValue.charAt(1)); //"e"
var stringValue = "hello world";
alert(stringValue.charCodeAt(1));//输出"101"

2. 字符串操作方法
  • concat()
  • 用于将一或多个字符串拼接起来,返回拼接得到的新字符串.()

    1
    2
    3
    4
    var stringValue = 'hello ';
    var result = stringValue.concat('world');
    alert(result); // 'hello world'
    alert(stringValue); // 'hello '
  • [x] slice()、substr()和 substring()

  • 它们只是返回一个基本类型的字符串值,对原始字符串没有任何影响

    1
    2
    3
    4
    5
    6
    7
    var stringValue = "hello world";
    alert(stringValue.slice(3));//"lo world"
    alert(stringValue.substring(3));//"lo world"
    alert(stringValue.substr(3));//"lo world"
    alert(stringValue.slice(3, 7));//"lo w"
    alert(stringValue.substring(3,7));//"lo w"
    alert(stringValue.substr(3, 7));//"lo worl"
  • 在传递给这些方法的参数是负值的情况下,它们的行为就不尽相同了。其中,slice()方法会将传 入的负值与字符串的长度相加,substr()方法将负的第一个参数加上字符串的长度,而将负的第二个 参数转换为 0。最后,substring()方法会把所有负值参数都转换为 0。

    1
    2
    3
    4
    5
    6
    7
    var stringValue = "hello world";
    alert(stringValue.slice(-3));//"rld"
    alert(stringValue.substring(-3));//"hello world"
    alert(stringValue.substr(-3));//"rld"
    alert(stringValue.slice(3, -4));//"lo w"
    alert(stringValue.substring(3, -4));//"hel"
    alert(stringValue.substr(3, -4));//""(空字符串)
3. 字符串位置方法
  • indexOf()和lastIndexOf()
  • 从一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没有找到该子字符串,则返回-1)。这两个方法的区别在于:indexOf()方法从字符串的开头向后搜索子字符串,而lastIndexOf()方法是从字符串的末尾向前搜索子字符串.
    1
    2
    3
    var stringValue = "hello world";
    alert(stringValue.indexOf("o"));//4
    alert(stringValue.lastIndexOf("o"));//7
1
2
3
var stringValue = "hello world";
alert(stringValue.indexOf("o", 6));//7
alert(stringValue.lastIndexOf("o", 6)); //4
4. trim()方法
  • ECMAScript5为所有字符串定义了trim()方法。这个方法会创建一个字符串的副本,删除前置及 后缀的所有空格,然后返回结果.
    1
    2
    3
    4
    var stringValue = " hello world ";
    var trimmedStringValue = stringValue.trim();
    alert(stringValue);//" hello world "
    alert(trimmedStringValue);//"hello world"
5. 字符串大小写转换方法

ECMAScript 中涉及字符串大小写转换的方法有 4 个:toLowerCase()、toLocaleLowerCase()、toUpperCase()和 toLocaleUpperCase()。其中,toLowerCase()和toUpperCase()是两个经典的方法,toLocaleLowerCase()和toLocaleUpperCase()方法则是针对特定地区的实现.

6. split()

这个方法可以基于指定的分隔符将一个字符串分割成 多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个RegExp对象(这个方 法不会将字符串看成正则表达式)。split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。

1
2
3
4
var colorText = "red,blue,green,yellow";
var colors1 =colorText.split(","); //['red','blue', 'green', 'yellow']
var colors2 = colorText.split(",", 2); //["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]

1.5.7 单体内置对象

ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对 象在 ECMAScript程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如 Object、Array 和 String。
ECMA-262还定义了两个单体内置对象:Global 和 Math。

1. Global对象

ECMAScript中的Global对象在某种意义上是作为一个终极的“兜底儿对象”来定义的.

isNaN()、isFinite()、parseInt()以及parseFloat(),实际上全都是Global对象的方法。除此之外,Global 对象还包含其他一些方法。

  • URI 编码方法
  • encodeURI()和encodeURIComponent()方法可以对 URI(UniformResourceIdentifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的 URI 中不能包含某些字符,例如空格。而这两个 URI 编码方法就可以对URI进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。
  • encodeURI()主要用于整个URI,而encodeURIComponent()主要用于对 URI 中的某一段进行编码。
  • encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码。
    1
    2
    3
    4
    5
    6
    var uri = "http://www.wrox.com/illegalvalue.htm#start";
    // "http://www.wrox.com/illegal%20value.htm#start"
    alert(encodeURI(uri));
    // "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
    alert(encodeURIComponent(uri));

使用 encodeURI()编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了 %20。而 encodeURIComponent()方法则会使用对应的编码替换所有非字母数字字符。这也正是可以 对整个 URI 使用encodeURI(),而只能对附加在现有 URI 后面的字符串使用 encodeURIComponent() 的原因所在。

一般来说,我们使用 encodeURIComponent()方法的时候要比使用 encodeURI()更多,因为在实践中更常见的是对查询字符串参数而不是对基础 URI 进行编码。

与 encodeURI()和encodeURIComponent()方法对应的两个方法分别是decodeURI()和decodeURIComponent()。

  • eval()方法
  • eval() 方法就像是一个完整的ECMAScript解析器,它只接受一个参数,即要执行的ECMAScript(或JavaScript) 字符串。

    1
    2
    3
    eval("alert('hi')");
    相当于
    alert("hi");
  • [x] Global 对象的属性

  • 特殊的值undefined、NaN 以及 Infinity 都是 Global 对象的属性。此外,所有原生引用类型的构造函数,像 Object 和Function,也都是Global 对象的属性。Global对象的所有属性:
  • undefined、NaN、Infinity、Object、Array、Function、Boolean、String、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError
  • window对象
  • ECMAScript虽然没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为 window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了window 对象的属性。

    2. Math对象

  • [x] min()和 max()方法

    1
    2
    3
    4
    var max = Math.max(3, 54, 32, 16);
    alert(max); //54
    var min = Math.min(3, 54, 32, 16);
    alert(min); //3
  • 在数组中找出最大值或最小值,使用apply()方法

    1
    2
    var arr = [1,2,3,4,5,6,7];
    var max = Math.max.apply(Math, arr);

这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确地设置 this 值。然后,可 以将任何数组作为第二个参数。

  • 舍入方法

下面来介绍将小数值舍入为整数的几个方法:Math.ceil()、Math.floor()和Math.round()。这三个方法分别遵循下列舍入规则:

  • Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
  • Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
  • Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    alert(Math.ceil(25.9)); //26
    alert(Math.ceil(25.5)); //26
    alert(Math.ceil(25.1)); //26
    alert(Math.round(25.9)); //26
    alert(Math.round(25.5)); //26
    alert(Math.round(25.1)); //25
    alert(Math.floor(25.9)); //25
    alert(Math.floor(25.5)); //25
    alert(Math.floor(25.1)); //25
  • [x] random()方法

Math.random()方法返回大于等于 0 小于 1 的一个随机数。对于某些站点来说,这个方法非常实用,因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式,就可以利用Math.random() 从某个整数范围内随机选择一个值。

==值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)==

公式中用到了 Math.floor()方法,这是因为 Math.random()总返回一个小数值。而用这个小数 值乘以一个整数,然后再加上一个整数,最终结果仍然还是一个小数。举例来说,如果你想选择一个 1 到 10 之间的数值,可以像下面这样编写代码:

1
var num = Math.floor(Math.random() * 10 + 1);

总共有 10 个可能的值(1到10),而第一个可能的值是1。而如果想要选择一个介于 2 到 10 之间的值,就应该将上面的代码改成这样:
var num = Math.floor(Math.random() * 9 + 2);

从 2 数到 10 要数9个数,因此可能值的总数就是 9,而第一个可能的值就是 2。多数情况下,其实 都可以通过一个函数来计算可能值的总数和第一个可能的值

1
2
3
4
5
6
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
var num = selectFrom(2, 10);
alert(num); // 介于2和10之间(包括2和10)的一个数值

从数组中随机 取出一项

1
2
3
var colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
var color = colors[selectFrom(0, colors.length-1)];
alert(color);//可能是数组中包含的任何一个字符串

  • 其他方法(略)

小结

对象在JavaScript中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象, 现简要总结如下:

  • 引用类型与传统面向对象程序设计中的类相似,但实现不同
  • Object是一个基础类型,其他所有类型都从 Object继承了基本的行为;
  • Array 类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;
  • Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
  • RegExp类型是ECMAScript支持正则表达式的一个接口,提供了最基本的和一些高级的正则表 达式功能。

函数实际上是Function类型的实例,因此函数也是对象;而这一点正是JavaScript最有特色的地方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。

因为有了基本包装类型,所以JavaScript中的基本类型值可以被当作对象来访问。三种基本包装类 型分别是:Boolean、Number和String。以下是它们共同的特征:

  • 每个包装类型都映射到同名的基本类型;
  • 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;
  • 操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。

在所有代码执行之前,作用域中就已经存在两个内置对象:Global 和 Math。在大多数 ECMAScript 实现中都不能直接访问 Global 对象;不过,Web 浏览器实现了承担该角色的 window 对象。全局变 量和函数都是 Global 对象的属性。Math 对象提供了很多属性和方法,用于辅助完成复杂的数学计算 任务。

js 变量、作用域、内存

变量、作用域和内存问题

  • 理解基本类型和引用类型的值
  • 理解执行环境
  • 理解垃圾收集

    1.1 基本类型和引用类型的值

    基本类型值指的是 简单的数据段,而引用类型值指那些可能由多个值构成的对象。

    1.1.1 动态的属性

    对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。

    1.1.2 复制变量值

    基本类型:如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
    引用类型:当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。

    1.1.3 传递参数

    ECMAScript中所有函数的参数都是按值传递的。访问变量有按值和按引用两种方式,而参数只能按值传递。
    1
    2
    3
    4
    5
    6
    7
    8
    function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
    }
    var person = new Object();
    setName(person);
    alert(person.name); //"Nicholas"

即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
可以把ECMAScript函数的参数想象成局部变量。

1.1.4 检测类型

typeof操作符是确定一个变量是字符串、数值、布尔值,还是 undefined 的最佳工具。如果变量的值是一个对象或 null,则typeof操作符会像下面例子中所示的那样返回”object”。

虽然在检测基本数据类型时 typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的 用处不大。

想知对象的类型,ECMAScript提供了instanceof操作符。语法如下:

1
result = variable instanceof constructor

根据规定,所有引用类型的值都是 Object 的实例。因此,==在检测一个引用类型值和 Object构造函数时,instanceof操作符始终会返回true==。当然,如果 ==使用instanceof操作符检测基本类型的值,则该操作符始终会返回false==,因为基本类型不是对象。

使用typeof操作符检测函数时,该操作符会返回”function”

1.2 执行环境及作用域

JavaScript 没有块级作用域

1
2
3
4
if (true) {
var color = "blue";
}
alert(color); // "blue"

1
2
3
4
for (var i = 0; i < 10; i++){
doSomething(i);
}
alert(i); //10

1.2.1 声明变量

使用 var 声明的变量会自动被添加到最接近的环境中。如果初始化变量时没有使用 var 声明,该变量会自 动被添加到全局环境。

在编写 JavaScript 代码的过程中,不声明而直接初始化变量是一个常见的错误做 法,因为这样可能会导致意外。我们建议在初始化变量之前,一定要先声明,这样就 可以避免类似问题。在严格模式下,初始化未经声明的变量会导致错误。

1.2.2 查询标识符

搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到 了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域链向上 搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也没有找到这个标识符,则意味 着该变量尚未声明。

小结

JavaScript变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下5种基本数据类型:Undefined、Null、Boolean、Number、String和Symbol(ES6)。基本类型值和引用类型值具 有以下特点:

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
  • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  • 引用类型的值是对象,保存在堆内存中;
  • 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
  • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同
    一个对象;
  • 确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执 行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几点总结:

  • 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  • 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
  • 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全
    局环境
  • 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  • 变量的执行环境有助于确定应该何时释放内存。
|