知乎 - JavaScript 能做什么,该做什么?
JavaScript 标准参考教程 - JavaScript 语言的历史
JS中String是值类型,和C#、Java不同!
typeof
instanceof
Object.prototype.toString.call()
JavaScript 运行分为两个阶段:
先预解析全局作用域,然后执行全局作用域中的代码,
在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。
new Object()
// 构造函数创建对象
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
// 伪代码
function Person (name, age) {
// 当使用 new 操作符调用 Person() 的时候,实际上这里会先创建一个对象
// var instance = {}
// 然后让内部的 this 指向 instance 对象
// this = instance
// 接下来所有针对 this 的操作实际上操作的就是 instance
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
// 在函数的结尾处会将 this 返回,也就是 instance
Javascript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
function Person(name, age){
this.name = name;
this.age = age;
}
var p1 =new Person("Gedy", 18);
console.dir(p1);
console.dir(Person);
console.log(p1.__proto__.constructor === Person); // true
console.log(p1.constructor === Person); // true
console.log(p1.constructor === Person.prototype.constructor); // true
console.log(Person.prototype.constructor === Person); // true
关系
__proto__.constructor
属性,指向的就是构造函数本身prototype.constructor
属性,指向的也是自己本身但是判断对象属于什么类型,还是推荐使用
instanceof
function Person(name, age){
this.name = name;
this.age = age;
this.eat = function(){
console.log("哈哈哈");
}
}
上面的构造函数中,每创建一个对象,就会创建一个匿名方法,大量的对象会导致内存浪费。
__proto__
)指向的是该构造函数的原型对象prototype
)中的方法是可以被实例对象直接访问的prototype
添加实例共享方法/属性实例对象中有个属性,__proto__
,也是对象,叫原型,不是标准的属性,浏览器使用的
构造函数中有一个属性,prototype,也是对象,叫原型,是标准属性,程序员使用
__proto__
或者是prototype
,都是原型对象,案例1:
function Person(name,age) {
this.name=name;
this.age=age;
}
//通过原型来添加方法,解决数据共享,节省内存空间
Person.prototype.eat=function () {
console.log("吃凉菜");
};
var p1=new Person("小明",20);
var p2=new Person("小红",30);
console.log(p1.eat==p2.eat);//true
案例2:
function ChangeStyle(btnObj, dvObj, json) {
this.btnObj = btnObj;
this.dvObj = dvObj;
this.json = json;
}
ChangeStyle.prototype.init = function () {
//点击按钮,改变div多个样式属性值
var that = this;
this.btnObj.onclick = function () {//按钮
for (var key in that.json) {
that.dvObj.style[key] = that.json[key];
}
};
};
与其一个一个设置属性,不如将原型对象设置成一整个对象,但是要注意的是,一定要手动加上constructor
属性,否则这个属性就丢失了。
Student.prototype = {
//手动修改构造器的指向
constructor:Student, // 这个一定要手动加上!!
height: "188",
weight: "55kg",
study: function () {
console.log("学习好开心啊");
},
eat: function () {
console.log("我要吃好吃的");
}
};
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性
搜索顺序: 实例对象 --> 原型
案例:
function Person(age,sex) {
this.age=age;//年龄
this.sex=sex;
this.eat=function () {
console.log("构造函数中的吃");
};
}
Person.prototype.sex="女";
Person.prototype.eat=function () {
console.log("原型对象中的吃");
};
var per=new Person(20,"男");
console.log(per.sex);//男,因为先搜索实例对象
per.eat(); // 构造函数中的吃, 因为先搜索实例对象
this.方法名
this
指代的是实例对象this.方法名
, this
代表实例对象。function Animal(name,age) {
this.name=name;
this.age=age;
}
//原型中添加方法
Animal.prototype.eat=function () {
console.log("动物吃东西");
this.play();
};
Animal.prototype.play=function () {
console.log("玩球");
this.sleep();
};
Animal.prototype.sleep=function () {
console.log("睡觉了");
};
var dog=new Animal("小苏",20);
dog.eat();
直接给Array, String, Date
的prototype
添加方法就行了。
String.prototype.myReverse=function () {
for(var i=this.length-1;i>=0;i--){
console.log(this[i]);
}
};
var str="abcdefg";
str.myReverse();
//为Array内置对象的原型对象中添加方法
Array.prototype.mySort=function () {
for(var i=0;i<this.length-1;i++){
for(var j=0;j<this.length-1-i;j++){
if(this[j]<this[j+1]){
var temp=this[j];
this[j]=this[j+1];
this[j+1]=temp;
}//end if
}// end for
}//end for
};
(function (win) {
var num=10;//局部变量
// //js是一门动态类型的语言,对象没有属性,点了就有了
win.num=num;
})(window);
console.log(num);
没有做的功能
注意点:
Game
类来做,因为他要在每次运动之后判断游戏是否结束document
来注册的document
注册按键时,都需要用bind
来改变函数的this
指向init
: 设置地图的尺寸和格子数,绘制地图init
:清除地图上原有的食物,然后随机生成坐标,创建一个食物元素,绘制在地图上clear
:在地图上消除食物init
:先清除地图上的蛇,然后根据蛇身体的坐标和颜色,绘制在地图上run
:根据蛇的 方向,计算下一步的坐标grow
:让蛇边长一个身体clear
:在地图上清除蛇start
:初始化所有对象,调用其他方法开始游戏runGame
:开启定时器来玩蛇,判断是否游戏结束,以及是否吃到食物bindkey
:注册按键事件来改变蛇的方向endGame
:停止游戏定时器,弹出提示
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<style>
style>
head>
<body>
<script src="jquery-1.12.4.js">script>
<script>
$(function () {
// 公共方法
(function (window) {
Util = {
getRandomValue: function (min, max) {
return parseInt(Math.random() * (max - min) + min)
},
};
window.Util = Util;
}(window));
// 地图类
(function (window) {
function Map(widthPixel, heightPixel, widthGrid, heightGrid) {
this.widthPixel = widthPixel || 400;
this.heightPixel = heightPixel || 400;
// 传递一共横竖几个格子
this.widthGrid = widthGrid || 20;
this.heightGrid = heightGrid || 20;
// 地图元素
this.element = null;
// 地图内单个小块的尺寸
this.gridWidthSize = null;
this.gridHeightSize = null;
}
// 初始化方法
Map.prototype.init = function () {
// 创建div
var element = document.createElement("div");
// 设置样式和尺寸
element.className = "map";
element.style.width = this.widthPixel + "px";
element.style.height = this.heightPixel + "px";
element.style.backgroundColor = "grey";
element.style.position = "relative";
// 加到body中
document.body.appendChild(element);
// 计算单个小方块的尺寸
this.gridWidthSize = this.widthPixel / this.widthGrid;
this.gridHeightSize = this.heightPixel / this.heightGrid;
this.element = element;
}
window.Map = Map;
}(window));
// 食物类
(function (window) {
function Food(color) {
this.color = color || "green";
// 应该是随机产生位置
this.element = null;
// 记录坐标
this.xGrid = null;
this.yGrid = null;
}
Food.prototype.init = function (mapObj) {
// 先清除食物
if(this.element){
this.clear();
}
// 创建食物div,放入map中,并且设置坐标
var foodEle = document.createElement("div");
// 设置样式
foodEle.style.backgroundColor = this.color;
foodEle.style.position = "absolute";
// 随机产生格子number,然后转换成像素
this.xGrid = Util.getRandomValue(0, mapObj.widthGrid);
foodEle.style.left = mapObj.gridWidthSize * this.xGrid + "px"; //Util.gridToPixel(xGrid, mapObj.widthGrid, mapObj.widthPixel) + "px";
this.yGrid = Util.getRandomValue(0, mapObj.heightGrid);
foodEle.style.top = mapObj.gridHeightSize * this.yGrid + "px"; //Util.gridToPixel(yGrid, mapObj.heightGrid, mapObj.heightPixel) + "px";
// 宽高
foodEle.style.width = mapObj.widthPixel / mapObj.widthGrid + "px";
foodEle.style.height = mapObj.heightPixel / mapObj.heightGrid + "px";
// 加入map
mapObj.element.appendChild(foodEle);
// 设置为food对象的属性
this.element = foodEle;
};
Food.prototype.clear = function(){
this.element.parentNode.removeChild(this.element);
this.element = null;
};
window.Food = Food;
}(window));
// 蛇对象
(function (window) {
function Snake(direction, headColor, bodyColor) {
// 初始化位置、长度、方向、身体
this.direction = direction || "right";
this.headColor = headColor || "red";
this.bodyColor = bodyColor || "blue";
// 身体每个小块都有坐标和颜色
this.body = [
{xGrid: 3, yGrid: 2, color: this.headColor},
{xGrid: 2, yGrid: 2, color: this.bodyColor},
{xGrid: 1, yGrid: 2, color: this.bodyColor}
];
this.elements = [];
}
Snake.prototype.init = function (mapObj) {
// 先删除地图上的蛇
this.clear();
// 蛇的初始化,应该固定在起始位置
for (let i = 0; i < this.body.length; i++) {
// console.log(this.body[i]);
var obj = this.body[i];
// 创建div
var ele = document.createElement("div");
// 设置样式
ele.style.position = "absolute";
ele.style.backgroundColor = obj.color;
ele.style.width = mapObj.gridWidthSize + "px";
ele.style.height = mapObj.gridHeightSize + "px";
ele.style.left = obj.xGrid * mapObj.gridWidthSize + "px";
ele.style.top = obj.yGrid * mapObj.gridHeightSize + "px";
// 添加进地图
mapObj.element.appendChild(ele);
// 保存每一个身体部位
this.elements.push(ele);
this.map = mapObj;
}
};
Snake.prototype.run = function () {
// 只行动一次!!根据方向,改变位置,然后画蛇
// 让每一个部位的坐标都变为前一个的坐标
for (let i = this.body.length - 1; i > 0; i--) {
this.body[i].xGrid = this.body[i - 1].xGrid;
this.body[i].yGrid = this.body[i - 1].yGrid;
}
// console.log(this.body[2]);
switch (this.direction) {
case "top":
this.body[0].yGrid -= 1;
break;
case "right":
this.body[0].xGrid += 1;
break;
case "left":
this.body[0].xGrid -= 1;
break;
case "bottom":
this.body[0].yGrid += 1;
break;
}
// 重新绘制蛇
this.init(this.map);
};
Snake.prototype.grow = function(){
// 变长一个 —————— !!只要 复制一次最后的身体即可
var newBody = {
xGrid:this.body[this.body.length-1].xGrid,
yGrid:this.body[this.body.length-1].yGrid,
color:this.bodyColor
}
this.body[this.body.length] = newBody;
console.log(newBody);
};
Snake.prototype.clear = function () {
// 将身体清除
for (let i = this.elements.length - 1; i >= 0; i--) {
this.elements[i].parentNode.removeChild(this.elements[i]);
// 然后从element中去除
this.elements.splice(i, 1);
}
};
// 暴露对象
window.Snake = Snake;
}(window));
// 游戏对象
(function (window) {
function Game() {
};
Game.prototype.start = function () {
// 执行代码, 都设置为自己的属性
this.map = new Map();
this.map.init();
this.food = new Food();
this.food.init(this.map);
this.snake = new Snake();
this.snake.init(this.map);
this.snake.run();
// 注册按键事件
this.bindkey();
// 行走,并且判定游戏结束
this.runGame();
};
Game.prototype.runGame = function(){
this.gameId = setInterval(function(){
this.snake.run();
// 判断是否吃到食物
if (this.snake.body[0].xGrid == this.food.xGrid && this.snake.body[0].yGrid == this.food.yGrid){
// 重新绘制食物
this.food.init(this.map);
// 蛇增长
this.snake.grow();
}
// 每走一次,判断是否游戏结束
if (this.snake.body[0].xGrid >= this.map.widthGrid ||
this.snake.body[0].xGrid < 0 ||
this.snake.body[0].yGrid >= this.map.heightGrid ||
this.snake.body[0].yGrid < 0) {
alert("撞墙了!游戏结束");
this.endGame();
}
}.bind(this),200);
}
Game.prototype.bindkey = function () {
document.addEventListener("keydown", function (e) {
// 上: 38 下40 右39 左37
switch (e.keyCode) {
case 38:
if (this.snake.direction == "bottom") {
return;
}
this.snake.direction = "top";
break;
case 39:
if (this.snake.direction == "left") {
return;
}
this.snake.direction = "right";
break;
case 40:
if (this.snake.direction == "top") {
return;
}
this.snake.direction = "bottom";
break;
case 37:
if (this.snake.direction == "right") {
return;
}
this.snake.direction = "left";
break;
}
}.bind(this));
};
Game.prototype.endGame = function(){
clearInterval(this.gameId);
};
window.Game = Game;
}(window));
var game = new Game();
game.start();
});
script>
body>
html>
原型链:是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__
)来联系的
实例对象的原型(__proto__
) 就是它构造函数的prototype
属性指向的对象,如果修改该属性指向的对象,那么就修改了实例对象的原型。
构造函数.prototype = 实例
prototype
指向如果改变了, 那么实例对象的__proto__
指向也会跟着改变。function Person(){
// this.eat = function(){
// console.log("人实例 的吃");
// }
}
Person.prototype.eat = function () {
console.log("人 原型 的吃");
};
function Student(){
}
Student.prototype = new Person();
var s = new Student();
s.eat(); // 人 原型 的吃
调用eat
时候的查找顺序就变成了:
__proto__
属性__proto__
指向的是其构造函数的prototype
__proto__
来指向其构造函数的prototype
__proto__
最终指向到 一个Object
对象Object
的原型对象为 null
function Person(){}
console.log(Person.prototype.__proto__ == Object.prototype); // true
console.log(Object.prototype.__proto__); //null
var divObj=document.getElementById("dv");
console.dir(divObj);
divObj.__proto__
—>HTMLDivElement.prototype的__proto__
—>HTMLElement.prototype的__proto__
---->Element.prototype的__proto__
---->Node.prototype的__proto__
---->EventTarget.prototype的__proto__
---->Object.prototype没有__proto__
,所以,Object.prototype中的__proto__
是null
面向对象的编程语言中有类(class)的概念(也是一种特殊的数据类型),但是JS不是面向对象的语言,所以,JS中没有类(class),但是JS可以模拟面向对象的思想编程,JS中会通过构造函数来模拟类的概念(class)
继承: 首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承
多态:一个对象有不同的行为,或者是同一个行为针对不同的对象,产生不同的结果,要想有多态,就要先有继承,js中可以模拟多态,但是不会去使用,也不会模拟,
//动物有名字,有体重,有吃东西的行为
//小狗有名字,有体重,有毛色, 有吃东西的行为,还有咬人的行为
//哈士奇名字,有体重,有毛色,性别, 有吃东西的行为,还有咬人的行为,逗主人开心的行为
//动物的构造函数
function Animal(name,weight) {
this.name=name;
this.weight=weight;
}
//动物的原型的方法
Animal.prototype.eat=function () {
console.log("天天吃东西,就是吃");
};
//狗的构造函数
function Dog(color) {
this.color=color;
}
Dog.prototype=new Animal("哮天犬","50kg");
Dog.prototype.bitePerson=function () {
console.log("哼~汪汪~咬死你");
};
//哈士奇
function ErHa(sex) {
this.sex=sex;
}
ErHa.prototype=new Dog("黑白色");
ErHa.prototype.playHost=function () {
console.log("哈哈~要坏衣服,要坏桌子,拆家..嘎嘎...好玩,开心不,惊喜不,意外不");
};
var erHa=new ErHa("雄性");
console.log(erHa.name,erHa.weight,erHa.color);
erHa.eat();
erHa.bitePerson();
erHa.playHost();
因为改变原型指向的同时实现继承,直接初始化了属性,继承过来的属性的值都是一样的, 只能重新调用对象的属性进行重新赋值,
function Person(name,age,sex,weight) {
this.name=name;
this.age=age;
this.sex=sex;
this.weight=weight;
}
Person.prototype.sayHi=function () {
console.log("您好");
};
function Student(score) {
this.score=score;
}
//希望人的类别中的数据可以共享给学生---继承
Student.prototype=new Person("小明",10,"男","50kg");
var stu1=new Student("100");
console.log(stu1.name,stu1.age,stu1.sex,stu1.weight,stu1.score);
stu1.sayHi();
var stu2=new Student("120");
stu2.name="张三";
stu2.age=20;
stu2.sex="女";
console.log(stu2.name,stu2.age,stu2.sex,stu2.weight,stu2.score);
stu2.sayHi();
var stu3=new Student("130");
console.log(stu3.name,stu3.age,stu3.sex,stu3.weight,stu3.score);
stu3.sayHi();
// stu1~stu3 都是同样的 name,age, sex, weight
继承的时候,不用改变原型的指向,直接调用父级的构造函数的方式来为属性赋值就可以了
构造函数名字.call(当前对象,属性,属性,属性....);
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
function Student(name,age,sex,weight,score) {
//借用构造函数
Person.call(this,name,age,sex,weight);
this.score = score;
}
var stu1 = new Student("小明",10,"男","10kg","100");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
var stu2 = new Student("小红",20,"女","20kg","120");
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
var stu3 = new Student("小丽",30,"妖","30kg","130");
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
父级类别中的方法不能继承
将上面2种结合在一起,既能达到每次都调用构造方法,每个实例有不同的属性,同时又能共享实例方法:
function Person(name,age,sex) {
this.name=name;
this.age=age;
this.sex=sex;
}
// 父类的方法
Person.prototype.sayHi=function () {
console.log("阿涅哈斯诶呦");
};
function Student(name,age,sex,score) {
//借用构造函数:属性值重复的问题
Person.call(this,name,age,sex);
this.score=score;
}
//改变原型指向----继承
Student.prototype=new Person();//不传值,为的只是改变继承指向
Student.prototype.eat=function () {
console.log("吃东西");
};
var stu=new Student("小黑",20,"男","100分");
console.log(stu.name,stu.age,stu.sex,stu.score);
stu.sayHi();
stu.eat();
var stu2=new Student("小黑黑",200,"男人","1010分");
console.log(stu2.name,stu2.age,stu2.sex,stu2.score);
stu2.sayHi();
stu2.eat();
直接改变指向:
var obj1={
name:"小糊涂",
age:20,
sleep:function () {
console.log("睡觉了");
}
};
//改变了地址的指向
var obj2=obj1;
拷贝所有属性:
var obj1={
name:"小糊涂",
age:20,
sleep:function () {
console.log("睡觉了");
}
};
var obj2={};
for(var key in obj1){
obj2[key]=obj1[key];
}
或者拷贝原型的属性
原型有一些属性默认是无法访问到的,所以没有被拷贝,但是用户自定义的属性/方法,都是可以拷贝的
function Person() {
}
Person.prototype.age=10;
Person.prototype.sex="男";
Person.prototype.height=100;
Person.prototype.play=function () {
console.log("玩的好开心");
};
var obj2={};
//Person的构造中有原型prototype,prototype就是一个对象,那么里面,age,sex,height,play都是该对象中的属性或者方法
for(var key in Person.prototype){
obj2[key]=Person.prototype[key];
}
if(true){
function f1() {
console.log("哈哈,我又变帅了");
}
}else{
function f1() {
console.log("小苏好猥琐");
}
}
f1();
上面的代码, chrome浏览器最终会显示第一个f1
, 而IE8会显示第二个f1
,因为它发现2个都是函数声明,那么在预解析的时候,会把第二个提前,覆盖第一个。
var ff;
if(true){
ff=function () {
console.log("哈哈,我又变帅了");
};
}else{
ff=function () {
console.log("小苏好猥琐");
};
}
ff();
上面的代码,2种浏览器都是同样的结果
结论:以后宁愿用函数表达式,都不用函数声明
参考链接:
https://www.cnblogs.com/wjaaron/p/8280373.html
https://blog.csdn.net/water_v/article/details/78352119
https://blog.csdn.net/Ayiayi00/article/details/77802810
除了正常运行模式,ECMAscript 5添加第二种运行模式:”严格模式”(strict mode)
"use strict";//严格模式
xxxxx代码
use strict
放在脚本文件的第一行,指定整个脚本都以严格模式运行。use strict
放在函数体的第一行,指定函数以严格模式运行。Function
的实例function F1() {
}
console.dir(F1);
无论是普通函数还是匿名韩式,都是一个对象,那么对象就会有__proto__
, 它指向的原型对象的constructor
为 Function
也就是说,函数本身是Function
对象创建出来的实例,本质上也可以这么写:
var f1=new Function("num1","num2","return num1+num2");
console.log(f1(10,20)); // 30
console.log(f1.__proto__==Function.prototype); // true
Function
的函数实例
prototype
指向Object
的原型对象Function的实例
, 它的__proto__
指向的是 Function
的原型对象Function
原型对象的__proto__
指向Object
的原型对象Function
构造函数,因为他是个构造函数,它的prototype
指向Object
原型 var arr=[
function () {
console.log("十一假期快乐");
},
function () {
console.log("十一假期开心");
}
,
function () {
console.log("十一假期健康");
}
,
function () {
console.log("十一假期安全");
},
function () {
console.log("十一假期如意");
}
];
//回调函数:函数作为参数使用
arr.forEach(function (ele) {
ele();
});
this
调用方式 | 非严格模式 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window |
因为定义的函数,都是window对象的属性,所以普通的函数,都相当于是 window
对象的方法,那么里面的this
就是指向window
//普通函数
function f1() {
console.log(this);
}
f1(); // Window
//定时器中的this
setInterval(function () {
console.log(this); // window
},1000);
//构造函数
function Person() {
console.log(this);
//对象的方法
this.sayHi=function () {
console.log(this);
};
}
var p = new Person(); // 打印当前对象
原型中的方法
Person.prototype.eat=function () {
console.log(this); // 打印当前对象
};
var per=new Person();
per.eat();
//构造函数
function Person() {
//对象的方法
this.sayHi=function () {
console.log(this);
};
}
var p = new Person();
p.sayHi(); // 打印当前对象
作用: 都能调用函数,并且改变函数中this
的指向
call()
方法调用一个函数, 其具有一个指定的 this
值和分别地提供的参数(参数的列表)。apply()
方法调用一个函数, 其具有一个指定的 this
值,以及作为一个数组(或类似数组的对象)提供的参数。fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
thisArg
null
或者 undefined
则内部 this 指向 window
arg1, arg2, ...
argsArray
apply()
与
call()` 非常相似,不同之处在于提供参数的方式。
案例一:
函数名.call/apply
调用函数,如果不传参数或者参数传递为null
,那么不改变this
的指向。
function f1(x,y) {
console.log("结果是:"+(x+y)+this);
return "10000";
}
f1(10,20); // 非严格模式,打印windows
f1.apply(); // 非严格模式,打印windows
f1.call(); // 非严格模式,打印windows
f1.apply(null);
f1.call(null);
案例二:
如果第一个参数传递的不是null,那么第一个参数就是this
的新指向
window.f1.apply(obj,[10,20]); // 打印obj,改变了this
window.f1.call(obj,10,20); // 打印obj,改变了this
案例三:
对于实例方法也是一样,传递的第一个参数会将this
的指向改变。
并且可以和普通方法一样,接收返回值
function Person(age,sex) {
this.age=age;
this.sex=sex;
}
//通过原型添加方法
Person.prototype.sayHi=function (x,y) {
console.log("您好啊:"+this.sex);
return 1000;
};
var per=new Person(10,"男");
per.sayHi(); // 打印的 男
console.log("==============");
function Student(name,sex) {
this.name=name;
this.sex=sex;
}
var stu=new Student("小明","人妖");
var r1=per.sayHi.apply(stu,[10,20]); // 打印 人妖
var r2=per.sayHi.call(stu,10,20); // 打印 人妖
console.log(r1);
console.log(r2);
作用: bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。
var newfun = fun.bind(thisArg[, arg1[, arg2[, ...]]])
this
就无法再更改: 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。this
,如果不传或者传递的null
,那么不会改变bind
之后会返回一个新的函数对象,可以在使用bind
的时候传递函数参数,也可以再之后调用时传递参数function f1(x,y){
console.log(this)
console.log(x, y);
}
obj1 = {
name:"obj1"
};
obj2 = {
name: "obj2"
};
obj3 = {
name:"obj3"
};
var f2 = f1.bind(obj1,1,2);
f2(3,4); // this为 obj1, 打印的是 1,2
var f3 = f1.bind(obj2);
f3(); // this 为obj2
var f4 = f2.bind(obj3);
f4(); // this 为 obj1
特点
bind
修改this
,返回新函数;而call,apply
不仅改变this
,还会调用函数,返回值为原函数的返回值bind
可以在调用bind
时传递参数,也可以在调用新函数时传递参数bind
调用时的参数加上函数调用时传递的参数成员名 | 含义 |
---|---|
prototye |
原型对象 |
name |
函数的名称,只读属性无法被修改 |
arguments |
函数实参,可以使用函数名.arguments 调用 |
length |
函数形参的个数,可以使用函数名.length 调用 |
caller |
函数的调用者, 表示的是哪个函数调用了该函数,没有调用者则返回null |
函数可以作为 其他函数的参数或者返回值
function f1(fn) {
setInterval(function () {
console.log("定时器开始");
fn();
console.log("定时器结束");
},1000);
}
f1(function () {
console.log("好困啊,好累啊,就是想睡觉");
});
案例
用于判断一个对象的类型:
Object.prototype.toString().call( 对象 )
console.log(Object.prototype.toString.call(new Date())); // 打印 [object Date]
function getFunc(type) {
return function (obj) {
return Object.prototype.toString.call(obj) === type; // 这一句话可以判断类型
}
}
var ff = getFunc("[object Array]");
var result = ff([10, 20, 30]);
console.log(result);
var ff1 = getFunc("[object Object]");
var dt = new Date();
var result1 = ff1(dt);
console.log(result1);
变量---->局部变量和全局变量,
作用域:就是变量的使用范围
作用域链:变量的使用,从里向外,层层的搜索,搜索到了就可以直接使用了
层层搜索,搜索到0级作用域的时候,如果还是没有找到这个变量,结果就是报错
var num=10; //作用域链 级别:0
var num2=20;
var str = "abc"
function f1() {
var num2=20;
function f2() {
var num3=30;
console.log(num);
}
f2();
}
f1();
函数A中,有一个函数B,函数B中可以访问函数A内部定义的数据
闭包的模式:
闭包和对象都可以缓存数据
function f1() {
var num=10;
return function () {
console.log(num);
return num;
}
}
var ff= f1();
var result= ff();
console.log(result);
function f2() {
var num=100;
return {
age:num
}
}
var obj= f2();
console.log(obj.age);
function f2() {
var num = 10;
return function () {
num++;
return num;
}
}
var ff = f2();
console.log(ff());//11
console.log(ff());//12
console.log(ff());//13
// 产生多次相同的随机数
function f1() {
var num=parseInt(Math.random()*10+1);
return function () {
console.log(num);
}
}
var ff=f1();
ff();
ff();
ff();
每个闭包函数都缓存了一份共享数据,而不是所有共享一份
function getValue() {
var value=2;
return function () {
//每一次点击的时候,都应该改变当前点击按钮的value值
this.value="赞("+(value++)+")";
}
}
//获取所有的按钮
var btnObjs=my$("input");
//循环遍历每个按钮,注册点击事件
for(var i=0;i<btnObjs.length;i++){
//注册事件
btnObjs[i].onclick=getValue();
}
沙箱:环境,黑盒,在一个虚拟的环境中模拟真实世界,做实验,实验结果和真实世界的结果是一样,但是不会影响真实世界
(function () {
var str="小白喜欢小黑";
str=str.substr(2);
console.log(str);
}());
//沙箱
(function () {
var str="小明喜欢小红";
str=str.substr(2);
console.log(str);
}());
(function () {
document.getElementById("btn").onclick=function () {
console.log("按钮被点击了");
};
}());
var obj1={
age:10,
sex:"男",
car:["奔驰","宝马","特斯拉","奥拓"]
};
//另一个对象
var obj2={};
//写一个函数,作用:把一个对象的属性复制到另一个对象中,浅拷贝
//把a对象中的所有的属性复制到对象b中
function extend(a,b) {
for(var key in a){
b[key]=a[key];
}
}
extend(obj1,obj2);
console.dir(obj2);//开始的时候这个对象是空对象
console.dir(obj1);//有属性
深拷贝:拷贝还是复制,深:把一个对象中所有的属性或者方法,一个一个的找到.并且在另一个对象中开辟相应的空间,一个一个的存储到另一个对象中
varr obj1={
age:10,
sex:"男",
car:["奔驰","宝马","特斯拉","奥拓"],
dog:{
name:"大黄",
age:5,
color:"黑白色"
}
};
var obj2={};//空对象
//通过函数实现,把对象a中的所有的数据深拷贝到对象b中
function extend(a,b) {
for(var key in a){
//先获取a对象中每个属性的值
var item=a[key];
//判断这个属性的值是不是数组
if(item instanceof Array){
//如果是数组,那么在b对象中添加一个新的属性,并且这个属性值也是数组
b[key]=[];
//调用这个方法,把a对象中这个数组的属性值一个一个的复制到b对象的这个数组属性中
extend(item,b[key]);
}else if(item instanceof Object){//判断这个值是不是对象类型的
//如果是对象类型的,那么在b对象中添加一个属性,是一个空对象
b[key]={};
//再次调用这个函数,把a对象中的属性对象的值一个一个的复制到b对象的这个属性对象中
extend(item,b[key]);
}else{
//如果值是普通的数据,直接复制到b对象的这个属性中
b[key]=item;
}
}
}
extend(obj1,obj2);
console.dir(obj1);
console.dir(obj2);
使用步骤:
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意单个字符 |
\d | 匹配数字 |
\D | 匹配任意非数字的字符 |
\w | 匹配字母或数字或下划线 |
\W | 匹配任意不是字母,数字,下划线 |
\s | 匹配任意的空白符 |
\S | 匹配任意不是空白符的字符 |
^ | 表示匹配行首的文本(以谁开始) / 在[] 内则是取反 |
$ | 表示匹配行尾的文本(以谁结束) |
限定符 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? |
重复零次或一次 / 阻止贪婪模式 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
符号 | 说明 |
---|---|
[] | 字符串用中括号括起来,表示匹配其中的任一字符,相当于或的意思; 把正则表达式中元字符的意义干掉 [.] 就是一个. |
[^] | 取反 |
\ |
转义符 |
| |
或者,选择两者中的一个。注意| 将左右两边分为两部分,而不管左右两边有多长多乱 |
() | 提升优先级/ 分组 |
[\u4e00-\u9fa5] |
匹配汉字 |
邮箱: [0-9a-zA-Z_.-]+[@][0-9a-zA-Z_.-]+([.][a-zA-Z]+){1,2}
习惯性的把一些不确定的特殊符号,放在
[]
里,消除可能存在的特殊含义
/ 和 /
之间可以直接写正则,不需要转义,这种写法会创建正则对象test
方法相当于查找是否满足,只要有就行,相当于python
的search
"\\d"
相当于/\d/
new RegExp(正则, 模式)
标志 | 说明 |
---|---|
i |
忽略大小写 |
g |
全局匹配 |
gi |
全局匹配+忽略大小写 |
var reg=new RegExp(/\d{5}/);
var flag=reg.test("我的电话是10086"); // true
/正则内容/
/正则内容/模式
var reg=/\d{1,5}/;
var flag=reg.test("小苏的幸运数字:888"); //true
console.log(/./.test("除了回车换行以为的任意字符"));//true
console.log(/.*/.test("0个到多个"));//true
console.log(/.+/.test("1个到多个"));//true
console.log(/.?/.test("哈哈"));//true
console.log(/[0-9]/.test("9527"));//true
console.log(/[a-z]/.test("what"));//true
console.log(/[A-Z]/.test("Are"));//true
console.log(/[a-zA-Z]/.test("干啥子"));//false
console.log(/[0-9a-zA-Z]/.test("9ebg"));//true
console.log(/b|(ara)/.test("abra"));//true
console.log(/[a-z]{2,3}/.test("arfsf"));//
方法 | 介绍 | 案例 |
---|---|---|
str.match(正则) |
按照正则匹配第一个,把匹配到的返回 | var array = str.match(/\d+/); |
str.match(正则 全局模式) |
按照正则匹配所有结果,返回数组 | var array = str.match(/\d+/g); |
RegExp.$1 |
获取匹配 之后的组 | console.log(RegExp.$3); |
str.replace(正则, 替换成的字符串) |
把str中第一个匹配到正则的内容,替换掉 | |
str.replace(正则 全局模式, 替换成的字符串) |
把str中所有匹配到正则的内容,替换掉 | |
正则对象.exec(str) |
根据正则匹配str,然后把匹配到的第一个结果放在数组中返回; 要匹配全部应该多次调用该方法 |
var array=reg.exec(str); |
RegExp.$组号
:var str="2017-11-12";
var array=str.match(/(\d{4})[-](\d{2})[-](\d{2})/g);
//console.log(array);
//正则表达式对象.$3
console.log(RegExp.$3);
var bool = /(\d{4})[-](\d{2})[-](\d{2})/g.test(str);
console.log(bool); // true
console.log(RegExp.$2); // 11
var str="小苏好帅哦,真的是太帅了,帅,就是真帅";
str=str.replace(/帅/g,"猥琐");
console.log(str);
var str="HhpphH";//SSppSS
str=str.replace(/[h]/gi,"S");
console.log(str);
正则.exec
案例:每次调用exec
返回的结构是:
[ 匹配到的字符串, {index: 该字符串起始索引}, {input: 原字符串}]
null
每次匹配都是从上一次之后开始往后找
var str = "中国移动:10086,中国联通:10010,中国电信:10000";
var reg=/\d{5}/g;
//通过正则表达式匹配这个字符串
var array=reg.exec(str);
while (array!=null){
//输出匹配的内容
console.log(array[0]);
array=reg.exec(str);
}
在JavaScript中,除了5种原始数据类型之外,其他所有的都是对象,包括函数(Function)。
//数组
var arr=[10,20,30];
arr[3]=100;
console.log(arr.length);
//对象---假的数组
var obj={
0:10,
1:20,
2:30,
length:3
};
new Array()
或 []
创建出来的数组对象length
属性来表示内容的长度length
会根据增加的内容自动变化,而伪数组需要手动修改只有JS中的数组,可以这样:
var arr = []
console.log(arr.length); // 0
arr[2] = 3; // 直接给不存在的索引赋值,JS语言可以,python不行
console.log(arr.length); // 3
arguments
document.getElementsByTags
得到的列表)$("div")
)this
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
[].push.call(obj, 'd'); // 调用push
console.log([].slice.call(obj)); // 调用slice
;[].forEach.call(obj, function (num, index) { // 调用forEach
console.log(num)
});
小结
第一天:
原型及作用
实例对象、构造函数、原型对象之间的关系
原型的简单语法
例子——随机食物
第二天:
贪吃蛇
原型添加方法的练习
私有的函数
陌生的方法:bind(对象)
三个对象
第三天:
原型链
原型的指向是否可以改变
继承
如何实现继承
原型的方式继承
借用构造函数继承
组合继承
拷贝继承
函数的不同表现方式
函数的调用的不同方式
this指向
严格模式
函数也是对象
数组中的函数如何调用
第四天:
apply和call方法
bind方法
函数中的几个成员介绍
高阶函数
函数作为参数和返回值
作用域,作用域链,预解析
闭包
沙箱
递归
第五天:
浅拷贝
深拷贝
遍历DOM树
正则表达式
元字符
数组和伪数组