JavaScript 精粹
数据类型
javascript是弱类型语言,是一种轻量级的编程语言。不要小看它。
字符串(String)
数字(Number)
布尔(Boolean)
空(Null)
未定义(Undefined)
对象(Object):数组(Array),正则表达式(RegExp),日期(Date),数学(Math)......
可以使用 typeof 判断数据类型,操作符返回一个字符串,但并非返回的所有结果都符合预期
typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。
var a; //输出值为undefined,数据类型为undefined
var b=2; //输出值为2,数据类型为number
var c='你好'; //输出值为你好,数据类型为string
var d=[1,2,3]; //输出值为1,2,3,数据类型为object
var e=new Object(); //输出值为[object Object],数据类型为object
e.aa=1;
e.bb=2;
var f=true; //输出值为true,数据类型为boolean
var g=null; //输出值为null,数据类型为object
var h = new Date(); //输出值为Wed Oct 12 2016 22:42:13 GMT+0800 (中国标准时间),数据类型为object
var i = function(a,c){
alert(a*c) //数据类型为function
};
//最常见的判断方法:typeof
console.log('输出值为'+a+',数据类型为'+typeof a);
console.log('输出值为'+b+',数据类型为'+typeof b);
console.log('输出值为'+c+',数据类型为'+typeof c);
console.log('输出值为'+d+',数据类型为'+typeof d);
console.log('输出值为'+e+',数据类型为'+typeof e);
console.log('输出值为'+f+',数据类型为'+typeof f);
console.log('输出值为'+g+',数据类型为'+typeof g);
console.log('输出值为'+h+',数据类型为'+typeof h);
console.log('输出值为'+i+',数据类型为'+typeof i);
instanceof 运算符简介
在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。
var Stringobj = new String("hello world");
console.log(Stringobj instanceof String); // 输出 "true"
这段代码问的是“变量 oStringObject 是否为 String 对象的实例?”oStringObject 的确是 String 对象的实例,因此结果是"true"。尽管不像 typeof 方法那样灵活,但是在 typeof 方法返回 "object" 的情况下,instanceof 方法还是很有用的。
使用 instanceof 就是判断一个实例是否属于某种类型
// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true
instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型。
// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
根据对象的constructor判断对象类型
var a=[1,2,3];
var b=new Date();
var c=function(){};
console.log(a.constructor === Array); //true
console.log(b.constructor === Date) //true
console.log(c.constructor === Function) //true
constructor是什么,简单的理解,constructor指的就是对象的构造函数。
function Foo(){};
var foo = new Foo();
console.log(foo.constructor);//Foo
console.log(Foo.constructor);//Function
console.log(Object.constructor);//Function
console.log(Function.constructor);//Function
知道了constructor是什么,不得不讲讲Prototype与Constructor的关系;
在 JavaScript 中,每个函数都有名为“prototype”的属性,用于引用原型对象。此原型对象又有名为“constructor”的属性,它反过来引用函数本身。这是一种循环引用;
function Dog(){}
alert(Dog === Dog.prototype.constructor);//true
注意: constructor 在类继承时会出错
function A(){};
function B(){};
A.prototype=new B(); //A继承自B
var cuu=new A();
console.log(A.constructor===Function); //true
console.log(cuu.constructor===B); //true
cuu.constructor=A; //将自己的类赋值给对象的constructor属性
console.log(cuu.constructor===A); //true
console.log(cuu.constructor===B); //false
使用Object.prototype上的原生toString()方法判断数据类型
var aa='String';
var bb=123;
var cc=[1,2,3];
var dd=new Date();
var ee=function(){};
console.log(Object.prototype.toString.call(aa) === '[object String]');//true
console.log(Object.prototype.toString.call(bb) === '[object Number]');//true
console.log(Object.prototype.toString.call(cc) === '[object Array]');//true
console.log(Object.prototype.toString.call(dd) === '[object Date]'); //true
console.log(Object.prototype.toString.call(ee) === '[object Function]');//true
console.log(ee.toString.call()); //toString()函数用于将当前对象以字符串的形式返回。
变量
在应用程序中,使用变量来来为值命名。变量的名称称为 identifiers
声明
1.使用关键字 var :函数作用域
2.使用关键字 let :块作用域 (block scope local variable)
3.直接使用:全局作用域
var obj = 1;
console.log(obj);//1
(function() {
var varTest=2;
let letTest=1;
console.log(varTest); //输出2
console.log(letTest); //输出1
})();
只声明不赋值,变量的默认值是 undefined
const 关键字可以声明不可变变量,同样为块作用域。对不可变的理解在对象上的理解需要注意
const num = 1;
const obj = {
prop: 'value'
};
num = 2; // Uncaught TypeError: Assignment to constant variable.
obj['prop'] = 'value2';
obj = []; // Uncaught TypeError: Assignment to constant variable.
变量提升
JavaScript中可以引用稍后声明的变量,而不会引发异,这一概念称为变量声明提升(hoisting)
console.log(a); // undefined
var a = 2;
函数
一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的 子程序
定义函数
1.函数声明
2.函数表达式
3.Function 构造函数
4.箭头函数
function fn(){}
var fn1 = function(){}
var fn2 = new fn('num','obj')
var fn3 = (param) => {}
arguments
1.arguments: 一个包含了传递给当前执行函数参数的类似于数组的对象
2.arguments.length: 传给函数的参数的数目
3.arguments.caller: 调用当前执行函数的函数
4.arguments.callee: 当前正在执行的函数
function foo() {
return arguments;
}
var bb=foo(1, 2, 3); // Arguments[3]
console.log(bb); //[1, 2, 3]
console.log(bb.length); //3
function myFunction(a, b) {
return arguments.length;
}
document.getElementById("demo").innerHTML = myFunction(4, 3,5);
求最大数
function findMax() {
var i, max = 0;
for(i = 0; i < arguments.length; i++) { //arguments.length等于传参函数的长度 进行循环机制
if (arguments[i] > max) { //如果遍历每一个arguments的值大于max就给max赋值
max = arguments[i]; //遍历第一次2大于0,max=2,第二次14大于max,max=14,第三次6不大于14if等于false不成立max不赋值
}
}
return max;
}
console.log(findMax(2,14,6,8))//14
计算和
function sumAll() {
var i, sum = 0;
for(i = 0; i < arguments.length; i++) { //arguments.length等于传参函数的长度 进行循环机制
sum += arguments[i]; //遍历每一次sum=0+2;2+14......计算出和
}
return sum; //返回计算值sum
}
console.log(sumAll(2,14,6,8))//30
函数的参数可以在定义的时候约定默认值
function myFunction(x, y) {
if (y === undefined) {
y = 0;
}
return x * y;
}
console.log(myFunction(1,2));//2
console.log(myFunction(1)); //0
//如果y已经定义 , y || 返回 y, 因为 y 是 true, 否则返回 0, 因为 undefined 为 false。
function myFunction(x, y) {
y=y||0
return x * y;
}
console.log(myFunction(1,2));//2
console.log(myFunction(1)); //0
function fn (a = 2, b = 3) {
return a + b;
}
console.log(fn(2, 3)); // 5
console.log(fn(2)); // 5
console.log(fn()); // 5
JavaScript 函数调用
作为一个函数调用
function myFunction(a, b) {
return a * b; //调用函数返回参数a*b
}
console.log(myFunction(12, 2)); //24
以上函数不属于任何对象。但是在 JavaScript 中它始终是默认的全局对象。
在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTML 页面。
在浏览器中的页面对象是浏览器窗口(window 对象)。以上函数会自动变为 window 对象的函数。
myFunction() 和 window.myFunction() 是一样的:
function myFunction(a, b) {
return a * b; //调用函数返回参数a*b
}
console.log(window.myFunction(12, 2)); //24
全局对象
当函数没有被自身的对象调用时, this 的值就会变成全局对象。
在 web 浏览器中全局对象是浏览器窗口(window 对象)。
该实例返回 this 的值是 window 对象:
function myFunction() {
return this; //this 的值是 window 对象:
}
console.log(myFunction()); //Window {external: Object, …}
函数作为方法调用
在 JavaScript 中你可以将函数定义为对象的方法。
以下实例创建了一个对象 (myObject), 对象有两个属性 (firstName 和 lastName), 及一个方法 (fullName):
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
console.log(myObject.fullName()); //John Doe
fullName 方法是一个函数。函数属于对象。 myObject 是函数的所有者。
this对象,拥有 JavaScript 代码。实例中 this 的值为 myObject 对象。
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function() {
return this;
}
}
console.log(myObject.fullName()); //返回 [object Object] (所有者对象)
使用构造函数调用函数
如果函数调用前使用了 new 关键字, 则是调用了构造函数。
这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
var x = new myFunction("John","Doe");
console.log(x.firstName); // 返回 "John"
构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。
构造函数中 this 关键字没有任何的值。
this 的值在函数调用时实例化对象(new object)时创建。
作为函数方法调用函数
在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。
call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。
function myFunction(a, b) {
return a * b;
}
console.log(myFunction.call(myFunction, 10, 2)); // 返回 20
function myFunctions(a, b) {
return a * b;
}
myArray = [10,2];
console.log(myFunctions.apply(myFunctions,myArray)); // 返回 20
两个方法都使用了对象本身作为第一个参数。两者的区别在于第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。JavaScript 闭包
JavaScript 变量可以是局部变量或全局变量。
私有变量可以用到闭包。
全局变量
函数可以访问函数内部定义的变量
function myFunction() {
var a = 4; //函数可以访问函数内部定义的变量
console.log( a * a); //16
}
myFunction(); //调用函数
函数也可以访问函数外部定义的变量
var a = 4;
function myFunction() {
console.log( a * a); //16
}
myFunction(); //调用函数
后面一个实例中, a 是一个 全局 变量。
在web页面中全局变量属于 window 对象。
全局变量可应用于页面上的所有脚本。
在第一个实例中, a 是一个 局部 变量。
局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。
全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。
变量声明是如果不使用 var 关键字,那么它就是一个全局变量,即便它在函数内定义。JavaScript 内嵌函数
所有函数都能访问全局变量。
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
console.log(add()); //1
console.log(add()); //1
JavaScript 闭包
<p>局部变量计数。</p>
<button type="button" οnclick="myFunction()">计数!</button>
<p id="demo">0</p>
<script>
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
function myFunction(){
document.getElementById("demo").innerHTML = add();
}
</script>
解析
变量 add 指定了函数自我调用的返回字值。
自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。
add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。
这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。
计数器受匿名函数的作用域保护,只能通过 add 方法修改。
闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。
每个函数都有一个 prototype 的对象属性,对象内有一个 constructor 属性,默认指向函数本身
每个对象都有一个 __proto__ 的属性,属相指向其父类型的 prototype
function oo(){
this.x=1;
}
console.log(oo.prototype);
function oo(){
this.x=1;
}
console.log(oo.prototype);
var gg=new oo(); //构造函数
console.log(gg.__proto__);
function oo(a){
this.x=a;
}
oo.prototype.print = function () {
console.log(this.x);
};
var gg=new oo(1); //构造函数
gg.print();
console.log(oo.prototype);
console.log(gg.__proto__);
console.log(gg.print.prototype);
对象
JavaScript 中对象是可变 键控集合 (keyed collections)
定义对象
1, 字面量
2,构造函数
var obj = {
prop: 'value',
fn: function(){this.l=2}
};
var date = new Date();
构造函数
构造函数和普通函数并没有区别,使用 new 关键字调用就是构造函数,使用构造函数可以 实例化 一个对象
函数的返回值有两种可能
显式调用 return 返回 return 后表达式的求值
没有调用 return 返回 undefined
function People(name, age) {
this.name = name;
this.age = age;
}
var people = new People('Byron', 26);
构造函数返回值
1,没有返回值
2,简单数据类型
3,对象类型
前两种情况构造函数返回构造对象的实例,实例化对象正是利用的这个特性
第三种构造函数和普通函数表现一致,返回 return 后表达式的结果
prototype
1,每个函数都有一个 prototype 的对象属性,对象内有一个 constructor 属性,默认指向函数本身
2,每个对象都有一个 __proto__ 的属性,属相指向其父类型的 prototype
function Person(name) {
this.name = name;
}
Person.prototype.print = function () {
console.log(this.name);
};
var p1 = new Person('Byron');
var p2 = new Person('Casper');
p1.print();
p2.print();
this 和作用域
作用域可以通俗的理解
我是谁
我有哪些马仔
其中我是谁的回答就是 this
马仔就是我的局部变量
this 场景
普通函数
严格模式:undefined
非严格模式: 全局对象
Node: global
浏览器: window
构造函数:对象的实例
对象方法:对象本身
call & apply
fn.call(context, arg1, arg2, …, argn)
fn.apply(context, args)
function isNumber(obj) {
return Object.prototype.toString.call(obj) === '[object Number]';
}
Function.prototype.bind
bind 返回一个新函数,函数的作用域为 bind 参数
function fn() {
this.i = 0;
setInterval(function () {
console.log(this.i++);
}.bind(this), 500)
}
fn();
() => {}
箭头函数是 ES6 提供的新特性,是简写的 函数表达式,拥有词法作用域和 this 值
function fn() {
this.i = 0;
setInterval(() => {
console.log(this.i++);
}, 500)
}
fn();
继承
在 JavaScript 的场景,继承有两个目标,子类需要得到父类的:
对象的属性
对象的方法
function inherits(child, parent) {
var _proptotype = Object.create(parent.prototype);
_proptotype.constructor = child.prototype.constructor;
child.prototype = _proptotype;
}
function People(name, age) {
this.name = name;
this.age = age;
}
People.prototype.getName = function () {
return this.name;
}
function English(name, age, language) {
People.call(this, name, age);
this.language = language;
}
inherits(English, People);
English.prototype.introduce = function () {
console.log('Hi, I am ' + this.getName());
console.log('I speak ' + this.language);
}
function Chinese(name, age, language) {
People.call(this, name, age);
this.language = language;
}
inherits(Chinese, People);
Chinese.prototype.introduce = function () {
console.log('你好,我是' + this.getName());
console.log('我说' + this.language);
}
var en = new English('Byron', 26, 'English');
var cn = new Chinese('色拉油', 27, '汉语');
en.introduce();
cn.introduce();
ES6 class 与继承
"use strict";
class People{
constructor(name, age){
this.name = name;
this.age = age;
}
getName(){
return this.name;
}
}
class English extends People{
constructor(name, age, language){
super(name, age);
this.language = language;
}
introduce(){
console.log('Hi, I am ' + this.getName());
console.log('I speak ' + this.language);
}
}
let en = new English('Byron', 26, 'English');
en.introduce();
语法
label statement
loop:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 5; j++) {
console.log(j);
if (j === 1) {
break loop;
}
}
}
console.log(i);
语句与表达式
var x = { a:1 };
{ a:1 }
{ a:1, b:2 }
立即执行函数
( function() {}() );
( function() {} )();
[ function() {}() ];
~ function() {}();
! function() {}();
+ function() {}();
- function() {}();
delete function() {}();
typeof function() {}();
void function() {}();
new function() {}();
new function() {};
var f = function() {}();
1, function() {}();
1 ^ function() {}();
1 > function() {}();
高阶函数
高阶函数是把函数当做参数或者返回值是函数的函数
回调函数
[1, 2, 3, 4].forEach(function(item){
console.log(item);
});
闭包
闭包由两部分组成
函数
环境:函数创建时作用域内的局部变量
function makeCounter(init) {
var init = init || 0;
return function(){
return ++init;
}
}
var counter = makeCounter(10);
console.log(counter());
console.log(counter());
典型错误
for (var i = 0; i < doms.length; i++) {
doms.eq(i).on('click', function (ev) {
console.log(i);
});
}
for (var i = 0; i < doms.length; i++) {
(function (i) {
doms.eq(i).on('click', function (ev) {
console.log(i);
});
})(i);
}
惰性函数
function eventBinderGenerator() {
if (window.addEventListener) {
return function (element, type, handler) {
element.addEventListener(type, hanlder, false);
}
} else {
return function (element, type, handler) {
element.attachEvent('on' + type, handler.bind(element, window.event));
}
}
}
var addEvent = eventBinderGenerator();
柯里化
一种允许使用部分参数生成函数的方式
function isType(type) {
return function(obj){
return Object.prototype.toString.call(obj) === '[object '+ type +']';
}
}
var isNumber = isType('Number');
console.log(isNumber(1));
console.log(isNumber('s'));
var isArray = isType('Array');
console.log(isArray(1));
console.log(isArray([1, 2, 3]));
function f(n) {
return n * n;
}
function g(n) {
return n * 2;
}
console.log(f(g(5)));
function pipe(f, g) {
return function () {
return f.call(null, g.apply(null, arguments));
}
}
var fn = pipe(f, g);
console.log(fn(5));
尾递归
- 尾调用是指某个函数的最后一步是调用另一个函数
- 函数调用自身,称为递归
- 如果尾调用自身,就称为尾递归
递归很容易发生"栈溢出"错误(stack overflow)
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
柯里化减少参数
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
factorial(5) // 120
反柯里化
Function.prototype.uncurry = function () {
return this.call.bind(this);
};
push 通用化
var push = Array.prototype.push.uncurry();
var arr = [];
push(arr, 1);
push(arr, 2);
push(arr, 3);
console.log(arr);